From 25cdacece2afc157c2edc92f22406f6dc2f26d20 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 2 May 2022 15:19:19 +0200 Subject: [PATCH 0001/1995] [feat]: Integrated in lastest ABCI++ from tendermint master. The ABCI feature is working. Still need to fix the ABCI++ feature --- Cargo.lock | 66 ++--- apps/Cargo.toml | 10 +- apps/src/lib/node/ledger/events.rs | 11 +- apps/src/lib/node/ledger/mod.rs | 16 +- .../lib/node/ledger/shell/finalize_block.rs | 210 +++++---------- apps/src/lib/node/ledger/shell/mod.rs | 47 ++-- .../lib/node/ledger/shell/process_proposal.rs | 152 +++++++---- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 109 ++++---- .../node/ledger/shims/abcipp_shim_types.rs | 239 ++++++++++++------ apps/src/lib/node/ledger/storage/rocksdb.rs | 14 +- shared/Cargo.toml | 8 +- shared/src/ledger/ibc/vp/client.rs | 13 +- shared/src/ledger/ibc/vp/mod.rs | 30 +-- shared/src/ledger/storage/mockdb.rs | 13 +- shared/src/ledger/storage/mod.rs | 17 +- shared/src/types/hash.rs | 9 + shared/src/types/storage.rs | 21 ++ shared/src/types/time.rs | 27 ++ shared/src/vm/host_env.rs | 9 +- tests/src/vm_host_env/ibc.rs | 33 +-- 20 files changed, 568 insertions(+), 486 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7c12cd7fe2..4f20ec9cc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,9 +92,9 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_abcipp_v0.23.5)", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", "ibc 0.12.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?branch=yuji/v0.12.0_abcipp_v0.23.5)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", "ics23", "itertools 0.10.3", @@ -113,9 +113,9 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "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=bat/abcipp-rebase-master)", "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=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "test-log", "thiserror", @@ -195,13 +195,13 @@ dependencies = [ "sysinfo", "tar", "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=bat/abcipp-rebase-master)", "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/abcipp-v0.23.5)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "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/abcipp-v0.23.5)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 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/abcipp-v0.23.5)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "test-log", "thiserror", @@ -211,7 +211,7 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/abcipp-v0.23.5-tracing)", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", "tracing 0.1.31", "tracing-log", @@ -2775,12 +2775,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_abcipp_v0.23.5#7d811ab5567d027c11e8f5a19102cdeb93e2af11" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#eeba012008ee86e7c35f36b2f5d323cc11d81470" 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_abcipp_v0.23.5)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", "ics23", "num-traits 0.2.14", "prost 0.9.0", @@ -2791,10 +2791,10 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint-light-client-verifier 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/abcipp-v0.23.5)", - "tendermint-testgen 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=bat/abcipp-rebase-master)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "time 0.3.7", "tracing 0.1.31", ] @@ -2829,13 +2829,13 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_abcipp_v0.23.5#7d811ab5567d027c11e8f5a19102cdeb93e2af11" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#eeba012008ee86e7c35f36b2f5d323cc11d81470" dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.136", - "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=bat/abcipp-rebase-master)", "tonic", ] @@ -6284,7 +6284,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6304,7 +6304,7 @@ dependencies = [ "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)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "time 0.3.7", "zeroize", ] @@ -6340,12 +6340,12 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "flex-error", "serde 1.0.136", "serde_json", - "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=bat/abcipp-rebase-master)", "toml", "url 2.2.2", ] @@ -6366,13 +6366,13 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "derive_more", "flex-error", "serde 1.0.136", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint-rpc 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=bat/abcipp-rebase-master)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "time 0.3.7", ] @@ -6392,7 +6392,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6426,7 +6426,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "async-trait", "async-tungstenite", @@ -6444,9 +6444,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint-config 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/abcipp-v0.23.5)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "thiserror", "time 0.3.7", "tokio", @@ -6492,7 +6492,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6500,7 +6500,7 @@ dependencies = [ "serde_json", "simple-error", "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=bat/abcipp-rebase-master)", "time 0.3.7", ] @@ -6930,13 +6930,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=yuji/abcipp-v0.23.5-tracing#e2f6c1e3d23ee80b24e4d91018e242f15397809c" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#25a0c673ea6748730f86f7f20c933d0f07b50621" dependencies = [ "bytes 1.1.0", "futures 0.3.19", "pin-project 1.0.10", "prost 0.9.0", - "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=bat/abcipp-rebase-master)", "tokio", "tokio-stream", "tokio-util", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 890548ce0b..b18291bf4f 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -122,12 +122,12 @@ 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", branch = "yuji/abcipp-v0.23.5", optional = true} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/abcipp-v0.23.5", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", optional = true} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", 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", branch = "yuji/abcipp-v0.23.5", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", 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", branch = "yuji/abcipp-v0.23.5", optional = true, features = ["http-client", "websocket-client"]} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", 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} thiserror = "1.0.30" @@ -137,7 +137,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", branch = "yuji/abcipp-v0.23.5-tracing", optional = true} +tower-abci = {git = "https://github.com/heliaxdev/tower-abci", branch = "bat/abcipp-rebase-master", optional = true} tower-abci-old = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", branch = "yuji/rebase_v0.23.5_tracing", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index a6211058f8..2a80953290 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -12,14 +12,14 @@ use tendermint_proto_abci::abci::EventAttribute; /// Custom events that can be queried from Tendermint /// using a websocket client -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Event { pub event_type: EventType, pub attributes: HashMap, } /// The two types of custom events we currently use -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum EventType { // The transaction was accepted to be included in a block Accepted, @@ -102,9 +102,16 @@ impl Event { event } + /// Check if the events keys contains a given string pub fn contains_key(&self, key: &str) -> bool { self.attributes.contains_key(key) } + + /// Get the value corresponding to a given key, if it exists. + /// Else return None. + pub fn get(&self, key: &str) -> Option<&String> { + self.attributes.get(key) + } } impl Index<&str> for Event { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 849ff95a99..4781179c54 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -72,18 +72,14 @@ impl Shell { Request::VerifyHeader(_req) => { Ok(Response::VerifyHeader(self.verify_header(_req))) } + #[cfg(not(feature = "ABCI"))] Request::ProcessProposal(block) => { - #[cfg(not(feature = "ABCI"))] - { - Ok(Response::ProcessProposal(self.process_proposal(block))) - } - #[cfg(feature = "ABCI")] - { - Ok(Response::ProcessProposal( - self.process_and_decode_proposal(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))) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1b5dfcc8d7..329515306f 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,8 +1,6 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use anoma::types::storage::BlockHash; -#[cfg(not(feature = "ABCI"))] -use tendermint::block::Header; +use anoma::types::storage::{BlockHash, Header}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::Evidence; #[cfg(not(feature = "ABCI"))] @@ -11,8 +9,6 @@ use tendermint_proto::crypto::PublicKey as TendermintPublicKey; use tendermint_proto_abci::abci::Evidence; #[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; -#[cfg(feature = "ABCI")] -use tendermint_stable::block::Header; use super::*; @@ -55,8 +51,9 @@ where tx } else { tracing::error!( - "Internal logic error: FinalizeBlock received a tx that \ - could not be deserialized to a Tx type" + "FinalizeBlock received a tx that could not be \ + deserialized to a Tx type. This is likely a protocol \ + transaction." ); continue; }; @@ -68,7 +65,7 @@ where if ErrorCodes::from_u32(processed_tx.result.code).unwrap() == ErrorCodes::InvalidSig { - let mut tx_result = match process_tx(tx.clone()) { + let mut tx_event = match process_tx(tx.clone()) { Ok(tx @ TxType::Wrapper(_)) | Ok(tx @ TxType::Protocol(_)) => { Event::new_tx_event(&tx, height.0) @@ -89,11 +86,11 @@ where } }, }; - tx_result["code"] = processed_tx.result.code.to_string(); - tx_result["info"] = + tx_event["code"] = processed_tx.result.code.to_string(); + tx_event["info"] = format!("Tx rejected: {}", &processed_tx.result.info); - tx_result["gas_used"] = "0".into(); - response.events.push(tx_result.into()); + tx_event["gas_used"] = "0".into(); + response.events.push(tx_event); continue; } @@ -114,12 +111,12 @@ where != ErrorCodes::Ok && !req.reject_all_decrypted { - let mut tx_result = Event::new_tx_event(&tx_type, height.0); - tx_result["code"] = processed_tx.result.code.to_string(); - tx_result["info"] = + let mut tx_event = Event::new_tx_event(&tx_type, height.0); + tx_event["code"] = processed_tx.result.code.to_string(); + tx_event["info"] = format!("Tx rejected: {}", &processed_tx.result.info); - tx_result["gas_used"] = "0".into(); - response.events.push(tx_result.into()); + tx_event["gas_used"] = "0".into(); + response.events.push(tx_event); // if the rejected tx was decrypted, remove it // from the queue of txs to be processed if let TxType::Decrypted(_) = &tx_type { @@ -128,7 +125,7 @@ where continue; } - let mut tx_result = match &tx_type { + let mut tx_event = match &tx_type { TxType::Wrapper(_wrapper) => { if !cfg!(feature = "ABCI") { self.storage.tx_queue.push(_wrapper.clone()); @@ -141,15 +138,15 @@ where // of those. New encrypted txs may still // be accepted. if req.reject_all_decrypted { - let mut tx_result = + let mut tx_event = Event::new_tx_event(&tx_type, height.0); - tx_result["code"] = ErrorCodes::InvalidOrder.into(); - tx_result["info"] = "All decrypted txs rejected as \ - they were not submitted in \ - correct order" + tx_event["code"] = ErrorCodes::InvalidOrder.into(); + tx_event["info"] = "All decrypted txs rejected as \ + they were not submitted in \ + correct order" .into(); - tx_result["gas_used"] = "0".into(); - response.events.push(tx_result.into()); + tx_event["gas_used"] = "0".into(); + response.events.push(tx_event); continue; } // We remove the corresponding wrapper tx from the queue @@ -192,19 +189,19 @@ where result ); self.write_log.commit_tx(); - if !tx_result.contains_key("code") { - tx_result["code"] = ErrorCodes::Ok.into(); + if !tx_event.contains_key("code") { + tx_event["code"] = ErrorCodes::Ok.into(); } if let Some(ibc_event) = &result.ibc_event { - // Add the IBC event besides the tx_result + // Add the IBC event besides the tx_event let event = Event::from(ibc_event.clone()); - response.events.push(event.into()); + response.events.push(event); } match serde_json::to_string( &result.initialized_accounts, ) { Ok(initialized_accounts) => { - tx_result["initialized_accounts"] = + tx_event["initialized_accounts"] = initialized_accounts; } Err(err) => { @@ -222,23 +219,23 @@ where result.vps_result.rejected_vps ); self.write_log.drop_tx(); - tx_result["code"] = ErrorCodes::InvalidTx.into(); + tx_event["code"] = ErrorCodes::InvalidTx.into(); } - tx_result["gas_used"] = result.gas_used.to_string(); - tx_result["info"] = result.to_string(); + tx_event["gas_used"] = result.gas_used.to_string(); + tx_event["info"] = result.to_string(); } Err(msg) => { tracing::info!("Transaction failed with: {}", msg); self.write_log.drop_tx(); - tx_result["gas_used"] = self + tx_event["gas_used"] = self .gas_meter .get_current_transaction_gas() .to_string(); - tx_result["info"] = msg.to_string(); - tx_result["code"] = ErrorCodes::WasmRuntimeError.into(); + tx_event["info"] = msg.to_string(); + tx_event["code"] = ErrorCodes::WasmRuntimeError.into(); } } - response.events.push(tx_result.into()); + response.events.push(tx_event); } self.reset_tx_queue_iter(); @@ -246,7 +243,7 @@ where self.update_epoch(&mut response); } - response.gas_used = self + let _ = self .gas_meter .finalize_transaction() .map_err(|_| Error::GasOverflow)?; @@ -264,7 +261,7 @@ where hash: BlockHash, byzantine_validators: Vec, ) -> (BlockHeight, bool) { - let height = BlockHeight(header.height.into()); + let height = self.storage.last_height + 1; self.gas_meter.reset(); @@ -283,11 +280,8 @@ where .header .as_ref() .expect("Header must have been set in prepare_proposal."); - let height = BlockHeight(header.height.into()); - let time: DateTimeUtc = header - .time - .try_into() - .expect("Time conversion shouldn't failed"); + let height = self.storage.last_height + 1; + let time = header.time; let new_epoch = self .storage .update_epoch(height, time) @@ -491,18 +485,10 @@ mod test_finalize_block { .iter() .enumerate() { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); - assert_eq!( - String::from_utf8(code).expect("Test failed"), - index.rem_euclid(2).to_string() - ); + 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()); } } @@ -592,18 +578,10 @@ mod test_finalize_block { }) .expect("Test failed") { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); - assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::InvalidTx) - ); + 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()); @@ -725,29 +703,12 @@ mod test_finalize_block { }) .expect("Test failed") { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); - assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::Undecryptable) - ); + 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 = String::from_utf8( - event - .attributes - .iter() - .find(|attr| attr.key == "log".as_bytes()) - .expect("Test failed") - .value - .clone(), - ) - .expect("Test failed"); + 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 @@ -845,62 +806,29 @@ mod test_finalize_block { { if index < 2 { // these should be accepted wrapper txs - - #[cfg(not(feature = "ABCI"))] - { - assert_eq!(event.r#type, "accepted"); - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); - } - #[cfg(feature = "ABCI")] - { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); + if !cfg!(feature = "ABCI") { assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::Ok) + event.event_type.to_string(), + String::from("accepted") ); - } - } else { - // these should be accepted decrypted txs - assert_eq!(event.r#type, "applied"); - #[cfg(not(feature = "ABCI"))] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); - } - #[cfg(feature = "ABCI")] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); + } else { assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::Ok) + 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::Ok).as_str()); + } else { + // these should be accepted decrypted txs + 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::Ok).as_str()); } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 11e9a0bfa1..5b56291660 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -620,8 +620,9 @@ mod test_utils { use anoma::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; use anoma::types::address::{xan, EstablishedAddressGen}; use anoma::types::chain::ChainId; + use anoma::types::hash::Hash; use anoma::types::key::*; - use anoma::types::storage::{BlockHash, Epoch}; + use anoma::types::storage::{BlockHash, Epoch, Header}; use anoma::types::transaction::Fee; use tempfile::tempdir; #[cfg(not(feature = "ABCI"))] @@ -635,18 +636,14 @@ mod test_utils { #[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf::Timestamp; #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::{Event as TmEvent, RequestInitChain}; + use tendermint_proto_abci::abci::{RequestDeliverTx, RequestInitChain}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf::Timestamp; - #[cfg(feature = "ABCI")] - use tendermint_stable::block::{header::Version, Header}; - #[cfg(feature = "ABCI")] - use tendermint_stable::{Hash, Time}; use tokio::sync::mpsc::UnboundedReceiver; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ - FinalizeBlock, ProcessProposal, + FinalizeBlock, ProcessProposal, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; @@ -728,14 +725,19 @@ mod test_utils { pub fn process_proposal( &mut self, req: ProcessProposal, - ) -> shim::response::ProcessProposal { + ) -> Vec { #[cfg(not(feature = "ABCI"))] { - self.shell.process_proposal(req) + req.txs + .iter() + .map(|tx_bytes| self.process_single_tx(tx_bytes)) + .collect(); } #[cfg(feature = "ABCI")] { - self.shell.process_and_decode_proposal(req) + vec![self.shell.process_and_decode_proposal(RequestDeliverTx { + tx: req.tx, + })] } } @@ -744,7 +746,7 @@ mod test_utils { pub fn finalize_block( &mut self, req: FinalizeBlock, - ) -> Result> { + ) -> Result> { match self.shell.finalize_block(req) { Ok(resp) => Ok(resp.events), Err(err) => Err(err), @@ -795,26 +797,9 @@ mod test_utils { FinalizeBlock { hash: BlockHash([0u8; 32]), header: Header { - version: Version { block: 0, app: 0 }, - chain_id: String::from("test") - .try_into() - .expect("Should not fail"), - height: 0u64.try_into().expect("Should not fail"), - time: Time::now(), - last_block_id: None, - last_commit_hash: None, - data_hash: None, - validators_hash: Hash::None, - next_validators_hash: Hash::None, - consensus_hash: Hash::None, - app_hash: Vec::::new() - .try_into() - .expect("Should not fail"), - last_results_hash: None, - evidence_hash: None, - proposer_address: vec![0u8; 20] - .try_into() - .expect("Should not fail"), + hash: Hash([0; 32]), + time: DateTimeUtc::now(), + next_validators_hash: Hash([0; 32]), }, byzantine_validators: vec![], txs: vec![], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5519c95c6f..51f726811e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,5 +1,11 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +#[cfg(not(feature = "ABCI"))] +use tendermint_proto::abci::{ + ExecTxResult, RequestProcessProposal, ResponseProcessProposal, +}; +use tendermint_proto_abci::abci::RequestDeliverTx; + use super::*; impl Shell @@ -15,10 +21,34 @@ where Default::default() } - /// Validate a transaction request. On success, the transaction will - /// included in the mempool and propagated to peers, otherwise it will be - /// rejected. - /// + /// Check all the txs in a block. Some txs may be incorrect, + /// 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, + ) -> ResponseProcessProposal { + let tx_results: Vec = req + .txs + .iter() + .map(|tx_bytes| { + ExecTxResult::from(self.process_single_tx(tx_bytes)) + }) + .collect(); + + ResponseProcessProposal { + status: if tx_results.iter().any(|res| res.code > 3) { + 1 + } else { + 0 + }, + tx_results, + ..Default::default() + } + } + /// 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. /// @@ -37,19 +67,15 @@ 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 fn process_proposal( - &mut self, - req: shim::request::ProcessProposal, - ) -> shim::response::ProcessProposal { - let tx = match Tx::try_from(req.tx.as_ref()) { + fn process_single_tx(&mut self, tx_bytes: &[u8]) -> TxResult { + let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { - return shim::response::TxResult { + return TxResult { code: ErrorCodes::InvalidTx.into(), info: "The submitted transaction was not deserializable" .into(), - } - .into(); + }; } }; // TODO: This should not be hardcoded @@ -115,7 +141,7 @@ where info: format!( "The ciphertext of the wrapped tx {} is \ invalid", - hash_tx(&req.tx) + hash_tx(tx_bytes) ), } } else { @@ -125,14 +151,14 @@ where .unwrap_or_default(); if tx.fee.amount <= balance { - shim::response::TxResult { + TxResult { code: ErrorCodes::Ok.into(), info: "Process proposal accepted this \ transaction" .into(), } } else { - shim::response::TxResult { + TxResult { code: ErrorCodes::InvalidTx.into(), info: "The address given does not have \ sufficient balance to pay fee" @@ -143,7 +169,6 @@ where } }, } - .into() } /// If we are not using ABCI++, we check the wrapper, @@ -151,14 +176,14 @@ where #[cfg(feature = "ABCI")] pub fn process_and_decode_proposal( &mut self, - req: shim::request::ProcessProposal, - ) -> shim::response::ProcessProposal { + 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::response::ProcessProposal { - result: shim::response::TxResult { + return shim::request::ProcessedTx { + result: TxResult { code: ErrorCodes::InvalidTx.into(), info: "The submitted transaction was not \ deserializable" @@ -172,14 +197,12 @@ where match process_tx(req_tx.clone()) { Ok(TxType::Wrapper(_)) => {} Ok(TxType::Protocol(_)) => { - let tx_bytes = req.tx.clone(); - let mut response = self.process_proposal(req); - response.tx = tx_bytes; - return response; + let result = self.process_single_tx(&req.tx); + return shim::request::ProcessedTx { tx: req.tx, result }; } Ok(_) => { - return shim::response::ProcessProposal { - result: shim::response::TxResult { + return shim::request::ProcessedTx { + result: TxResult { code: ErrorCodes::InvalidTx.into(), info: "Transaction rejected: Non-encrypted \ transactions are not supported" @@ -194,10 +217,10 @@ where } } - let mut wrapper_resp = self.process_proposal(req.clone()); + let wrapper_resp = self.process_single_tx(&req.tx); let privkey = ::G2Affine::prime_subgroup_generator(); - if wrapper_resp.result.code == 0 { + 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) { @@ -208,25 +231,25 @@ where // we are not checking that txs are out of order self.storage.tx_queue.push(wrapper); // check the decoded tx - let mut decoded_resp = - self.process_proposal(shim::request::ProcessProposal { - tx: decoded.clone(), - }); - - // this ensures that emitted events are of the correct type - decoded_resp.tx = decoded; + let decoded_resp = self.process_single_tx(&decoded); // this ensures that the tx queue is empty even if an error - // happend in [`process_proposal`]. + // happened in [`process_proposal`]. self.storage.tx_queue.pop(); - decoded_resp + 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 { - // this ensures that emitted events are of the correct type - wrapper_resp.tx = req.tx; - wrapper_resp + shim::request::ProcessedTx { + // this ensures that emitted events are of the correct type + tx: req.tx, + result: wrapper_resp, + } } } @@ -294,7 +317,12 @@ mod test_process_proposal { #[allow(clippy::redundant_clone)] let request = ProcessProposal { tx: tx.clone() }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, @@ -370,7 +398,12 @@ mod test_process_proposal { let request = ProcessProposal { tx: new_tx.to_bytes(), }; - let response = shell.process_proposal(request); + let response = + if let [response] = shell.process_proposal(request).as_slice() { + response.clone() + } else { + panic!("Test failed") + }; let expected_error = "Signature verification failed: Invalid signature"; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert!( @@ -412,7 +445,12 @@ mod test_process_proposal { let request = ProcessProposal { tx: wrapper.to_bytes(), }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, @@ -464,7 +502,12 @@ mod test_process_proposal { tx: wrapper.to_bytes(), }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, @@ -612,7 +655,12 @@ mod test_process_proposal { }; let request = ProcessProposal { tx: tx.to_bytes() }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); #[cfg(feature = "ABCI")] { @@ -676,7 +724,12 @@ mod test_process_proposal { let request = ProcessProposal { tx: signed.to_bytes(), }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); #[cfg(feature = "ABCI")] { @@ -731,7 +784,12 @@ mod test_process_proposal { ); let tx = Tx::from(TxType::Raw(tx)); let request = ProcessProposal { tx: tx.to_bytes() }; - let response = shell.process_proposal(request); + let response = + if let [resp] = shell.process_proposal(request).as_slice() { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 8a96375f44..7fd75f3c7c 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -1,11 +1,12 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::future::Future; use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; -use anoma::types::storage::BlockHeight; use futures::future::FutureExt; +#[cfg(feature = "ABCI")] +use tendermint_proto_abci::abci::RequestBeginBlock; use tokio::sync::mpsc::UnboundedSender; use tower::Service; #[cfg(not(feature = "ABCI"))] @@ -14,10 +15,10 @@ use tower_abci::{BoxError, Request as Req, Response as Resp}; use tower_abci_old::{BoxError, Request as Req, Response as Resp}; use super::super::Shell; -use super::abcipp_shim_types::shim::{request, Error, Request, Response}; +use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ - BeginBlock, ProcessedTx, + FinalizeBlock, ProcessedTx, }; /// The shim wraps the shell, which implements ABCI++. @@ -26,8 +27,9 @@ use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ #[derive(Debug)] pub struct AbcippShim { service: Shell, - begin_block_request: Option, - block_txs: Vec, + #[cfg(feature = "ABCI")] + begin_block_request: Option, + processed_txs: Vec, shell_recv: std::sync::mpsc::Receiver<( Req, tokio::sync::oneshot::Sender>, @@ -59,7 +61,7 @@ impl AbcippShim { tx_wasm_compilation_cache, ), begin_block_request: None, - block_txs: vec![], + processed_txs: vec![], shell_recv, }, AbciService { shell_send }, @@ -71,48 +73,69 @@ impl AbcippShim { 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 + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + for (result, tx) in resp + .tx_results + .iter() + .map(TxResult::from) + .zip(txs.into_iter()) + { + self.processed_txs + .push(ProcessedTx { tx, result }); + } + Ok(Resp::ProcessProposal(resp)) + } + _ => unreachable!(), + }) + } + #[cfg(not(feature = "ABCI"))] + Req::FinalizeBlock(block) => { + let mut txs = vec![]; + std::mem::swap(&mut txs, &mut self.processed_txs); + let mut finalize_req = 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())) + } + _ => 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.try_into().unwrap_or_else(|_| { - panic!("Could not read begin block request"); - })); + self.begin_block_request = Some(block); Ok(Resp::BeginBlock(Default::default())) } + #[cfg(feature = "ABCI")] Req::DeliverTx(deliver_tx) => { - // We call [`process_proposal`] to report back the validity + // We call [`process_single_tx`] to report back the validity // of the tx to tendermint. - // Invariant: The service call with - // `Request::ProcessProposal` - // must always return `Response::ProcessProposal` self.service - .call(Request::ProcessProposal( - #[cfg(not(feature = "ABCI"))] - deliver_tx.tx.clone().into(), - #[cfg(feature = "ABCI")] - deliver_tx.tx.into(), - )) + .call(Request::DeliverTx(deliver_tx)) .map_err(Error::from) .and_then(|res| match res { - Response::ProcessProposal(resp) => { - self.block_txs.push(ProcessedTx { - #[cfg(not(feature = "ABCI"))] - tx: deliver_tx.tx, - #[cfg(feature = "ABCI")] - tx: resp.tx, - result: resp.result, - }); + Response::DeliverTx(resp) => { + self.processed_txs.push(resp); Ok(Resp::DeliverTx(Default::default())) } _ => unreachable!(), }) } - Req::EndBlock(end) => { - BlockHeight::try_from(end.height).unwrap_or_else(|_| { - panic!("Unexpected block height {}", end.height) - }); + #[cfg(feature = "ABCI")] + Req::EndBlock(_) => { let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.block_txs); + std::mem::swap(&mut txs, &mut self.processed_txs); // If the wrapper txs were not properly submitted, reject // all txs let out_of_order = @@ -122,22 +145,16 @@ impl AbcippShim { // and included in the proposed block after the current self.service.reset_tx_queue_iter(); } - let begin_block_request = - self.begin_block_request.take().unwrap(); + let mut end_block_request: FinalizeBlock = + self.begin_block_request.take().unwrap().into(); + end_block_request.reject_all_decrypted = out_of_order; + end_block_request.txs = txs; self.service - .call(Request::FinalizeBlock(request::FinalizeBlock { - hash: begin_block_request.hash, - header: begin_block_request.header, - byzantine_validators: begin_block_request - .byzantine_validators, - txs, - reject_all_decrypted: out_of_order, - })) + .call(Request::FinalizeBlock(end_block_request)) .map_err(Error::from) .and_then(|res| match res { Response::FinalizeBlock(resp) => { - let x = Resp::EndBlock(resp.into()); - Ok(x) + Ok(Resp::EndBlock(resp.into())) } _ => Err(Error::ConvertResp(res)), }) 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 389a23b772..081446df0d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -9,24 +9,25 @@ pub mod shim { #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, - RequestExtendVote, RequestFlush, RequestInfo, RequestInitChain, - RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, - RequestPrepareProposal, RequestQuery, RequestVerifyVoteExtension, - ResponseApplySnapshotChunk, ResponseCheckTx, ResponseCommit, - ResponseEcho, ResponseExtendVote, ResponseFlush, ResponseInfo, - ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, - ResponseOfferSnapshot, ResponsePrepareProposal, ResponseQuery, - ResponseVerifyVoteExtension, + RequestExtendVote, RequestFinalizeBlock, RequestFlush, RequestInfo, + RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, + RequestOfferSnapshot, RequestPrepareProposal, RequestProcessProposal, + RequestQuery, RequestVerifyVoteExtension, ResponseApplySnapshotChunk, + ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseExtendVote, + ResponseFinalizeBlock, ResponseFlush, ResponseInfo, ResponseInitChain, + ResponseListSnapshots, ResponseLoadSnapshotChunk, + ResponseOfferSnapshot, ResponsePrepareProposal, + ResponseProcessProposal, ResponseQuery, ResponseVerifyVoteExtension, }; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::{ - RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, - RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, - RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestQuery, - ResponseApplySnapshotChunk, ResponseCheckTx, ResponseCommit, - ResponseEcho, ResponseFlush, ResponseInfo, ResponseInitChain, - ResponseListSnapshots, ResponseLoadSnapshotChunk, - ResponseOfferSnapshot, ResponseQuery, + 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; @@ -68,8 +69,10 @@ pub mod shim { PrepareProposal(RequestPrepareProposal), #[allow(dead_code)] VerifyHeader(request::VerifyHeader), - #[allow(dead_code)] - ProcessProposal(request::ProcessProposal), + #[cfg(not(feature = "ABCI"))] + ProcessProposal(RequestProcessProposal), + #[cfg(feature = "ABCI")] + DeliverTx(RequestDeliverTx), #[allow(dead_code)] #[cfg(not(feature = "ABCI"))] RevertProposal(request::RevertProposal), @@ -119,6 +122,14 @@ pub mod shim { Req::PrepareProposal(inner) => { Ok(Request::PrepareProposal(inner)) } + #[cfg(not(feature = "ABCI"))] + Req::ProcessProposal(inner) => { + Ok(Request::ProcessProposal(inner)) + } + #[cfg(feature = "ABCI")] + Req::DeliverTx(inner) => Ok(Request::DeliverTx(inner)), + #[cfg(not(feature = "ABCI"))] + Req::FinalizeBlock(inner) => Ok(Request::FinalizeBlock(inner)), _ => Err(Error::ConvertReq(req)), } } @@ -135,7 +146,10 @@ pub mod shim { #[cfg(not(feature = "ABCI"))] PrepareProposal(ResponsePrepareProposal), VerifyHeader(response::VerifyHeader), - ProcessProposal(response::ProcessProposal), + #[cfg(not(feature = "ABCI"))] + ProcessProposal(ResponseProcessProposal), + #[cfg(feature = "ABCI")] + DeliverTx(request::ProcessedTx), #[cfg(not(feature = "ABCI"))] RevertProposal(response::RevertProposal), #[cfg(not(feature = "ABCI"))] @@ -143,6 +157,8 @@ pub mod shim { #[cfg(not(feature = "ABCI"))] VerifyVoteExtension(ResponseVerifyVoteExtension), FinalizeBlock(response::FinalizeBlock), + #[cfg(feature = "ABCI")] + EndBlock(ResponseEndBlock), Commit(ResponseCommit), Flush(ResponseFlush), Echo(ResponseEcho), @@ -188,6 +204,14 @@ pub mod shim { Response::VerifyVoteExtension(inner) => { Ok(Resp::VerifyVoteExtension(inner)) } + #[cfg(not(feature = "ABCI"))] + Response::ProcessProposal(inner) => { + Ok(Resp::ProcessProposal(inner)) + } + #[cfg(not(feature = "ABCI"))] + Response::FinalizeBlock(inner) => { + Ok(Resp::FinalizeBlock(inner)) + } _ => Err(Error::ConvertResp(resp)), } } @@ -195,17 +219,17 @@ pub mod shim { /// Custom types for request payloads pub mod request { - use std::convert::{TryFrom, TryInto}; + use std::convert::TryFrom; - use anoma::types::storage::BlockHash; + use anoma::types::hash::Hash; + use anoma::types::storage::{BlockHash, Header}; + use anoma::types::time::DateTimeUtc; #[cfg(not(feature = "ABCI"))] - use tendermint::block::Header; + use tendermint_proto::abci::RequestFinalizeBlock; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{Evidence, RequestBeginBlock}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::{Evidence, RequestBeginBlock}; - #[cfg(feature = "ABCI")] - use tendermint_stable::block::Header; pub struct VerifyHeader; @@ -230,71 +254,80 @@ pub mod shim { pub result: super::response::TxResult, } - #[derive(Debug)] - pub struct BeginBlock { + pub struct FinalizeBlock { pub hash: BlockHash, pub header: Header, pub byzantine_validators: Vec, + pub txs: Vec, + #[cfg(feature = "ABCI")] + pub reject_all_decrypted: bool, } - impl TryFrom for BeginBlock { - type Error = super::Error; - - fn try_from(req: RequestBeginBlock) -> Result { - match ( - BlockHash::try_from(&*req.hash), - req.header - .clone() - .expect("Missing block's header") - .try_into(), - ) { - (Ok(hash), Ok(header)) => Ok(BeginBlock { - hash, - header, - byzantine_validators: req.byzantine_validators, - }), - (Ok(_), Err(msg)) => { - tracing::error!("Unexpected block header {}", msg); - Err(super::Error::ConvertReq(super::Req::BeginBlock( - req, - ))) - } - (err @ Err(_), _) => { - tracing::error!("{:#?}", err); - Err(super::Error::ConvertReq(super::Req::BeginBlock( - req, - ))) - } + #[cfg(not(feature = "ABCI"))] + impl From for FinalizeBlock { + fn from(req: RequestFinalizeBlock) -> FinalizeBlock { + FinalizeBlock { + hash: BlockHash::try_from(req.hash.as_slice()).unwrap(), + header: Header { + hash: Hash::try_from(req.hash.as_slice()).unwrap(), + time: DateTimeUtc::try_from(req.time).unwrap(), + next_validators_hash: Hash::try_from( + req.next_validators_hash.as_slice(), + ) + .unwrap(), + }, + byzantine_validators: req.byzantine_validators, + txs: vec![], } } } - pub struct FinalizeBlock { - pub hash: BlockHash, - pub header: Header, - pub byzantine_validators: Vec, - pub txs: Vec, - pub reject_all_decrypted: bool, + #[cfg(feature = "ABCI")] + impl From for FinalizeBlock { + fn from(req: RequestBeginBlock) -> FinalizeBlock { + let header = req.header.unwrap(); + FinalizeBlock { + hash: BlockHash::try_from(req.hash.as_slice()).unwrap(), + header: Header { + hash: Hash::try_from(header.app_hash.as_slice()) + .unwrap(), + 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![], + reject_all_decrypted: false, + } + } } } /// Custom types for response payloads pub mod response { #[cfg(not(feature = "ABCI"))] - use tendermint_proto::abci::{Event, ValidatorUpdate}; + use std::convert::TryFrom; + + #[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, ValidatorUpdate}; + use tendermint_proto_abci::abci::{Event as TmEvent, ValidatorUpdate}; #[cfg(not(feature = "ABCI"))] use tower_abci::response; #[cfg(feature = "ABCI")] use tower_abci_old::response; - #[cfg(feature = "ABCI")] - use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + use crate::node::ledger::events::Event; #[derive(Debug, Default)] pub struct VerifyHeader; @@ -317,26 +350,23 @@ pub mod shim { } } - #[derive(Debug, Default)] - pub struct ProcessProposal { - pub result: TxResult, - #[cfg(feature = "ABCI")] - pub tx: TxBytes, - } - #[cfg(not(feature = "ABCI"))] - impl From for ProcessProposal { - fn from(result: TxResult) -> Self { - ProcessProposal { result } + impl From for ExecTxResult { + fn from(TxResult { code, info }: TxResult) -> Self { + ExecTxResult { + code: code.into(), + info, + ..Default::default() + } } } - #[cfg(feature = "ABCI")] - impl From for ProcessProposal { - fn from(result: TxResult) -> Self { - ProcessProposal { - result, - ..Default::default() + #[cfg(not(feature = "ABCI"))] + impl From<&ExecTxResult> for TxResult { + fn from(ExecTxResult { code, info, .. }: &ExecTxResult) -> Self { + TxResult { + code: u32::try_from(code).unwrap(), + info: info.clone(), } } } @@ -347,15 +377,62 @@ pub mod shim { #[derive(Debug, Default)] pub struct FinalizeBlock { pub events: Vec, - pub gas_used: u64, pub validator_updates: Vec, pub consensus_param_updates: Option, } + #[cfg(not(feature = "ABCI"))] + impl From for ResponseFinalizeBlock { + fn from(resp: FinalizeBlock) -> Self { + ResponseFinalizeBlock { + tx_results: resp + .events + .iter() + .map(|event| ExecTxResult { + code: event + .get("code") + .map(|code| { + u32::from_str_radix(code, 10).unwrap() + }) + .unwrap_or_default(), + log: event + .get("log") + .map(|log| log.to_owned()) + .unwrap_or_default(), + info: event + .get("info") + .map(|info| info.to_owned()) + .unwrap_or_default(), + gas_used: event + .get("gas_used") + .map(|gas| { + i64::from_str_radix(gas, 10).unwrap() + }) + .unwrap_or_default(), + ..Default::default() + }) + .collect(), + events: resp + .events + .into_iter() + .map(TmEvent::from) + .collect(), + consensus_param_updates: resp.consensus_param_updates, + validator_updates: resp.validator_updates, + ..Default::default() + } + } + } + + #[cfg(feature = "ABCI")] impl From for response::EndBlock { fn from(resp: FinalizeBlock) -> Self { Self { - events: resp.events, + 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/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 7e0e1ec9a6..c09c8a38b0 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -37,21 +37,16 @@ use anoma::ledger::storage::{ MerkleTreeStoresRead, Result, StoreType, DB, }; use anoma::types::storage::{ - BlockHeight, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, + BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; use anoma::types::time::DateTimeUtc; +use borsh::{BorshDeserialize, BorshSerialize}; use rocksdb::{ BlockBasedOptions, Direction, FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, WriteBatch, WriteOptions, }; #[cfg(not(feature = "ABCI"))] -use tendermint::block::Header; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::Protobuf; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::Protobuf; -#[cfg(feature = "ABCI")] -use tendermint_stable::block::Header; use crate::config::utils::num_of_threads; @@ -503,7 +498,7 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; batch.put( key.to_string(), - h.encode_vec().expect("serialization failed"), + h.try_to_vec().expect("serialization failed"), ); } } @@ -557,7 +552,8 @@ impl DB for RocksDB { .map_err(|e| Error::DBError(e.into_string()))?; match value { Some(v) => Ok(Some( - Header::decode_vec(&v).map_err(Error::ProtobufCodingError)?, + Header::try_from_slice(&v[..]) + .map_err(Error::BorshCodingError)?, )), None => Ok(None), } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index f6a6a2a59c..705f5f6d73 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -78,9 +78,9 @@ 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", branch = "yuji/v0.12.0_abcipp_v0.23.5", default-features = false, optional = true} +ibc = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abcipp-rebase-master", 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", branch = "yuji/v0.12.0_abcipp_v0.23.5", default-features = false, optional = true} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abcipp-rebase-master", 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} ics23 = "0.6.7" itertools = "0.10.0" @@ -101,8 +101,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", branch = "yuji/abcipp-v0.23.5", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/abcipp-v0.23.5", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abcipp-rebase-master", 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} thiserror = "1.0.30" diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 837b181566..9c834296cd 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -1,4 +1,5 @@ //! IBC validity predicate for client module +use std::convert::TryInto; use std::str::FromStr; use thiserror::Error; @@ -27,6 +28,7 @@ use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; use crate::ibc::core::ics02_client::msgs::ClientMsg; 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::storage::{self, StorageHasher}; @@ -571,7 +573,16 @@ where .add(gas) .map_err(|_| Ics02Error::implementation_specific())?; match header { - Some(h) => Ok(TmConsensusState::from(h).wrap_any()), + Some(h) => Ok(TmConsensusState { + root: CommitmentRoot::from_bytes(h.hash.as_slice()), + timestamp: h.time.try_into().unwrap(), + next_validators_hash: h + .next_validators_hash + .to_vec() + .try_into() + .unwrap(), + } + .wrap_any()), None => Err(Ics02Error::missing_raw_header()), } } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 30e3c52aba..de4c6a367a 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -334,11 +334,6 @@ mod tests { use crate::ibc_proto::cosmos::base::v1beta1::Coin; use prost::Message; use sha2::Digest; - use crate::tendermint::account::Id as TmAccountId; - use crate::tendermint::block::header::{Header as TmHeader, Version as TmVersion}; - use crate::tendermint::block::Height as TmHeight; - use crate::tendermint::chain::Id as TmChainId; - use crate::tendermint::hash::{AppHash, Hash as TmHash}; use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf; @@ -431,26 +426,11 @@ mod tests { (storage, write_log) } - fn get_dummy_header() -> TmHeader { - TmHeader { - version: TmVersion { block: 10, app: 0 }, - chain_id: TmChainId::try_from("test_chain".to_owned()) - .expect("Creating an TmChainId shouldn't fail"), - height: TmHeight::try_from(10_u64) - .expect("Creating a height shouldn't fail"), - time: TmTime::now(), - last_block_id: None, - last_commit_hash: None, - data_hash: None, - validators_hash: TmHash::None, - next_validators_hash: TmHash::None, - consensus_hash: TmHash::None, - app_hash: AppHash::try_from(vec![0]) - .expect("Creating an AppHash shouldn't fail"), - last_results_hash: None, - evidence_hash: None, - proposer_address: TmAccountId::try_from(vec![0u8; 20]) - .expect("Creating an AccountId shouldn't fail"), + fn get_dummy_header() -> crate::types::storage::Header { + crate::types::storage::Header { + hash: crate::types::hash::Hash([0; 32]), + time: TmTime::now().try_into().unwrap(), + next_validators_hash: crate::types::hash::Hash([0; 32]), } } diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 2fbdf1d856..3cc7e8863c 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -6,16 +6,18 @@ use std::ops::Bound::{Excluded, Included}; use std::path::Path; use std::str::FromStr; +use borsh::{BorshDeserialize, BorshSerialize}; + use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; -use crate::tendermint::block::Header; -use crate::tendermint_proto::Protobuf; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; -use crate::types::storage::{BlockHeight, Key, KeySeg, KEY_SEGMENT_SEPARATOR}; +use crate::types::storage::{ + BlockHeight, Header, Key, KeySeg, KEY_SEGMENT_SEPARATOR, +}; use crate::types::time::DateTimeUtc; /// An in-memory DB for testing. @@ -228,7 +230,7 @@ impl DB for MockDB { .map_err(Error::KeyError)?; self.0.borrow_mut().insert( key.to_string(), - h.encode_vec().expect("serialization failed"), + h.try_to_vec().expect("serialization failed"), ); } } @@ -283,7 +285,8 @@ impl DB for MockDB { let value = self.0.borrow().get(&key.to_string()).cloned(); match value { Some(v) => Ok(Some( - Header::decode_vec(&v).map_err(Error::ProtobufCodingError)?, + BorshDeserialize::try_from_slice(&v[..]) + .map_err(Error::BorshCodingError)?, )), None => Ok(None), } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index c6cb58059d..f6464733cf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -8,21 +8,11 @@ pub mod write_log; use core::fmt::Debug; -#[cfg(not(feature = "ABCI"))] -use tendermint::block::Header; #[cfg(not(feature = "ABCI"))] use tendermint::merkle::proof::Proof; #[cfg(not(feature = "ABCI"))] -use tendermint_proto::Error as TmProtoError; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::Protobuf; #[cfg(feature = "ABCI")] -use tendermint_proto_abci::Error as TmProtoError; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::Protobuf; -#[cfg(feature = "ABCI")] -use tendermint_stable::block::Header; -#[cfg(feature = "ABCI")] use tendermint_stable::merkle::proof::Proof; use thiserror::Error; @@ -41,7 +31,8 @@ use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ - BlockHash, BlockHeight, Epoch, Epochs, Key, KeySeg, BLOCK_HASH_LENGTH, + BlockHash, BlockHeight, Epoch, Epochs, Header, Key, KeySeg, + BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; @@ -108,8 +99,8 @@ pub enum Error { MerkleTreeError(MerkleTreeError), #[error("DB error: {0}")] DBError(String), - #[error("Tendermint Protobuf error: {0}")] - ProtobufCodingError(TmProtoError), + #[error("Borsh (de)-serialization error: {0}")] + BorshCodingError(std::io::Error), #[error("Merkle tree at the height {height} is not stored")] NoMerkleTree { height: BlockHeight }, } diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 85951d33f2..f41a8707ad 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -1,6 +1,7 @@ //! Types for working with 32 bytes hashes. use std::fmt::{self, Display}; +use std::ops::Deref; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; @@ -49,6 +50,14 @@ impl Display for Hash { } } +impl Deref for Hash { + type Target = [u8; 32]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl TryFrom<&[u8]> for Hash { type Error = self::Error; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 4a93926635..d13dc6423e 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -13,6 +13,8 @@ use thiserror::Error; use super::transaction::WrapperTx; use crate::bytes::ByteBuf; use crate::types::address::{self, Address, InternalAddress}; +use crate::types::hash::Hash; +use crate::types::time::DateTimeUtc; use crate::types::token::BALANCE_STORAGE_KEY; #[allow(missing_docs)] @@ -157,6 +159,25 @@ impl core::fmt::Debug for BlockHash { } } +/// The data from Tendermint header +/// relevant for Anoma storage +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct Header { + /// Merkle root hash of block + pub hash: Hash, + /// Timestamp associated to block + pub time: DateTimeUtc, + /// Hash of the addresses of the next validator set + pub next_validators_hash: Hash, +} + +impl Header { + /// The number of bytes when this header is encoded + pub fn encoded_len(&self) -> usize { + self.try_to_vec().map(|ser| ser.len()).unwrap_or(usize::MAX) + } +} + /// A storage key is made of storage key segments [`DbKeySeg`], separated by /// [`KEY_SEGMENT_SEPARATOR`]. #[derive( diff --git a/shared/src/types/time.rs b/shared/src/types/time.rs index 0840565d82..ca4b8b26db 100644 --- a/shared/src/types/time.rs +++ b/shared/src/types/time.rs @@ -5,6 +5,10 @@ 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; @@ -98,6 +102,18 @@ impl DateTimeUtc { pub fn now() -> Self { Self(Utc::now()) } + + /// Returns an rfc3339 string or an error. + pub fn to_rfc3339(&self) -> Result { + Time::try_from(*self) + .map(|t| t.to_rfc3339()) + .map_err(|err| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("Could not parse timestamp because: {}", err), + ) + }) + } } impl Add for DateTimeUtc { @@ -185,6 +201,17 @@ impl From for prost_types::Timestamp { } } +impl TryFrom for DateTimeUtc { + type Error = prost_types::TimestampOutOfSystemRangeError; + + fn try_from(timestamp: protobuf::Timestamp) -> Result { + Self::try_from(prost_types::Timestamp { + seconds: timestamp.seconds, + nanos: timestamp.nanos, + }) + } +} + impl From for std::time::SystemTime { fn from(dt: DateTimeUtc) -> Self { dt.0.into() diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 2603f7210c..4489cbe784 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1555,11 +1555,10 @@ where .map_err(TxRuntimeError::StorageError)?; Ok(match header { Some(h) => { - let time = h - .time - .to_rfc3339() - .try_to_vec() - .map_err(TxRuntimeError::EncodingError)?; + let time = + h.time.to_rfc3339().map_err(TxRuntimeError::EncodingError)?; + let time = + time.try_to_vec().map_err(TxRuntimeError::EncodingError)?; let len: i64 = time .len() .try_into() diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index f64894f18d..38f56361a5 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -1,6 +1,5 @@ use core::time::Duration; use std::collections::{BTreeSet, HashMap}; -use std::convert::TryFrom; use std::str::FromStr; use anoma::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; @@ -60,13 +59,6 @@ use anoma::ledger::native_vp::{Ctx, NativeVp}; use anoma::ledger::storage::mockdb::MockDB; use anoma::ledger::storage::Sha256Hasher; use anoma::proto::Tx; -use anoma::tendermint::account::Id as TmAccountId; -use anoma::tendermint::block::header::{ - Header as TmHeader, Version as TmVersion, -}; -use anoma::tendermint::block::Height as TmHeight; -use anoma::tendermint::chain::Id as TmChainId; -use anoma::tendermint::hash::{AppHash, Hash as TmHash}; use anoma::tendermint::time::Time as TmTime; use anoma::tendermint_proto::Protobuf; use anoma::types::address::{self, Address, InternalAddress}; @@ -268,26 +260,11 @@ pub fn init_storage() -> (Address, Address) { (token, account) } -pub fn tm_dummy_header() -> TmHeader { - TmHeader { - version: TmVersion { block: 10, app: 0 }, - chain_id: TmChainId::try_from("test_chain".to_owned()) - .expect("Creating an TmChainId shouldn't fail"), - height: TmHeight::try_from(10_u64) - .expect("Creating a height shouldn't fail"), - time: TmTime::now(), - last_block_id: None, - last_commit_hash: None, - data_hash: None, - validators_hash: TmHash::None, - next_validators_hash: TmHash::None, - consensus_hash: TmHash::None, - app_hash: AppHash::try_from(vec![0]) - .expect("Creating an AppHash shouldn't fail"), - last_results_hash: None, - evidence_hash: None, - proposer_address: TmAccountId::try_from(vec![0u8; 20]) - .expect("Creating an AccountId shouldn't fail"), +pub fn tm_dummy_header() -> anoma::types::storage::Header { + anoma::types::storage::Header { + hash: anoma::types::hash::Hash([0; 32]), + time: TmTime::now().try_into().unwrap(), + next_validators_hash: anoma::types::hash::Hash([0; 32]), } } From c11ceab7b2f67c4c7c7dc9a09f0132f9d18472cf Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 3 May 2022 18:18:29 +0200 Subject: [PATCH 0002/1995] [feat]: Fixed shims and ABCI++ integration for the ABCI++ feature flag. Fixed tests, linting, and formatting --- .../lib/node/ledger/shell/finalize_block.rs | 233 +----------------- apps/src/lib/node/ledger/shell/mod.rs | 149 ++++++----- .../lib/node/ledger/shell/prepare_proposal.rs | 106 ++++++-- .../lib/node/ledger/shell/process_proposal.rs | 191 +++++++++----- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 19 +- .../node/ledger/shims/abcipp_shim_types.rs | 83 ++----- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 - shared/src/ledger/storage/mod.rs | 2 - 8 files changed, 334 insertions(+), 451 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 329515306f..cae5ad4015 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -2,7 +2,7 @@ use anoma::types::storage::{BlockHash, Header}; #[cfg(not(feature = "ABCI"))] -use tendermint_proto::abci::Evidence; +use tendermint_proto::abci::Misbehavior as Evidence; #[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::PublicKey as TendermintPublicKey; #[cfg(feature = "ABCI")] @@ -59,9 +59,7 @@ where }; let tx_length = processed_tx.tx.len(); // If [`process_proposal`] rejected a Tx due to invalid signature, - // emit an event here and move on to next tx. If we are - // rejecting all decrypted txs because they were - // submitted in an incorrect order, we do that later. + // emit an event here and move on to next tx. if ErrorCodes::from_u32(processed_tx.result.code).unwrap() == ErrorCodes::InvalidSig { @@ -105,11 +103,8 @@ where }; // If [`process_proposal`] rejected a Tx, emit an event here and // move on to next tx - // If we are rejecting all decrypted txs because they were submitted - // in an incorrect order, we do that later. if ErrorCodes::from_u32(processed_tx.result.code).unwrap() != ErrorCodes::Ok - && !req.reject_all_decrypted { let mut tx_event = Event::new_tx_event(&tx_type, height.0); tx_event["code"] = processed_tx.result.code.to_string(); @@ -133,22 +128,6 @@ where Event::new_tx_event(&tx_type, height.0) } TxType::Decrypted(inner) => { - // If [`process_proposal`] detected that decrypted txs were - // submitted out of order, we apply none - // of those. New encrypted txs may still - // be accepted. - if req.reject_all_decrypted { - let mut tx_event = - Event::new_tx_event(&tx_type, height.0); - tx_event["code"] = ErrorCodes::InvalidOrder.into(); - tx_event["info"] = "All decrypted txs rejected as \ - they were not submitted in \ - correct order" - .into(); - tx_event["gas_used"] = "0".into(); - response.events.push(tx_event); - continue; - } // We remove the corresponding wrapper tx from the queue if !cfg!(feature = "ABCI") { self.storage.tx_queue.pop(); @@ -404,21 +383,14 @@ mod test_finalize_block { for (index, event) in shell .finalize_block(FinalizeBlock { txs: processed_txs.clone(), - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") .iter() .enumerate() { - assert_eq!(event.r#type, "accepted"); - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); + assert_eq!(event.event_type.to_string(), String::from("accepted")); + let code = event.attributes.get("code").expect("Test failed"); assert_eq!(code, &index.rem_euclid(2).to_string()); } // verify that the queue of wrapper txs to be processed is correct @@ -478,7 +450,6 @@ mod test_finalize_block { for (index, event) in shell .finalize_block(FinalizeBlock { txs: processed_txs.clone(), - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") @@ -531,20 +502,13 @@ mod test_finalize_block { for event in shell .finalize_block(FinalizeBlock { txs: vec![processed_tx], - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); + assert_eq!(event.event_type.to_string(), String::from("applied")); + let code = event.attributes.get("code").expect("Test failed"); + assert_eq!(code, &String::from(ErrorCodes::InvalidTx)); } // check that the corresponding wrapper tx was removed from the queue assert!(shell.next_wrapper().is_none()); @@ -573,7 +537,6 @@ mod test_finalize_block { for event in shell .finalize_block(FinalizeBlock { txs: vec![processed_tx], - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") @@ -630,27 +593,14 @@ mod test_finalize_block { for event in shell .finalize_block(FinalizeBlock { txs: vec![processed_tx], - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") { - assert_eq!(event.r#type, "applied"); - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!(code, String::from(ErrorCodes::Undecryptable).as_str()); - let log = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "log") - .expect("Test failed") - .value - .as_str(); + assert_eq!(event.event_type.to_string(), String::from("applied")); + let code = event.attributes.get("code").expect("Test failed"); + assert_eq!(code, &String::from(ErrorCodes::Undecryptable)); + let log = event.attributes.get("log").expect("Test failed"); assert!(log.contains("Transaction could not be decrypted.")) } // check that the corresponding wrapper tx was removed from the queue @@ -698,7 +648,6 @@ mod test_finalize_block { for event in shell .finalize_block(FinalizeBlock { txs: vec![processed_tx], - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") @@ -797,7 +746,6 @@ mod test_finalize_block { for (index, event) in shell .finalize_block(FinalizeBlock { txs: processed_txs, - reject_all_decrypted: false, ..Default::default() }) .expect("Test failed") @@ -849,163 +797,4 @@ mod test_finalize_block { assert_eq!(counter, 2); } } - - #[cfg(not(feature = "ABCI"))] - /// Tests that if the decrypted txs are submitted out of - /// order then - /// 1. They are still enqueued in order - /// 2. New wrapper txs are enqueued in correct order - #[test] - fn test_decrypted_txs_out_of_order() { - let (mut shell, _) = setup(); - let keypair = gen_keypair(); - let mut processed_txs = vec![]; - let mut valid_txs = vec![]; - // create a wrapper tx to be included in block proposal - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(String::from("transaction data").as_bytes().to_owned()), - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: xan(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx, - Default::default(), - ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); - valid_txs.push(wrapper_tx); - processed_txs.push(ProcessedTx { - tx: wrapper.to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }); - // Create two decrypted txs to be part of block proposal. - // We give them an error code of two to indicate that order - // was not respected (although actually it was, but the job - // of detecting this lies with process_proposal so at this stage - // we can just lie to finalize_block to get the desired behavior) - for i in 0..2 { - 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: 0.into(), - token: xan(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - ); - // add the corresponding wrapper tx to the queue - shell.enqueue_tx(wrapper.clone()); - valid_txs.push(wrapper); - processed_txs.push(ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(raw_tx))) - .to_bytes(), - result: TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "".into(), - }, - }) - } - // We tell [`finalize_block`] that the decrypted txs are out of - // order although in fact they are not. This should not affect - // the expected behavior - // We check that the correct events are created. - for (index, event) in shell - .finalize_block(FinalizeBlock { - txs: processed_txs.clone(), - reject_all_decrypted: true, - ..Default::default() - }) - .expect("Test failed") - .iter() - .enumerate() - { - if index == 0 { - // the wrapper tx should be accepted - assert_eq!(event.r#type, "accepted"); - #[cfg(not(feature = "ABCI"))] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); - } - #[cfg(feature = "ABCI")] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); - assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::Ok) - ); - } - } else { - // both decrypted txs should be rejected - assert_eq!(event.r#type, "applied"); - #[cfg(not(feature = "ABCI"))] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key.as_str() == "code") - .expect("Test failed") - .value - .as_str(); - assert_eq!( - code, - String::from(ErrorCodes::InvalidOrder).as_str() - ); - } - #[cfg(feature = "ABCI")] - { - let code = event - .attributes - .iter() - .find(|attr| attr.key == "code".as_bytes()) - .expect("Test failed") - .value - .clone(); - assert_eq!( - String::from_utf8(code).expect("Test failed"), - String::from(ErrorCodes::InvalidOrder) - ); - } - } - } - // the wrapper tx should appear at the end of the queue - valid_txs.rotate_left(1); - // check that the queue has 3 wrappers in correct order - let mut counter = 0; - let mut txs = valid_txs.iter(); - while let Some(wrapper) = shell.next_wrapper() { - assert_eq!( - wrapper.tx_hash, - txs.next().expect("Test failed").tx_hash - ); - counter += 1; - } - assert_eq!(counter, 3); - } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 5b56291660..c6d6659d6a 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -48,7 +48,8 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ - self, Evidence, RequestPrepareProposal, ValidatorUpdate, + Misbehavior as Evidence, MisbehaviorType as EvidenceType, + RequestPrepareProposal, ValidatorUpdate, }; #[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::public_key; @@ -57,7 +58,7 @@ use tendermint_proto::types::ConsensusParams; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::ConsensusParams; #[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::{self, Evidence, ValidatorUpdate}; +use tendermint_proto_abci::abci::{Evidence, EvidenceType, ValidatorUpdate}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::public_key; use thiserror::Error; @@ -104,6 +105,15 @@ pub enum Error { Broadcaster(tokio::sync::mpsc::error::TryRecvError), } +impl From for TxResult { + fn from(err: Error) -> Self { + TxResult { + code: 1, + info: err.to_string(), + } + } +} + /// The different error codes that the ledger may /// send back to a client indicating the status /// of their submitted tx @@ -392,31 +402,30 @@ where continue; } }; - let slash_type = - match abci::EvidenceType::from_i32(evidence.r#type) { - Some(r#type) => match r#type { - abci::EvidenceType::DuplicateVote => { - pos::types::SlashType::DuplicateVote - } - abci::EvidenceType::LightClientAttack => { - pos::types::SlashType::LightClientAttack - } - abci::EvidenceType::Unknown => { - tracing::error!( - "Unknown evidence: {:#?}", - evidence - ); - continue; - } - }, - None => { + let slash_type = match EvidenceType::from_i32(evidence.r#type) { + Some(r#type) => match r#type { + EvidenceType::DuplicateVote => { + pos::types::SlashType::DuplicateVote + } + EvidenceType::LightClientAttack => { + pos::types::SlashType::LightClientAttack + } + EvidenceType::Unknown => { tracing::error!( - "Unexpected evidence type {}", - evidence.r#type + "Unknown evidence: {:#?}", + evidence ); continue; } - }; + }, + None => { + tracing::error!( + "Unexpected evidence type {}", + evidence.r#type + ); + continue; + } + }; let validator_raw_hash = match evidence.validator { Some(validator) => { match String::from_utf8(validator.address) { @@ -614,6 +623,7 @@ where /// for the shell #[cfg(test)] mod test_utils { + use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use anoma::ledger::storage::mockdb::MockDB; @@ -626,13 +636,7 @@ mod test_utils { use anoma::types::transaction::Fee; use tempfile::tempdir; #[cfg(not(feature = "ABCI"))] - use tendermint::block::{header::Version, Header}; - #[cfg(not(feature = "ABCI"))] - use tendermint::{Hash, Time}; - #[cfg(not(feature = "ABCI"))] - use tendermint_proto::abci::{ - Event as TmEvent, RequestInitChain, ResponsePrepareProposal, - }; + use tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf::Timestamp; #[cfg(feature = "ABCI")] @@ -643,10 +647,17 @@ mod test_utils { use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ - FinalizeBlock, ProcessProposal, ProcessedTx, + FinalizeBlock, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; + #[derive(Error, Debug)] + pub enum TestError { + #[error("Proposal rejected with tx results: {0:?}")] + #[allow(dead_code)] + RejectProposal(Vec), + } + /// Gets the absolute path to root directory pub fn top_level_directory() -> PathBuf { let mut current_path = std::env::current_dir() @@ -677,6 +688,27 @@ mod test_utils { pub shell: Shell, } + impl Deref for TestShell { + type Target = Shell; + + fn deref(&self) -> &Self::Target { + &self.shell + } + } + + impl DerefMut for TestShell { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.shell + } + } + + #[derive(Clone)] + /// Helper for testing process proposal which has very different + /// input types depending on whether the ABCI++ feature is on or not. + pub struct ProcessProposal { + pub txs: Vec>, + } + impl TestShell { /// Returns a new shell paired with a broadcast receiver, which will /// receives any protocol txs sent by the shell. @@ -711,33 +743,45 @@ mod test_utils { .expect("Test shell failed to initialize"); } - /// Forward the prepare proposal request and return the response - #[cfg(not(feature = "ABCI"))] - pub fn prepare_proposal( - &mut self, - req: RequestPrepareProposal, - ) -> ResponsePrepareProposal { - self.shell.prepare_proposal(req) - } - /// Forward a ProcessProposal request and extract the relevant /// response data to return pub fn process_proposal( &mut self, req: ProcessProposal, - ) -> Vec { + ) -> std::result::Result, TestError> { #[cfg(not(feature = "ABCI"))] { - req.txs + let resp = + self.shell.process_proposal(RequestProcessProposal { + txs: req.txs.clone(), + ..Default::default() + }); + let results = resp + .tx_results .iter() - .map(|tx_bytes| self.process_single_tx(tx_bytes)) + .zip(req.txs.into_iter()) + .map(|(res, tx_bytes)| ProcessedTx { + result: res.into(), + tx: tx_bytes, + }) .collect(); + if resp.status > 0 { + Err(TestError::RejectProposal(results)) + } else { + Ok(results) + } } #[cfg(feature = "ABCI")] { - vec![self.shell.process_and_decode_proposal(RequestDeliverTx { - tx: req.tx, - })] + Ok(req + .txs + .into_iter() + .map(|tx_bytes| { + self.process_and_decode_proposal(RequestDeliverTx { + tx: tx_bytes, + }) + }) + .collect()) } } @@ -760,18 +804,6 @@ mod test_utils { self.shell.storage.tx_queue.push(wrapper); self.shell.reset_tx_queue_iter(); } - - #[cfg(not(feature = "ABCI"))] - /// Get the next wrapper tx to be decoded - pub fn next_wrapper(&mut self) -> Option<&WrapperTx> { - self.shell.next_wrapper() - } - - #[cfg(feature = "ABCI")] - /// Get the next wrapper tx to be decoded - pub fn next_wrapper(&mut self) -> Option { - self.shell.next_wrapper() - } } /// Start a new test shell and initialize it. Returns the shell paired with @@ -803,7 +835,6 @@ mod test_utils { }, byzantine_validators: vec![], txs: vec![], - reject_all_decrypted: false, } } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 897f078124..4a92a5cb16 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,6 +2,7 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { + use tendermint_proto::abci::TxRecord; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -35,16 +36,22 @@ mod prepare_block { // 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.block_data.len() / 2; - let mut txs: Vec = req - .block_data + let number_of_new_txs = 1 + req.txs.len() / 2; + let mut txs: Vec = req + .txs .into_iter() - .take(number_of_new_txs) - .filter(|tx_bytes| { - if let Ok(tx) = Tx::try_from(tx_bytes.as_slice()) { - matches!(process_tx(tx), Ok(TxType::Wrapper(_))) + .enumerate() + .map(|(ix, tx_bytes)| { + if let Ok(Ok(TxType::Wrapper(_))) = + Tx::try_from(tx_bytes.as_slice()).map(process_tx) + { + if ix < number_of_new_txs { + record::keep(tx_bytes) + } else { + record::remove(tx_bytes) + } } else { - false + record::remove(tx_bytes) } }) .collect(); @@ -61,6 +68,7 @@ mod prepare_block { }) .to_bytes() }) + .map(record::add) .collect(); txs.append(&mut decrypted_txs); @@ -68,7 +76,34 @@ mod prepare_block { } else { vec![] }; - response::PrepareProposal { block_data: txs } + + response::PrepareProposal { + tx_records: txs, + ..Default::default() + } + } + } + + /// Functions for creating the appropriate TxRecord given the + /// numeric code + pub(super) mod record { + use super::*; + + /// Keep this transaction in the proposal + pub fn keep(tx: TxBytes) -> TxRecord { + TxRecord { action: 1, tx } + } + + /// A transaction added to the proposal not provided by + /// Tendermint from the mempool + pub fn add(tx: TxBytes) -> TxRecord { + TxRecord { action: 2, tx } + } + + /// Remove this transaction from the set provided + /// by Tendermint from the mempool + pub fn remove(tx: TxBytes) -> TxRecord { + TxRecord { action: 3, tx } } } @@ -92,10 +127,14 @@ mod prepare_block { Some("transaction_data".as_bytes().to_owned()), ); let req = RequestPrepareProposal { - block_data: vec![tx.to_bytes()], - block_data_size: 0, + txs: vec![tx.to_bytes()], + max_tx_bytes: 0, + ..Default::default() }; - assert_eq!(shell.prepare_proposal(req).block_data.len(), 0); + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(tx.to_bytes())] + ); } /// Test that if an error is encountered while @@ -130,10 +169,14 @@ mod prepare_block { ) .to_bytes(); let req = RequestPrepareProposal { - block_data: vec![wrapper], - block_data_size: 0, + txs: vec![wrapper.clone()], + max_tx_bytes: 0, + ..Default::default() }; - assert_eq!(shell.prepare_proposal(req).block_data.len(), 0); + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper)] + ); } /// Test that the decrypted txs are included @@ -147,8 +190,9 @@ mod prepare_block { let mut expected_decrypted = vec![]; let mut req = RequestPrepareProposal { - block_data: vec![], - block_data_size: 0, + 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 @@ -177,7 +221,7 @@ mod prepare_block { let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); shell.enqueue_tx(wrapper_tx); expected_wrapper.push(wrapper.clone()); - req.block_data.push(wrapper.to_bytes()); + req.txs.push(wrapper.to_bytes()); } // we extract the inner data from the txs for testing // equality since otherwise changes in timestamps would @@ -190,14 +234,25 @@ mod prepare_block { let received: Vec> = shell .prepare_proposal(req) - .block_data + .tx_records .iter() - .map(|tx_bytes| { - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data - .expect("Test failed") - }) + .filter_map( + |TxRecord { + tx: tx_bytes, + action, + }| { + if *action == 2 || *action == 1 { + 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); @@ -205,5 +260,6 @@ mod prepare_block { } } +#[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 51f726811e..afe7d2eee4 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,6 +4,7 @@ use tendermint_proto::abci::{ ExecTxResult, RequestProcessProposal, ResponseProcessProposal, }; +#[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestDeliverTx; use super::*; @@ -67,7 +68,7 @@ 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). - fn process_single_tx(&mut self, tx_bytes: &[u8]) -> TxResult { + pub(crate) fn process_single_tx(&mut self, tx_bytes: &[u8]) -> TxResult { let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -285,8 +286,11 @@ mod test_process_proposal { use tendermint_proto_abci::google::protobuf::Timestamp; use super::*; - use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::ProcessProposal; + use crate::node::ledger::shell::test_utils::{ + gen_keypair, ProcessProposal, TestShell, + }; + #[cfg(not(feature = "ABCI"))] + use crate::node::ledger::shell::test_utils::TestError; /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. @@ -315,14 +319,19 @@ mod test_process_proposal { ) .to_bytes(); #[allow(clippy::redundant_clone)] - let request = ProcessProposal { tx: tx.clone() }; + let request = ProcessProposal { + txs: vec![tx.clone()], + }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, @@ -396,14 +405,17 @@ mod test_process_proposal { panic!("Test failed"); }; let request = ProcessProposal { - tx: new_tx.to_bytes(), + txs: vec![new_tx.to_bytes()], + }; + let response = if let [response] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + response.clone() + } else { + panic!("Test failed") }; - let response = - if let [response] = shell.process_proposal(request).as_slice() { - response.clone() - } else { - panic!("Test failed") - }; let expected_error = "Signature verification failed: Invalid signature"; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert!( @@ -443,14 +455,17 @@ mod test_process_proposal { .sign(&keypair) .expect("Test failed"); let request = ProcessProposal { - tx: wrapper.to_bytes(), + txs: vec![wrapper.to_bytes()], + }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, @@ -499,15 +514,18 @@ mod test_process_proposal { .expect("Test failed"); let request = ProcessProposal { - tx: wrapper.to_bytes(), + txs: vec![wrapper.to_bytes()], }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, @@ -550,16 +568,34 @@ mod test_process_proposal { txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(tx)))); } let req_1 = ProcessProposal { - tx: txs[0].to_bytes(), + txs: vec![txs[0].to_bytes()], + }; + let response_1 = if let [resp] = shell + .process_proposal(req_1) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") }; - let response_1 = shell.process_proposal(req_1); assert_eq!(response_1.result.code, u32::from(ErrorCodes::Ok)); let req_2 = ProcessProposal { - tx: txs[2].to_bytes(), + txs: vec![txs[2].to_bytes()], }; - let response_2 = shell.process_proposal(req_2); + let response_2 = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(req_2) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; assert_eq!(response_2.result.code, u32::from(ErrorCodes::InvalidOrder)); assert_eq!( response_2.result.info, @@ -598,9 +634,19 @@ mod test_process_proposal { let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); - let request = ProcessProposal { tx: tx.to_bytes() }; + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; - let response = shell.process_proposal(request); + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, @@ -654,13 +700,18 @@ mod test_process_proposal { wrapper.sign(&keypair).expect("Test failed") }; - let request = ProcessProposal { tx: tx.to_bytes() }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); #[cfg(feature = "ABCI")] { @@ -722,14 +773,17 @@ mod test_process_proposal { wrapper.sign(&keypair).expect("Test failed") }; let request = ProcessProposal { - tx: signed.to_bytes(), + txs: vec![signed.to_bytes()], + }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); #[cfg(feature = "ABCI")] { @@ -764,8 +818,20 @@ mod test_process_proposal { let tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(tx))); - let request = ProcessProposal { tx: tx.to_bytes() }; - let response = shell.process_proposal(request); + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::ExtraTxs)); assert_eq!( response.result.info, @@ -783,13 +849,18 @@ mod test_process_proposal { Some("transaction data".as_bytes().to_owned()), ); let tx = Tx::from(TxType::Raw(tx)); - let request = ProcessProposal { tx: tx.to_bytes() }; - let response = - if let [resp] = shell.process_proposal(request).as_slice() { - resp.clone() - } else { - panic!("Test failed") - }; + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( response.result.info, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 7fd75f3c7c..1070aecdd2 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -15,11 +15,11 @@ use tower_abci::{BoxError, Request as Req, Response as Resp}; 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; -use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ - FinalizeBlock, ProcessedTx, -}; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -60,6 +60,7 @@ impl AbcippShim { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), + #[cfg(feature = "ABCI")] begin_block_request: None, processed_txs: vec![], shell_recv, @@ -99,7 +100,7 @@ impl AbcippShim { Req::FinalizeBlock(block) => { let mut txs = vec![]; std::mem::swap(&mut txs, &mut self.processed_txs); - let mut finalize_req = block.into(); + let mut finalize_req: FinalizeBlock = block.into(); finalize_req.txs = txs; self.service .call(Request::FinalizeBlock(finalize_req)) @@ -136,18 +137,8 @@ impl AbcippShim { Req::EndBlock(_) => { let mut txs = vec![]; std::mem::swap(&mut txs, &mut self.processed_txs); - // If the wrapper txs were not properly submitted, reject - // all txs - let out_of_order = - txs.iter().any(|tx| tx.result.code > 3u32); - if out_of_order { - // The wrapper txs will need to be decrypted again - // and included in the proposed block after the current - self.service.reset_tx_queue_iter(); - } let mut end_block_request: FinalizeBlock = self.begin_block_request.take().unwrap().into(); - end_block_request.reject_all_decrypted = out_of_order; end_block_request.txs = txs; self.service .call(Request::FinalizeBlock(end_block_request)) 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 081446df0d..fd5e2908e9 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -9,15 +9,15 @@ pub mod shim { #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, - RequestExtendVote, RequestFinalizeBlock, RequestFlush, RequestInfo, - RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, - RequestOfferSnapshot, RequestPrepareProposal, RequestProcessProposal, - RequestQuery, RequestVerifyVoteExtension, ResponseApplySnapshotChunk, + RequestExtendVote, RequestFlush, RequestInfo, RequestInitChain, + RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, + RequestPrepareProposal, RequestProcessProposal, RequestQuery, + RequestVerifyVoteExtension, ResponseApplySnapshotChunk, ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseExtendVote, - ResponseFinalizeBlock, ResponseFlush, ResponseInfo, ResponseInitChain, - ResponseListSnapshots, ResponseLoadSnapshotChunk, - ResponseOfferSnapshot, ResponsePrepareProposal, - ResponseProcessProposal, ResponseQuery, ResponseVerifyVoteExtension, + ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, + ResponseLoadSnapshotChunk, ResponseOfferSnapshot, + ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, + ResponseVerifyVoteExtension, }; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::{ @@ -122,14 +122,6 @@ pub mod shim { Req::PrepareProposal(inner) => { Ok(Request::PrepareProposal(inner)) } - #[cfg(not(feature = "ABCI"))] - Req::ProcessProposal(inner) => { - Ok(Request::ProcessProposal(inner)) - } - #[cfg(feature = "ABCI")] - Req::DeliverTx(inner) => Ok(Request::DeliverTx(inner)), - #[cfg(not(feature = "ABCI"))] - Req::FinalizeBlock(inner) => Ok(Request::FinalizeBlock(inner)), _ => Err(Error::ConvertReq(req)), } } @@ -204,14 +196,6 @@ pub mod shim { Response::VerifyVoteExtension(inner) => { Ok(Resp::VerifyVoteExtension(inner)) } - #[cfg(not(feature = "ABCI"))] - Response::ProcessProposal(inner) => { - Ok(Resp::ProcessProposal(inner)) - } - #[cfg(not(feature = "ABCI"))] - Response::FinalizeBlock(inner) => { - Ok(Resp::FinalizeBlock(inner)) - } _ => Err(Error::ConvertResp(resp)), } } @@ -225,25 +209,14 @@ pub mod shim { use anoma::types::storage::{BlockHash, Header}; use anoma::types::time::DateTimeUtc; #[cfg(not(feature = "ABCI"))] - use tendermint_proto::abci::RequestFinalizeBlock; - #[cfg(not(feature = "ABCI"))] - use tendermint_proto::abci::{Evidence, RequestBeginBlock}; + use tendermint_proto::abci::{ + Misbehavior as Evidence, RequestFinalizeBlock, + }; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::{Evidence, RequestBeginBlock}; pub struct VerifyHeader; - #[derive(Clone)] - pub struct ProcessProposal { - pub tx: super::TxBytes, - } - - impl From for ProcessProposal { - fn from(tx: super::TxBytes) -> Self { - Self { tx } - } - } - #[cfg(not(feature = "ABCI"))] pub struct RevertProposal; @@ -259,8 +232,6 @@ pub mod shim { pub header: Header, pub byzantine_validators: Vec, pub txs: Vec, - #[cfg(feature = "ABCI")] - pub reject_all_decrypted: bool, } #[cfg(not(feature = "ABCI"))] @@ -270,7 +241,7 @@ pub mod shim { hash: BlockHash::try_from(req.hash.as_slice()).unwrap(), header: Header { hash: Hash::try_from(req.hash.as_slice()).unwrap(), - time: DateTimeUtc::try_from(req.time).unwrap(), + time: DateTimeUtc::try_from(req.time.unwrap()).unwrap(), next_validators_hash: Hash::try_from( req.next_validators_hash.as_slice(), ) @@ -300,7 +271,6 @@ pub mod shim { }, byzantine_validators: req.byzantine_validators, txs: vec![], - reject_all_decrypted: false, } } } @@ -308,9 +278,6 @@ pub mod shim { /// Custom types for response payloads pub mod response { - #[cfg(not(feature = "ABCI"))] - use std::convert::TryFrom; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ Event as TmEvent, ExecTxResult, ResponseFinalizeBlock, @@ -322,8 +289,6 @@ pub mod shim { use tendermint_proto_abci::abci::ConsensusParams; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::{Event as TmEvent, ValidatorUpdate}; - #[cfg(not(feature = "ABCI"))] - use tower_abci::response; #[cfg(feature = "ABCI")] use tower_abci_old::response; @@ -338,23 +303,11 @@ pub mod shim { pub info: String, } - impl From for TxResult - where - T: std::error::Error, - { - fn from(err: T) -> Self { - TxResult { - code: 1, - info: err.to_string(), - } - } - } - #[cfg(not(feature = "ABCI"))] impl From for ExecTxResult { fn from(TxResult { code, info }: TxResult) -> Self { ExecTxResult { - code: code.into(), + code, info, ..Default::default() } @@ -365,7 +318,7 @@ pub mod shim { impl From<&ExecTxResult> for TxResult { fn from(ExecTxResult { code, info, .. }: &ExecTxResult) -> Self { TxResult { - code: u32::try_from(code).unwrap(), + code: *code, info: info.clone(), } } @@ -391,9 +344,7 @@ pub mod shim { .map(|event| ExecTxResult { code: event .get("code") - .map(|code| { - u32::from_str_radix(code, 10).unwrap() - }) + .map(|code| code.parse::().unwrap()) .unwrap_or_default(), log: event .get("log") @@ -405,9 +356,7 @@ pub mod shim { .unwrap_or_default(), gas_used: event .get("gas_used") - .map(|gas| { - i64::from_str_radix(gas, 10).unwrap() - }) + .map(|gas| gas.parse::().unwrap()) .unwrap_or_default(), ..Default::default() }) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index c09c8a38b0..83c1655fa4 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -45,8 +45,6 @@ use rocksdb::{ BlockBasedOptions, Direction, FlushOptions, IteratorMode, Options, ReadOptions, SliceTransform, WriteBatch, WriteOptions, }; -#[cfg(not(feature = "ABCI"))] -use tendermint_proto::Protobuf; use crate::config::utils::num_of_threads; diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index f6464733cf..a51215bb06 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -10,8 +10,6 @@ use core::fmt::Debug; #[cfg(not(feature = "ABCI"))] use tendermint::merkle::proof::Proof; -#[cfg(not(feature = "ABCI"))] -use tendermint_proto::Protobuf; #[cfg(feature = "ABCI")] use tendermint_stable::merkle::proof::Proof; use thiserror::Error; From 76b2cbf7aa19388e13be05d2bc81c1eb8c59b276 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 6 May 2022 14:06:11 +0200 Subject: [PATCH 0003/1995] [feat]: Some small changes to get tendermint working correctly with the ABCI++ feature turned on --- Cargo.lock | 1410 +++++++++-------- apps/src/lib/node/ledger/mod.rs | 3 + apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/prepare_proposal.rs | 22 +- .../lib/node/ledger/shell/process_proposal.rs | 10 +- apps/src/lib/node/ledger/tendermint_node.rs | 15 +- 6 files changed, 752 insertions(+), 716 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f20ec9cc1..7b241ff963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", "once_cell", "version_check 0.9.4", ] @@ -105,10 +105,10 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "pwasm-utils", - "rand 0.8.4", + "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "serde 1.0.136", + "serde 1.0.137", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -120,8 +120,8 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.31", - "tracing-subscriber 0.3.9", + "tracing 0.1.34", + "tracing-subscriber 0.3.11", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -159,7 +159,7 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.19", + "futures 0.3.21", "git2", "hex", "itertools 0.10.3", @@ -169,7 +169,7 @@ dependencies = [ "libp2p", "message-io", "num-derive", - "num-traits 0.2.14", + "num-traits 0.2.15", "num_cpus", "once_cell", "orion", @@ -177,7 +177,7 @@ dependencies = [ "proptest 1.0.0 (git+https://github.com/heliaxdev/proptest?branch=tomas/sm)", "prost 0.9.0", "prost-types 0.9.0", - "rand 0.8.4", + "rand 0.8.5", "rand_core 0.6.3", "rayon", "regex", @@ -185,7 +185,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_regex", @@ -213,9 +213,9 @@ dependencies = [ "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", - "tracing 0.1.31", + "tracing 0.1.34", "tracing-log", - "tracing-subscriber 0.3.9", + "tracing-subscriber 0.3.11", "websocket", "winapi 0.3.9", ] @@ -273,8 +273,8 @@ dependencies = [ "sha2 0.9.9", "tempfile", "test-log", - "tracing 0.1.31", - "tracing-subscriber 0.3.9", + "tracing 0.1.34", + "tracing-subscriber 0.3.11", ] [[package]] @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.53" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "ark-bls12-381" @@ -339,7 +339,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-traits 0.2.14", + "num-traits 0.2.15", "zeroize", ] @@ -367,7 +367,7 @@ dependencies = [ "ark-std", "derivative", "num-bigint", - "num-traits 0.2.14", + "num-traits 0.2.15", "paste", "rustc_version 0.3.3", "zeroize", @@ -390,7 +390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ "num-bigint", - "num-traits 0.2.14", + "num-traits 0.2.15", "quote", "syn", ] @@ -436,8 +436,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "num-traits 0.2.14", - "rand 0.8.4", + "num-traits 0.2.15", + "rand 0.8.5", ] [[package]] @@ -511,9 +511,9 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" dependencies = [ "async-channel", "async-executor", @@ -534,7 +534,7 @@ dependencies = [ "concurrent-queue", "futures-lite", "libc", - "log 0.4.14", + "log 0.4.17", "once_cell", "parking", "polling", @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "async-process" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", "blocking", @@ -581,27 +581,27 @@ dependencies = [ [[package]] name = "async-std" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.6", + "crossbeam-utils 0.8.8", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", - "log 0.4.14", + "log 0.4.17", "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "pin-utils", "slab", "wasm-bindgen-futures", @@ -609,9 +609,9 @@ dependencies = [ [[package]] name = "async-std-resolver" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed4e2c3da14d8ad45acb1e3191db7a918e9505b6f155b218e70a7c9a1a48c638" +checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" dependencies = [ "async-std", "async-trait", @@ -623,9 +623,9 @@ dependencies = [ [[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", @@ -633,9 +633,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", @@ -644,15 +644,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" dependencies = [ "proc-macro2", "quote", @@ -667,8 +667,8 @@ checksum = "e00550829ef8e2c4115250d0ee43305649b0fa95f78a32ce5b07da0b73d95c5c" dependencies = [ "futures-io", "futures-util", - "log 0.4.14", - "pin-project-lite 0.2.8", + "log 0.4.17", + "pin-project-lite 0.2.9", "tokio", "tokio-rustls", "tungstenite 0.12.0", @@ -685,7 +685,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", ] [[package]] @@ -694,7 +694,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -716,28 +716,31 @@ dependencies = [ [[package]] name = "autocfg" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object", "rustc-demangle", ] @@ -784,7 +787,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.136", + "serde 1.0.137", ] [[package]] @@ -906,9 +909,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 0.14.5", ] @@ -930,9 +933,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ "async-channel", "async-task", @@ -1014,18 +1017,18 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byte-unit" -version = "4.0.13" +version = "4.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f" +checksum = "95ebf10dda65f19ff0f42ea15572a359ed60d7fc74fdc984d90310937be0014b" dependencies = [ "utf8-width", ] [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -1033,9 +1036,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" dependencies = [ "proc-macro2", "quote", @@ -1083,7 +1086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" dependencies = [ "clap 2.34.0", - "log 0.4.14", + "log 0.4.17", "shell-escape", "stderrlog", "watchexec", @@ -1104,7 +1107,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.0", + "nom 7.1.1", ] [[package]] @@ -1163,7 +1166,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits 0.2.14", + "num-traits 0.2.15", "time 0.1.44", "winapi 0.3.9", ] @@ -1179,9 +1182,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90" +checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" dependencies = [ "glob", "libc", @@ -1258,7 +1261,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.26", "tracing-error", ] @@ -1290,7 +1293,7 @@ dependencies = [ "lazy_static 1.4.0", "nom 5.1.2", "rust-ini", - "serde 1.0.136", + "serde 1.0.137", "serde-hjson", "serde_json", "toml", @@ -1305,9 +1308,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -1321,9 +1324,9 @@ 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", ] @@ -1348,7 +1351,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "gimli 0.25.0", - "log 0.4.14", + "log 0.4.17", "regalloc", "smallvec 1.8.0", "target-lexicon", @@ -1383,37 +1386,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "target-lexicon", ] [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crossbeam-channel" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" -dependencies = [ - "crossbeam-utils 0.6.6", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.6", + "crossbeam-utils 0.8.8", ] [[package]] @@ -1424,48 +1418,39 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.6", + "crossbeam-utils 0.8.8", ] [[package]] name = "crossbeam-epoch" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.6", + "crossbeam-utils 0.8.8", "lazy_static 1.4.0", "memoffset", "scopeguard", ] -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if 0.1.10", - "lazy_static 1.4.0", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "cfg-if 0.1.10", "lazy_static 1.4.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static 1.4.0", @@ -1524,9 +1509,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote", "syn", @@ -1560,9 +1545,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", @@ -1596,12 +1581,12 @@ 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 0.13.1", - "darling_macro 0.13.1", + "darling_core 0.13.4", + "darling_macro 0.13.4", ] [[package]] @@ -1620,15 +1605,14 @@ 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 0.10.0", "syn", ] @@ -1645,11 +1629,11 @@ dependencies = [ [[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 0.13.1", + "darling_core 0.13.4", "quote", "syn", ] @@ -1749,7 +1733,7 @@ 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 2.4.1", ] @@ -1765,9 +1749,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1798,9 +1782,9 @@ checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[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", @@ -1813,9 +1797,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", @@ -1824,24 +1808,24 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" +checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" dependencies = [ - "serde 1.0.136", + "serde 1.0.137", "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", "rand_core 0.6.3", - "serde 1.0.136", + "serde 1.0.137", "sha2 0.9.9", "thiserror", "zeroize", @@ -1857,7 +1841,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1871,31 +1855,33 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "embed-resource" -version = "1.6.5" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85505eb239fc952b300f29f0556d2d884082a83566768d980278d8faf38c780d" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" dependencies = [ "cc", + "rustc_version 0.4.0", + "toml", "vswhom", "winreg 0.10.1", ] [[package]] name = "encoding_rs" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ - "heck", + "heck 0.4.0", "proc-macro2", "quote", "syn", @@ -1923,20 +1909,20 @@ 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 0.13.1", + "darling 0.13.4", "proc-macro2", "quote", "syn", @@ -1948,7 +1934,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -1967,9 +1953,9 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ - "log 0.4.14", + "log 0.4.17", "once_cell", - "serde 1.0.136", + "serde 1.0.137", "serde_json", ] @@ -1981,9 +1967,9 @@ checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" [[package]] name = "eyre" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc225d8f637923fe585089fcf03e705c222131232d2c1fb622e84ecf725d0eb8" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -2038,8 +2024,8 @@ dependencies = [ "miracl_core", "num", "rand 0.7.3", - "rand 0.8.4", - "serde 1.0.136", + "rand 0.8.5", + "serde 1.0.137", "serde_bytes", "serde_json", "subproductdomain", @@ -2056,15 +2042,15 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", ] [[package]] name = "file-lock" -version = "2.0.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda66a9e6fd49a3ccec414748714bbcb401297bcf0dff9e9adf4bd86351d24fa" +checksum = "b5f90befe02a5389806504fc9fa78681fe950b7e56940e3a5e1515a7b7b86b35" dependencies = [ "cc", "libc", @@ -2074,13 +2060,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "winapi 0.3.9", ] @@ -2098,9 +2084,9 @@ checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -2205,9 +2191,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -2220,9 +2206,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -2230,15 +2216,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -2248,9 +2234,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" @@ -2263,15 +2249,15 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -2291,15 +2277,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-timer" @@ -2309,9 +2295,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -2320,7 +2306,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -2357,9 +2343,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2404,7 +2390,7 @@ dependencies = [ "bitflags", "libc", "libgit2-sys", - "log 0.4.14", + "log 0.4.17", "openssl-probe", "openssl-sys", "url 2.2.2", @@ -2425,15 +2411,15 @@ dependencies = [ "aho-corasick", "bstr", "fnv", - "log 0.4.14", + "log 0.4.17", "regex", ] [[package]] name = "gloo-timers" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", @@ -2443,9 +2429,9 @@ dependencies = [ [[package]] name = "good_lp" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e4f4cb8fac5e7a7862d8647529e3cc0cd478817c28efc7031d144ac259a718" +checksum = "b51d78cbb7b734379eea7f811ddb33b2b13defefa1dab50068d7bc7f781a3056" dependencies = [ "fnv", "minilp", @@ -2468,7 +2454,7 @@ dependencies = [ "hex", "itertools 0.10.3", "miracl_core", - "rand 0.8.4", + "rand 0.8.5", "rand_core 0.6.3", "rayon", "subproductdomain", @@ -2477,18 +2463,18 @@ dependencies = [ [[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", @@ -2497,9 +2483,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 1.1.0", "fnv", @@ -2510,8 +2496,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", - "tracing 0.1.31", + "tokio-util 0.7.1", + "tracing 0.1.34", ] [[package]] @@ -2525,32 +2511,32 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" dependencies = [ "ahash", ] [[package]] name = "hdrhistogram" -version = "6.3.4" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d331ebcdbca4acbefe5da8c3299b2e246f198a8294cc5163354e743398b89d" +checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" dependencies = [ - "base64 0.10.1", + "base64 0.13.0", "byteorder", - "crossbeam-channel 0.3.9", + "crossbeam-channel", "flate2", - "nom 4.2.3", - "num-traits 0.2.14", + "nom 7.1.1", + "num-traits 0.2.15", ] [[package]] name = "headers" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84c647447a07ca16f5fbd05b633e535cc41a08d2d74ab1e08648df53be9cb89" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ "base64 0.13.0", "bitflags", @@ -2559,7 +2545,7 @@ dependencies = [ "http", "httpdate", "mime 0.3.16", - "sha-1 0.9.8", + "sha-1 0.10.0", ] [[package]] @@ -2580,6 +2566,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2635,13 +2627,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ "bytes 1.1.0", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] @@ -2652,14 +2644,14 @@ checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.5.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -2688,9 +2680,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" dependencies = [ "bytes 1.1.0", "futures-channel", @@ -2701,12 +2693,12 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", - "pin-project-lite 0.2.8", + "itoa", + "pin-project-lite 0.2.9", "socket2 0.4.2", "tokio", "tower-service", - "tracing 0.1.31", + "tracing 0.1.34", "want", ] @@ -2717,10 +2709,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "headers", "http", - "hyper 0.14.16", + "hyper 0.14.18", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2737,8 +2729,8 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.16", - "log 0.4.14", + "hyper 0.14.18", + "log 0.4.17", "rustls", "rustls-native-certs", "tokio", @@ -2753,8 +2745,8 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.16", - "pin-project-lite 0.2.8", + "hyper 0.14.18", + "pin-project-lite 0.2.9", "tokio", "tokio-io-timeout", ] @@ -2766,7 +2758,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.1.0", - "hyper 0.14.16", + "hyper 0.14.18", "native-tls", "tokio", "tokio-native-tls", @@ -2775,18 +2767,18 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#eeba012008ee86e7c35f36b2f5d323cc11d81470" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", "ics23", - "num-traits 0.2.14", + "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.136", + "serde 1.0.137", "serde_derive", "serde_json", "sha2 0.10.2", @@ -2795,8 +2787,8 @@ dependencies = [ "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.7", - "tracing 0.1.31", + "time 0.3.9", + "tracing 0.1.34", ] [[package]] @@ -2809,11 +2801,11 @@ dependencies = [ "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.14", + "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.136", + "serde 1.0.137", "serde_derive", "serde_json", "sha2 0.10.2", @@ -2822,19 +2814,19 @@ dependencies = [ "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.7", - "tracing 0.1.31", + "time 0.3.9", + "tracing 0.1.34", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#eeba012008ee86e7c35f36b2f5d323cc11d81470" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tonic", ] @@ -2847,7 +2839,7 @@ dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "tonic", ] @@ -2924,12 +2916,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" dependencies = [ "async-io", - "futures 0.3.19", + "futures 0.3.21", "futures-lite", "if-addrs", "ipnet", "libc", - "log 0.4.14", + "log 0.4.17", "winapi 0.3.9", ] @@ -2941,13 +2933,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "hashbrown 0.11.2", - "serde 1.0.136", + "serde 1.0.137", ] [[package]] @@ -2986,13 +2978,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] name = "integer-encoding" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90c11140ffea82edce8dcd74137ce9324ec24b3cf0175fc9d7e29164da9915b8" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "iovec" @@ -3017,9 +3012,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" @@ -3039,12 +3034,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" @@ -3062,9 +3051,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -3075,8 +3064,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" dependencies = [ - "log 0.4.14", - "serde 1.0.136", + "log 0.4.17", + "serde 1.0.137", "serde_json", ] @@ -3102,7 +3091,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -3185,7 +3174,7 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "atomic", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "lazy_static 1.4.0", "libp2p-core", "libp2p-deflate", @@ -3226,11 +3215,11 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "lazy_static 1.4.0", "libsecp256k1", - "log 0.4.14", + "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", @@ -3255,7 +3244,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "flate2", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", ] @@ -3265,9 +3254,9 @@ version = "0.28.1" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std-resolver", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "smallvec 1.8.0", "trust-dns-resolver", ] @@ -3279,10 +3268,10 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", @@ -3299,11 +3288,11 @@ dependencies = [ "byteorder", "bytes 1.1.0", "fnv", - "futures 0.3.19", + "futures 0.3.21", "hex_fmt", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", @@ -3319,10 +3308,10 @@ name = "libp2p-identify" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "smallvec 1.8.0", @@ -3339,10 +3328,10 @@ dependencies = [ "bytes 1.1.0", "either", "fnv", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", @@ -3362,13 +3351,13 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.19", + "futures 0.3.21", "if-watch", "lazy_static 1.4.0", "libp2p-core", "libp2p-swarm", - "log 0.4.14", - "rand 0.8.4", + "log 0.4.17", + "rand 0.8.5", "smallvec 1.8.0", "socket2 0.4.2", "void", @@ -3381,9 +3370,9 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "asynchronous-codec", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", @@ -3398,13 +3387,13 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "bytes 1.1.0", "curve25519-dalek", - "futures 0.3.19", + "futures 0.3.21", "lazy_static 1.4.0", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.9.9", "snow", "static_assertions", @@ -3417,10 +3406,10 @@ name = "libp2p-ping" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "void", "wasm-timer", @@ -3433,9 +3422,9 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "asynchronous-codec", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "unsigned-varint 0.7.1", @@ -3447,8 +3436,8 @@ name = "libp2p-pnet" version = "0.21.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.19", - "log 0.4.14", + "futures 0.3.21", + "log 0.4.17", "pin-project 1.0.10", "rand 0.7.3", "salsa20", @@ -3462,11 +3451,11 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "asynchronous-codec", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "pin-project 1.0.10", "prost 0.7.0", "prost-build 0.7.0", @@ -3484,10 +3473,10 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "async-trait", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.17", "lru", "minicbor", "rand 0.7.3", @@ -3502,9 +3491,9 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "smallvec 1.8.0", "void", @@ -3526,13 +3515,13 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-io", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "if-watch", "ipnet", "libc", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "socket2 0.4.2", ] @@ -3542,9 +3531,9 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -3552,7 +3541,7 @@ name = "libp2p-wasm-ext" version = "0.28.2" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3566,10 +3555,10 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.19", + "futures 0.3.21", "futures-rustls", "libp2p-core", - "log 0.4.14", + "log 0.4.17", "quicksink", "rw-stream-sink", "soketto", @@ -3582,7 +3571,7 @@ name = "libp2p-yamux" version = "0.32.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "parking_lot 0.11.2", "thiserror", @@ -3633,9 +3622,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" dependencies = [ "cc", "libc", @@ -3649,7 +3638,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" dependencies = [ - "serde 1.0.136", + "serde 1.0.137", "serde_test", ] @@ -3664,10 +3653,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg 1.1.0", "scopeguard", ] @@ -3677,14 +3667,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.14", + "log 0.4.17", ] [[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", "value-bag", @@ -3747,7 +3737,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.136", + "serde 1.0.137", "serde_derive", "serde_yaml", ] @@ -3790,24 +3780,25 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "measure_time" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f07966480d8562b3622f51df0b4e3fe6ea7ddb3b48b19b0f44ef863c455bdf9" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" dependencies = [ - "log 0.4.14", + "instant", + "log 0.4.17", ] [[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.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -3818,7 +3809,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -3835,17 +3826,17 @@ dependencies = [ [[package]] name = "message-io" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dacbc3557867a8bb23e5f7ee0702e0aaa6574cff7e96fb6bbc495feb5f8ea5" +checksum = "5d6612f460798dabbdbb3d4643d9700a32291cb9a5637f07ad25428030fc06db" dependencies = [ - "crossbeam-channel 0.5.2", - "crossbeam-utils 0.8.6", + "crossbeam-channel", + "crossbeam-utils 0.8.8", "integer-encoding", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "mio 0.7.14", - "serde 1.0.136", + "serde 1.0.137", "strum", "tungstenite 0.16.0", "url 2.2.2", @@ -3892,7 +3883,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a7750a9e5076c660b7bec5e6457b4dbff402b9863c8d112891434e18fd5385" dependencies = [ - "log 0.4.14", + "log 0.4.17", "sprs", ] @@ -3904,12 +3895,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ "adler", - "autocfg 1.0.1", ] [[package]] @@ -3924,7 +3914,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.14", + "log 0.4.17", "miow 0.2.2", "net2", "slab", @@ -3938,7 +3928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", - "log 0.4.14", + "log 0.4.17", "miow 0.3.7", "ntapi", "winapi 0.3.9", @@ -3951,7 +3941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.14", + "log 0.4.17", "mio 0.6.23", "slab", ] @@ -4011,7 +4001,7 @@ dependencies = [ "good_lp", "petgraph 0.5.1", "rust_decimal", - "serde 1.0.136", + "serde 1.0.137", "serde_json", "tokio", ] @@ -4041,7 +4031,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -4061,8 +4051,8 @@ version = "0.10.3" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", - "log 0.4.14", + "futures 0.3.21", + "log 0.4.17", "pin-project 1.0.10", "smallvec 1.8.0", "unsigned-varint 0.7.1", @@ -4070,13 +4060,13 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static 1.4.0", "libc", - "log 0.4.14", + "log 0.4.17", "openssl", "openssl-probe", "openssl-sys", @@ -4095,7 +4085,7 @@ dependencies = [ "matrixmultiply", "num-complex 0.2.4", "num-integer", - "num-traits 0.2.14", + "num-traits 0.2.15", "rawpointer", ] @@ -4154,16 +4144,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check 0.1.5", -] - [[package]] name = "nom" version = "5.1.2" @@ -4177,13 +4157,12 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check 0.9.4", ] [[package]] @@ -4206,9 +4185,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi 0.3.9", ] @@ -4220,11 +4199,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint", - "num-complex 0.4.0", + "num-complex 0.4.1", "num-integer", "num-iter", "num-rational", - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[package]] @@ -4233,9 +4212,9 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-integer", - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[package]] @@ -4244,17 +4223,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" dependencies = [ - "autocfg 1.0.1", - "num-traits 0.2.14", + "autocfg 1.1.0", + "num-traits 0.2.15", ] [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" dependencies = [ - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[package]] @@ -4270,23 +4249,23 @@ 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 1.0.1", - "num-traits 0.2.14", + "autocfg 1.1.0", + "num-traits 0.2.15", ] [[package]] name = "num-iter" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-integer", - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[package]] @@ -4295,10 +4274,10 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-bigint", "num-integer", - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[package]] @@ -4307,16 +4286,16 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.14", + "num-traits 0.2.15", ] [[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 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -4331,9 +4310,9 @@ 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", ] @@ -4344,15 +4323,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "memchr", -] - [[package]] name = "object" version = "0.28.3" @@ -4367,9 +4337,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "opaque-debug" @@ -4385,18 +4355,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -4405,11 +4387,11 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -4423,7 +4405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.4", + "getrandom 0.2.5", "subtle 2.4.1", "zeroize", ] @@ -4436,9 +4418,9 @@ checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi 0.3.9", ] @@ -4460,7 +4442,7 @@ dependencies = [ "data-encoding", "multihash", "percent-encoding 2.1.0", - "serde 1.0.136", + "serde 1.0.137", "static_assertions", "unsigned-varint 0.7.1", "url 2.2.2", @@ -4502,7 +4484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.6", + "lock_api 0.4.7", "parking_lot_core 0.8.5", ] @@ -4530,16 +4512,16 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "smallvec 1.8.0", "winapi 0.3.9", ] [[package]] name = "paste" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "pathdiff" @@ -4609,7 +4591,7 @@ checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset 0.2.0", "indexmap", - "serde 1.0.136", + "serde 1.0.137", "serde_derive", ] @@ -4671,9 +4653,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[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" @@ -4683,9 +4665,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" @@ -4695,7 +4677,7 @@ checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ "cfg-if 1.0.0", "libc", - "log 0.4.14", + "log 0.4.17", "wepoll-ffi", "winapi 0.3.9", ] @@ -4779,9 +4761,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -4813,9 +4795,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" dependencies = [ "unicode-xid", ] @@ -4830,9 +4812,9 @@ dependencies = [ "bitflags", "byteorder", "lazy_static 1.4.0", - "num-traits 0.2.14", + "num-traits 0.2.15", "quick-error 2.0.1", - "rand 0.8.4", + "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", "regex-syntax", @@ -4843,15 +4825,15 @@ dependencies = [ [[package]] name = "proptest" version = "1.0.0" -source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#d8fd7ad8897df353987d2c41793b5fe164fed9b9" +source = "git+https://github.com/heliaxdev/proptest?branch=tomas/sm#b9517a726c032897a8b41c215147f44588b33dcc" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static 1.4.0", - "num-traits 0.2.14", + "num-traits 0.2.15", "quick-error 2.0.1", - "rand 0.8.4", + "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", "regex-syntax", @@ -4886,9 +4868,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ "bytes 1.1.0", - "heck", + "heck 0.3.3", "itertools 0.9.0", - "log 0.4.14", + "log 0.4.17", "multimap", "petgraph 0.5.1", "prost 0.7.0", @@ -4904,10 +4886,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.1.0", - "heck", + "heck 0.3.3", "itertools 0.10.3", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "multimap", "petgraph 0.6.0", "prost 0.9.0", @@ -4990,7 +4972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" dependencies = [ "byteorder", - "log 0.4.14", + "log 0.4.17", "parity-wasm", ] @@ -5019,9 +5001,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -5032,7 +5014,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "libc", "rand_chacha 0.1.1", "rand_core 0.4.2", @@ -5060,14 +5042,13 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -5076,7 +5057,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "rand_core 0.3.1", ] @@ -5130,7 +5111,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] [[package]] @@ -5151,15 +5132,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "rand_isaac" version = "0.1.1" @@ -5200,7 +5172,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "rand_core 0.4.2", ] @@ -5234,7 +5206,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "crossbeam-deque", "either", "rayon-core", @@ -5242,14 +5214,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" dependencies = [ - "crossbeam-channel 0.5.2", + "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.6", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.8", "num_cpus", ] @@ -5270,9 +5241,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] @@ -5283,17 +5254,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" dependencies = [ - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.4", - "redox_syscall 0.2.10", + "getrandom 0.2.5", + "redox_syscall 0.2.13", + "thiserror", ] [[package]] @@ -5302,16 +5274,16 @@ version = "0.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ - "log 0.4.14", + "log 0.4.17", "rustc-hash", "smallvec 1.8.0", ] [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -5365,9 +5337,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64 0.13.0", "bytes 1.1.0", @@ -5377,17 +5349,17 @@ dependencies = [ "h2", "http", "http-body", - "hyper 0.14.16", + "hyper 0.14.18", "hyper-tls", "ipnet", "js-sys", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.8", - "serde 1.0.136", + "pin-project-lite 0.2.9", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", @@ -5396,7 +5368,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.7.0", + "winreg 0.10.1", ] [[package]] @@ -5448,12 +5420,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.36" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5230ae2981a885590b0dc84e0b24c0ed23ad24f7adc0eb824b26cafa961f7c36" +checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" dependencies = [ "bytecheck", - "hashbrown 0.12.0", + "hashbrown 0.12.1", "ptr_meta", "rend", "rkyv_derive", @@ -5462,9 +5434,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.36" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc752d5925dbcb324522f3a4c93193d17f107b2e11810913aa3ad352fa01480" +checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" dependencies = [ "proc-macro2", "quote", @@ -5508,13 +5480,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.20.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0593ce4677e3800ddafb3de917e8397b1348e06e688128ade722d88fbe11ebf" +checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" dependencies = [ "arrayvec 0.7.2", - "num-traits 0.2.14", - "serde 1.0.136", + "num-traits 0.2.15", + "serde 1.0.137", ] [[package]] @@ -5547,6 +5519,15 @@ 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.9", +] + [[package]] name = "rustls" version = "0.19.1" @@ -5554,7 +5535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.14", + "log 0.4.17", "ring", "sct", "webpki", @@ -5596,7 +5577,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "pin-project 0.4.29", "static_assertions", ] @@ -5609,18 +5590,18 @@ checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "safe-proc-macro2" -version = "1.0.24" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9ca0693867c373d726819a6655d3dc4e88028905f1b1e9b9d399a57ea1d83e" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" dependencies = [ "unicode-xid", ] [[package]] name = "safe-quote" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566c55c34afcf23f8ae1170373f925201163ed0a9fd315274f35eab6d660b56d" +checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" dependencies = [ "safe-proc-macro2", ] @@ -5636,9 +5617,9 @@ dependencies = [ [[package]] name = "safe-regex-compiler" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77be88a67af0852122737d6944cf6cf3b493051ac063b074d11d6dc3aaa57047" +checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" dependencies = [ "safe-proc-macro2", "safe-quote", @@ -5646,9 +5627,9 @@ dependencies = [ [[package]] name = "safe-regex-macro" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9276d04505a852d89fab9b9d5d72b47e84c726b121f4b8b212b44fba990485d" +checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" dependencies = [ "safe-proc-macro2", "safe-regex-compiler", @@ -5712,9 +5693,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -5725,9 +5706,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -5751,6 +5732,12 @@ dependencies = [ "semver-parser 0.10.2", ] +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + [[package]] name = "semver-parser" version = "0.7.0" @@ -5774,9 +5761,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -5795,18 +5782,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ - "serde 1.0.136", + "serde 1.0.137", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -5815,14 +5802,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "indexmap", - "itoa 1.0.1", + "itoa", "ryu", - "serde 1.0.136", + "serde 1.0.137", ] [[package]] @@ -5832,14 +5819,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.136", + "serde 1.0.137", ] [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", @@ -5848,11 +5835,11 @@ dependencies = [ [[package]] name = "serde_test" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21675ba6f9d97711cc00eee79d8dd7d0a31e571c350fb4d8a7c78f70c0e7b0e9" +checksum = "fe196827aea34242c314d2f0dd49ed00a129225e80dda71b0dbf65d54d25628d" dependencies = [ - "serde 1.0.136", + "serde 1.0.137", ] [[package]] @@ -5862,9 +5849,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa", "ryu", - "serde 1.0.136", + "serde 1.0.137", ] [[package]] @@ -5875,7 +5862,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.136", + "serde 1.0.137", "yaml-rust", ] @@ -5904,6 +5891,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -6006,9 +6004,9 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" @@ -6034,7 +6032,7 @@ dependencies = [ "aes-gcm", "blake2 0.9.2", "chacha20poly1305", - "rand 0.8.4", + "rand 0.8.5", "rand_core 0.6.3", "ring", "rustc_version 0.3.3", @@ -6073,9 +6071,9 @@ dependencies = [ "base64 0.12.3", "bytes 0.5.6", "flate2", - "futures 0.3.19", + "futures 0.3.21", "httparse", - "log 0.4.14", + "log 0.4.17", "rand 0.7.3", "sha-1 0.9.8", ] @@ -6135,7 +6133,7 @@ checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" dependencies = [ "atty", "chrono", - "log 0.4.14", + "log 0.4.17", "termcolor", "thread_local 0.3.4", ] @@ -6167,7 +6165,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "syn", @@ -6215,9 +6213,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -6276,7 +6274,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "remove_dir_all", "winapi 0.3.9", ] @@ -6284,19 +6282,19 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "async-trait", "bytes 1.1.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.19", - "num-traits 0.2.14", + "futures 0.3.21", + "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", @@ -6305,7 +6303,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.7", + "time 0.3.9", "zeroize", ] @@ -6319,12 +6317,12 @@ dependencies = [ "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.19", - "num-traits 0.2.14", + "futures 0.3.21", + "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", @@ -6333,17 +6331,17 @@ dependencies = [ "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.7", + "time 0.3.9", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "flex-error", - "serde 1.0.136", + "serde 1.0.137", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "toml", @@ -6356,7 +6354,7 @@ 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.136", + "serde 1.0.137", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "toml", @@ -6366,14 +6364,14 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "derive_more", "flex-error", - "serde 1.0.136", + "serde 1.0.137", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -6383,27 +6381,27 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.2 dependencies = [ "derive_more", "flex-error", - "serde 1.0.136", + "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.7", + "time 0.3.9", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "bytes 1.1.0", "flex-error", "num-derive", - "num-traits 0.2.14", + "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -6414,33 +6412,33 @@ dependencies = [ "bytes 1.1.0", "flex-error", "num-derive", - "num-traits 0.2.14", + "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.9", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.1.0", "flex-error", - "futures 0.3.19", - "getrandom 0.2.4", + "futures 0.3.21", + "getrandom 0.2.5", "http", - "hyper 0.14.16", + "hyper 0.14.18", "hyper-proxy", "hyper-rustls", "peg", "pin-project 1.0.10", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -6448,9 +6446,9 @@ dependencies = [ "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "thiserror", - "time 0.3.7", + "time 0.3.9", "tokio", - "tracing 0.1.31", + "tracing 0.1.34", "url 2.2.2", "uuid", "walkdir", @@ -6465,15 +6463,15 @@ dependencies = [ "async-tungstenite", "bytes 1.1.0", "flex-error", - "futures 0.3.19", - "getrandom 0.2.4", + "futures 0.3.21", + "getrandom 0.2.5", "http", - "hyper 0.14.16", + "hyper 0.14.18", "hyper-proxy", "hyper-rustls", "peg", "pin-project 1.0.10", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -6481,9 +6479,9 @@ dependencies = [ "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.7", + "time 0.3.9", "tokio", - "tracing 0.1.31", + "tracing 0.1.34", "url 2.2.2", "uuid", "walkdir", @@ -6492,16 +6490,16 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#3fa4f08def72acfde7f213515ca28fe5f16eac2f" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.136", + "serde 1.0.137", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.7", + "time 0.3.9", ] [[package]] @@ -6511,12 +6509,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.2 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.136", + "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.7", + "time 0.3.9", ] [[package]] @@ -6531,9 +6529,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] @@ -6546,7 +6544,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", "numtoa", - "redox_syscall 0.2.10", + "redox_syscall 0.2.13", "redox_termios", ] @@ -6558,9 +6556,9 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" +checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" dependencies = [ "proc-macro2", "quote", @@ -6638,9 +6636,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "libc", "num_threads", @@ -6649,15 +6647,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", ] @@ -6681,7 +6679,7 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot 0.11.2", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "signal-hook-registry", "tokio-macros", "winapi 0.3.9", @@ -6716,7 +6714,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.31", - "log 0.4.14", + "log 0.4.17", ] [[package]] @@ -6725,7 +6723,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "tokio", ] @@ -6759,7 +6757,7 @@ dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "mio 0.6.23", "num_cpus", "parking_lot 0.9.0", @@ -6787,7 +6785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "tokio", ] @@ -6848,18 +6846,32 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "log 0.4.14", - "pin-project-lite 0.2.8", + "log 0.4.17", + "pin-project-lite 0.2.9", "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-sink", + "pin-project-lite 0.2.9", + "tokio", + "tracing 0.1.34", +] + [[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 1.0.136", + "serde 1.0.137", ] [[package]] @@ -6877,7 +6889,7 @@ dependencies = [ "h2", "http", "http-body", - "hyper 0.14.16", + "hyper 0.14.18", "hyper-timeout", "percent-encoding 2.1.0", "pin-project 1.0.10", @@ -6885,11 +6897,11 @@ dependencies = [ "prost-derive 0.9.0", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.6.9", "tower", "tower-layer", "tower-service", - "tracing 0.1.31", + "tracing 0.1.34", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -6907,24 +6919,23 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5651b5f6860a99bd1adb59dbfe1db8beb433e73709d9032b413a77e2fb7c066a" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", "pin-project 1.0.10", - "pin-project-lite 0.2.8", - "rand 0.8.4", + "pin-project-lite 0.2.9", + "rand 0.8.5", "slab", "tokio", - "tokio-stream", - "tokio-util", + "tokio-util 0.7.1", "tower-layer", "tower-service", - "tracing 0.1.31", + "tracing 0.1.34", ] [[package]] @@ -6933,13 +6944,13 @@ version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#25a0c673ea6748730f86f7f20c933d0f07b50621" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "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=bat/abcipp-rebase-master)", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.6.9", "tower", "tracing 0.1.30", "tracing-tower", @@ -6951,13 +6962,13 @@ 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.19", + "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", + "tokio-util 0.6.9", "tower", "tracing 0.1.30", "tracing-tower", @@ -6990,29 +7001,28 @@ version = "0.1.30" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite 0.2.8", - "tracing-attributes 0.1.19 (git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30)", - "tracing-core 0.1.22 (git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30)", + "pin-project-lite 0.2.9", + "tracing-attributes 0.1.19", + "tracing-core 0.1.22", ] [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", - "log 0.4.14", - "pin-project-lite 0.2.8", - "tracing-attributes 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing-core 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.17", + "pin-project-lite 0.2.9", + "tracing-attributes 0.1.21", + "tracing-core 0.1.26", ] [[package]] name = "tracing-attributes" version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "proc-macro2", "quote", @@ -7021,8 +7031,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" -source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -7032,19 +7043,19 @@ dependencies = [ [[package]] name = "tracing-core" version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "lazy_static 1.4.0", - "valuable", ] [[package]] name = "tracing-core" -version = "0.1.22" -source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static 1.4.0", + "valuable", ] [[package]] @@ -7053,7 +7064,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.31", + "tracing 0.1.34", "tracing-subscriber 0.2.25", ] @@ -7064,7 +7075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ "pin-project 1.0.10", - "tracing 0.1.31", + "tracing 0.1.34", ] [[package]] @@ -7072,19 +7083,19 @@ name = "tracing-futures" version = "0.2.5" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "pin-project-lite 0.2.8", + "pin-project-lite 0.2.9", "tracing 0.1.30", ] [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static 1.4.0", - "log 0.4.14", - "tracing-core 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.17", + "tracing-core 0.1.26", ] [[package]] @@ -7095,14 +7106,14 @@ checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", "thread_local 1.1.4", - "tracing-core 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.26", ] [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ "ansi_term", "lazy_static 1.4.0", @@ -7111,8 +7122,8 @@ dependencies = [ "sharded-slab", "smallvec 1.8.0", "thread_local 1.1.4", - "tracing 0.1.31", - "tracing-core 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.34", + "tracing-core 0.1.26", "tracing-log", ] @@ -7121,8 +7132,8 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.19", - "pin-project-lite 0.2.8", + "futures 0.3.21", + "pin-project-lite 0.2.9", "tower-layer", "tower-make", "tower-service", @@ -7138,9 +7149,9 @@ checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" [[package]] name = "trust-dns-proto" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -7152,8 +7163,8 @@ dependencies = [ "idna 0.2.3", "ipnet", "lazy_static 1.4.0", - "log 0.4.14", - "rand 0.8.4", + "log 0.4.17", + "rand 0.8.5", "smallvec 1.8.0", "thiserror", "tinyvec", @@ -7162,15 +7173,15 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", @@ -7197,8 +7208,8 @@ dependencies = [ "http", "httparse", "input_buffer", - "log 0.4.14", - "rand 0.8.4", + "log 0.4.17", + "rand 0.8.5", "sha-1 0.9.8", "url 2.2.2", "utf-8", @@ -7215,8 +7226,8 @@ dependencies = [ "bytes 1.1.0", "http", "httparse", - "log 0.4.14", - "rand 0.8.4", + "log 0.4.17", + "rand 0.8.5", "sha-1 0.9.8", "thiserror", "url 2.2.2", @@ -7243,9 +7254,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1b413ebfe8c2c74a69ff124699dd156a7fa41cb1d09ba6df94aa2f2b0a4a3a" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -7264,9 +7275,9 @@ dependencies = [ [[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-normalization" @@ -7279,9 +7290,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -7291,9 +7302,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 = "universal-hash" @@ -7369,9 +7380,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8-width" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cf7d77f457ef8dfa11e4cd5933c5ddb5dc52a94664071951219a97710f0a32b" +checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" [[package]] name = "uuid" @@ -7379,7 +7390,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.4", + "getrandom 0.2.5", ] [[package]] @@ -7390,9 +7401,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.8" +version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ "ctor", "version_check 0.9.4", @@ -7440,9 +7451,9 @@ dependencies = [ [[package]] name = "vswhom-sys" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f5402d3d0e79a069714f7b48e3ecc60be7775a2c049cb839457457a239532" +checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" dependencies = [ "cc", "libc", @@ -7480,7 +7491,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log 0.4.17", "try-lock", ] @@ -7498,9 +7509,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7508,13 +7519,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "proc-macro2", "quote", "syn", @@ -7523,9 +7534,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7535,9 +7546,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7545,9 +7556,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -7558,9 +7569,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wasm-timer" @@ -7568,7 +7579,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -7624,7 +7635,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "smallvec 1.8.0", "target-lexicon", @@ -7649,7 +7660,7 @@ dependencies = [ "rayon", "smallvec 1.8.0", "target-lexicon", - "tracing 0.1.31", + "tracing 0.1.34", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7699,7 +7710,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.136", + "serde 1.0.137", "serde_bytes", "target-lexicon", "thiserror", @@ -7720,11 +7731,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object", "rkyv", - "serde 1.0.136", + "serde 1.0.137", "tempfile", - "tracing 0.1.31", + "tracing 0.1.34", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7759,7 +7770,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.3", + "object", "thiserror", "wasmer-compiler", "wasmer-types", @@ -7774,7 +7785,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.136", + "serde 1.0.137", "thiserror", ] @@ -7795,7 +7806,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.136", + "serde 1.0.137", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -7815,9 +7826,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "40.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "9bb4f48a8b083dbc50e291e430afb8f524092bb00428957bcc63f49f856c64ac" dependencies = [ "leb128", "memchr", @@ -7826,9 +7837,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "0401b6395ce0db91629a75b29597ccb66ea29950af9fc859f1bb3a736609c76e" dependencies = [ "wast", ] @@ -7846,7 +7857,7 @@ dependencies = [ "glob", "globset", "lazy_static 1.4.0", - "log 0.4.14", + "log 0.4.17", "nix 0.20.0", "notify", "walkdir", @@ -7855,9 +7866,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.56" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -7884,9 +7895,9 @@ dependencies = [ [[package]] name = "websocket" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723abe6b75286edc51d8ecabb38a2353f62a9e9b0588998b59111474f1dcd637" +checksum = "d0e2836502b48713d4e391e7e016df529d46e269878fe5d961b15a1fd6417f1a" dependencies = [ "bytes 0.4.12", "futures 0.1.31", @@ -8001,15 +8012,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "winreg" version = "0.10.1" @@ -8042,9 +8044,9 @@ dependencies = [ [[package]] name = "xattr" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] @@ -8064,11 +8066,11 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.19", - "log 0.4.14", + "futures 0.3.21", + "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", - "rand 0.8.4", + "rand 0.8.5", "static_assertions", ] @@ -8083,9 +8085,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 4781179c54..ec495d918c 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -301,6 +301,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tracing::info!("Tendermint node is no longer running."); drop(aborter); + if res.is_err() { + tracing::error!("{:?}", &res); + } res }); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index c6d6659d6a..1550941d9e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -52,6 +52,8 @@ use tendermint_proto::abci::{ RequestPrepareProposal, ValidatorUpdate, }; #[cfg(not(feature = "ABCI"))] +use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; +#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::public_key; #[cfg(not(feature = "ABCI"))] use tendermint_proto::types::ConsensusParams; @@ -497,7 +499,9 @@ where &self, _req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - Default::default() + response::VerifyVoteExtension { + status: VerifyStatus::Accept as i32, + } } /// Commit a block. Persist the application state and return the Merkle root @@ -765,7 +769,7 @@ mod test_utils { tx: tx_bytes, }) .collect(); - if resp.status > 0 { + 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 4a92a5cb16..a05c4f7a2c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -87,23 +87,34 @@ mod prepare_block { /// Functions for creating the appropriate TxRecord given the /// numeric code pub(super) mod record { + use tendermint_proto::abci::tx_record::TxAction; + use super::*; /// Keep this transaction in the proposal pub fn keep(tx: TxBytes) -> TxRecord { - TxRecord { action: 1, tx } + 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: 2, tx } + 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: 3, tx } + TxRecord { + action: TxAction::Removed as i32, + tx, + } } } @@ -112,6 +123,7 @@ mod prepare_block { use anoma::types::address::xan; use anoma::types::storage::Epoch; use anoma::types::transaction::Fee; + use tendermint_proto::abci::tx_record::TxAction; use super::*; use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; @@ -241,7 +253,9 @@ mod prepare_block { tx: tx_bytes, action, }| { - if *action == 2 || *action == 1 { + if *action == (TxAction::Unmodified as i32) + || *action == (TxAction::Added as i32) + { Some( Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index afe7d2eee4..01a13a684b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,8 @@ //! 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, }; @@ -41,9 +43,9 @@ where ResponseProcessProposal { status: if tx_results.iter().any(|res| res.code > 3) { - 1 + ProposalStatus::Reject as i32 } else { - 0 + ProposalStatus::Accept as i32 }, tx_results, ..Default::default() @@ -286,11 +288,11 @@ mod test_process_proposal { 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, }; - #[cfg(not(feature = "ABCI"))] - use crate::node::ledger::shell::test_utils::TestError; /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index f027527383..ececc7adbe 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -201,6 +201,19 @@ 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", + "unsafe-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"); + #[cfg(feature = "ABCI")] std::process::Command::new(tendermint_path) .args(&[ "unsafe-reset-all", @@ -334,8 +347,6 @@ async fn update_tendermint_config( // In "dev", only produce blocks when there are txs or when the AppHash // changes config.consensus.create_empty_blocks = true; // !cfg!(feature = "dev"); - 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 From dc7adc4598ceef91c4cdc7354273fca823aa0d3b Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 9 May 2022 12:54:23 +0200 Subject: [PATCH 0004/1995] [fix]: Fixed how PrepareProposal includes txs from mempool. Fixed ABCI tests --- Cargo.lock | 12 +++++------ .../lib/node/ledger/shell/prepare_proposal.rs | 10 +++------ apps/src/lib/node/ledger/shims/abcipp_shim.rs | 17 +++++++++++++++ .../node/ledger/shims/abcipp_shim_types.rs | 5 ++--- apps/src/lib/node/ledger/tendermint_node.rs | 21 +++++++++++++++++-- shared/src/types/hash.rs | 1 + shared/src/types/storage.rs | 10 ++++++++- shared/src/types/time.rs | 11 ++-------- shared/src/vm/host_env.rs | 6 +++--- 9 files changed, 62 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b241ff963..4658d4ef6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6282,7 +6282,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6338,7 +6338,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "flex-error", "serde 1.0.137", @@ -6364,7 +6364,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "derive_more", "flex-error", @@ -6390,7 +6390,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6424,7 +6424,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "async-trait", "async-tungstenite", @@ -6490,7 +6490,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#be219db044b64bd86b2161bd84279c7ceb768c20" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a05c4f7a2c..53494765dd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -40,16 +40,12 @@ mod prepare_block { let mut txs: Vec = req .txs .into_iter() - .enumerate() - .map(|(ix, tx_bytes)| { + .take(number_of_new_txs) + .map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) { - if ix < number_of_new_txs { - record::keep(tx_bytes) - } else { - record::remove(tx_bytes) - } + record::keep(tx_bytes) } else { record::remove(tx_bytes) } diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 1070aecdd2..bdc2572514 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -4,6 +4,9 @@ use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; +use anoma::types::hash::Hash; +use anoma::types::transaction::hash_tx; +use anoma::types::storage::BlockHash; use futures::future::FutureExt; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestBeginBlock; @@ -21,6 +24,7 @@ use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; + /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used /// by tendermint and the shell's interface. @@ -69,6 +73,16 @@ impl AbcippShim { ) } + /// Get the hash of the txs in the block + pub fn get_hash(&self) -> Hash { + let bytes: Vec = self.processed_txs + .iter() + .map(|processed| processed.tx.clone()) + .flatten() + .collect(); + hash_tx(bytes.as_slice()) + } + /// Run the shell's blocking loop that receives messages from the /// [`AbciService`]. pub fn run(mut self) { @@ -139,6 +153,9 @@ impl AbcippShim { 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)) 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 fd5e2908e9..9f0a93fb37 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -258,10 +258,9 @@ pub mod shim { fn from(req: RequestBeginBlock) -> FinalizeBlock { let header = req.header.unwrap(); FinalizeBlock { - hash: BlockHash::try_from(req.hash.as_slice()).unwrap(), + hash: BlockHash::default(), header: Header { - hash: Hash::try_from(header.app_hash.as_slice()) - .unwrap(), + hash: Hash::default(), time: DateTimeUtc::try_from(header.time.unwrap()) .unwrap(), next_validators_hash: Hash::try_from( diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index ececc7adbe..7170b3f588 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -130,8 +130,14 @@ pub async fn run( .await; } } - - write_tm_genesis(&home_dir, chain_id, genesis_time).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; + } update_tendermint_config(&home_dir, config).await?; @@ -347,6 +353,11 @@ async fn update_tendermint_config( // 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 @@ -385,6 +396,8 @@ async fn write_tm_genesis( home_dir: impl AsRef, chain_id: ChainId, genesis_time: DateTimeUtc, + #[cfg(not(feature = "ABCI"))] + config: &config::Tendermint, ) { let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("genesis.json"); @@ -405,6 +418,10 @@ 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() + } let mut file = OpenOptions::new() .write(true) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index f41a8707ad..c7ae683f25 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -29,6 +29,7 @@ pub type HashResult = std::result::Result; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index d13dc6423e..7333f784df 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -99,6 +99,12 @@ impl From for u64 { )] pub struct BlockHash(pub [u8; BLOCK_HASH_LENGTH]); +impl From for BlockHash { + fn from(hash: Hash) -> Self { + BlockHash(hash.0) + } +} + impl TryFrom for BlockHeight { type Error = String; @@ -134,6 +140,7 @@ impl TryFrom<&[u8]> for BlockHash { Ok(BlockHash(hash)) } } + impl TryFrom> for BlockHash { type Error = self::Error; @@ -152,6 +159,7 @@ impl TryFrom> for BlockHash { Ok(BlockHash(hash)) } } + impl core::fmt::Debug for BlockHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let hash = format!("{}", ByteBuf(&self.0)); @@ -174,7 +182,7 @@ pub struct Header { impl Header { /// The number of bytes when this header is encoded pub fn encoded_len(&self) -> usize { - self.try_to_vec().map(|ser| ser.len()).unwrap_or(usize::MAX) + self.try_to_vec().map(|ser| ser.len()).unwrap() } } diff --git a/shared/src/types/time.rs b/shared/src/types/time.rs index ca4b8b26db..94c29fa688 100644 --- a/shared/src/types/time.rs +++ b/shared/src/types/time.rs @@ -104,15 +104,8 @@ impl DateTimeUtc { } /// Returns an rfc3339 string or an error. - pub fn to_rfc3339(&self) -> Result { - Time::try_from(*self) - .map(|t| t.to_rfc3339()) - .map_err(|err| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Could not parse timestamp because: {}", err), - ) - }) + pub fn to_rfc3339(&self) -> String { + chrono::DateTime::to_rfc3339(&self.0) } } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 4489cbe784..e7d6e37cf5 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1556,9 +1556,9 @@ where Ok(match header { Some(h) => { let time = - h.time.to_rfc3339().map_err(TxRuntimeError::EncodingError)?; - let time = - time.try_to_vec().map_err(TxRuntimeError::EncodingError)?; + h.time.to_rfc3339() + .try_to_vec() + .map_err(TxRuntimeError::EncodingError)?; let len: i64 = time .len() .try_into() From cb5d77b766378e7415f28c132bbeff8f44199970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 9 May 2022 17:58:01 +0200 Subject: [PATCH 0005/1995] ledger: add tx hashes to node tx result log lines --- apps/src/lib/node/ledger/shell/finalize_block.rs | 16 +++++++++++----- tests/src/e2e/gossip_tests.rs | 2 +- tests/src/e2e/ledger_tests.rs | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index cae5ad4015..75da3eb9cb 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -163,8 +163,9 @@ where Ok(result) => { if result.is_accepted() { tracing::info!( - "all VPs accepted apply_tx storage modification \ - {:#?}", + "all VPs accepted transaction {} storage \ + modification {:#?}", + tx_event["hash"], result ); self.write_log.commit_tx(); @@ -193,8 +194,9 @@ where } } else { tracing::info!( - "some VPs rejected apply_tx storage modification \ - {:#?}", + "some VPs rejected transaction {} storage \ + modification {:#?}", + tx_event["hash"], result.vps_result.rejected_vps ); self.write_log.drop_tx(); @@ -204,7 +206,11 @@ where tx_event["info"] = result.to_string(); } Err(msg) => { - tracing::info!("Transaction failed with: {}", msg); + tracing::info!( + "Transaction {} failed with: {}", + tx_event["hash"], + msg + ); self.write_log.drop_tx(); tx_event["gas_used"] = self .gas_meter diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs index db37049198..ff6e346c51 100644 --- a/tests/src/e2e/gossip_tests.rs +++ b/tests/src/e2e/gossip_tests.rs @@ -323,7 +323,7 @@ fn match_intents() -> Result<()> { ))?; // check that the all VPs accept the transaction - ledger.exp_string("all VPs accepted apply_tx storage modification")?; + ledger.exp_string("all VPs accepted transaction")?; Ok(()) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 30d65a82c6..ec858892fa 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -409,7 +409,7 @@ fn invalid_transactions() -> Result<()> { client.exp_string(r#""code": "1"#)?; client.assert_success(); - ledger.exp_string("some VPs rejected apply_tx storage modification")?; + ledger.exp_string("some VPs rejected transaction")?; // Wait to commit a block ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; From e742e9664f218a9ce8be5272f5a8cab3e8f2bad7 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 24 May 2022 11:24:04 +0200 Subject: [PATCH 0006/1995] [feat]: Added client to query events from the new events log. Still WIP --- Cargo.lock | 52 ++- apps/Cargo.toml | 6 +- apps/src/bin/anoma-client/cli.rs | 35 ++ apps/src/lib/client/mod.rs | 2 + apps/src/lib/client/rpc.rs | 3 +- apps/src/lib/client/signing.rs | 96 ++++- apps/src/lib/client/tendermint_rpc_types.rs | 348 ++++++++++++++++ .../lib/client/tendermint_websocket_client.rs | 26 +- apps/src/lib/client/tm_jsonrpc_client.rs | 267 ++++++++++++ apps/src/lib/client/tx.rs | 387 ++++-------------- apps/src/lib/node/ledger/events.rs | 35 +- apps/src/lib/node/ledger/shell/mod.rs | 4 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 10 +- apps/src/lib/node/ledger/tendermint_node.rs | 16 +- apps/src/lib/node/matchmaker.rs | 3 +- shared/src/ledger/ibc/vp/client.rs | 8 +- shared/src/ledger/ibc/vp/mod.rs | 21 +- shared/src/vm/host_env.rs | 9 +- tests/src/vm_host_env/ibc.rs | 13 +- 19 files changed, 962 insertions(+), 379 deletions(-) create mode 100644 apps/src/lib/client/tendermint_rpc_types.rs create mode 100644 apps/src/lib/client/tm_jsonrpc_client.rs diff --git a/Cargo.lock b/Cargo.lock index 4658d4ef6c..ff2ae8af42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,7 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", + "bincode", "bit-set", "blake2b-rs", "borsh", @@ -151,6 +152,7 @@ dependencies = [ "clap 3.0.0-beta.2", "color-eyre", "config", + "curl", "derivative", "directories", "ed25519-consensus", @@ -1543,6 +1545,36 @@ dependencies = [ "rand 0.7.3", ] +[[package]] +name = "curl" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.2", + "winapi 0.3.9", +] + +[[package]] +name = "curl-sys" +version = "0.4.55+curl-7.83.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi 0.3.9", +] + [[package]] name = "curve25519-dalek" version = "3.2.1" @@ -1808,9 +1840,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +checksum = "d916019f70ae3a1faa1195685e290287f39207d38e6dfee727197cffcc002214" dependencies = [ "serde 1.0.137", "signature", @@ -4325,9 +4357,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "object" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", @@ -6282,7 +6314,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6338,7 +6370,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "flex-error", "serde 1.0.137", @@ -6364,7 +6396,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "derive_more", "flex-error", @@ -6390,7 +6422,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6424,7 +6456,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "async-trait", "async-tungstenite", @@ -6490,7 +6522,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#bffe9971a1eca01b46884fdc92a03631d7f8979b" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b18291bf4f..ba031e93ca 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -73,12 +73,14 @@ base64 = "0.13.0" bech32 = "0.8.0" blake2b-rs = "0.2.0" borsh = "0.9.0" +bincode = "1.3.3" byte-unit = "4.0.13" byteorder = "1.4.2" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} color-eyre = "0.5.10" config = "0.11.0" +curl = "0.4.43" derivative = "2.2.0" directories = "4.0.1" ed25519-consensus = "1.2.0" @@ -107,13 +109,13 @@ rand = {version = "0.8", default-features = false} rand_core = {version = "0.6", default-features = false} rayon = "=1.5.1" regex = "1.4.5" -reqwest = "0.11.4" +reqwest = {version = "0.11.4", features = ["json"]} rlimit = "0.5.4" rocksdb = "0.16.0" rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" -serde_json = "1.0.62" +serde_json = {version = "1.0.62", features = ["raw_value"]} serde_regex = "1.1.0" sha2 = "0.9.3" signal-hook = "0.3.9" diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 44cab0cf4c..6128f5dfc0 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -1,11 +1,45 @@ //! Anoma client CLI. +use anoma::types::token::Amount; use anoma_apps::cli; use anoma_apps::cli::cmds::*; use anoma_apps::client::{gossip, rpc, tx, utils}; use color_eyre::eyre::Result; pub async fn main() -> Result<()> { + use std::str::FromStr; + let global_args = crate::cli::cli::args::Global { + chain_id: None, + base_dir: std::path::PathBuf::from_str(".anoma").unwrap(), + wasm_dir: None, + mode: Some(anoma_apps::config::TendermintMode::Full), + }; + let ctx = anoma_apps::cli::Context::new(global_args); + let args = anoma_apps::cli::args::TxTransfer { + tx: anoma_apps::cli::args::Tx { + dry_run: false, + force: false, + broadcast_only: false, + ledger_address: tendermint_config::net::Address::Tcp { + peer_id: None, + host: "127.0.0.1".into(), + port: 26657, + }, + initialized_account_alias: None, + fee_amount: Default::default(), + fee_token: anoma_apps::cli::context::WalletAddress::new( + "XAN".into(), + ), + gas_limit: 0.into(), + signing_key: None, + signer: None, + }, + source: anoma_apps::cli::context::WalletAddress::new("Bertha".into()), + target: anoma_apps::cli::context::WalletAddress::new("Albert".into()), + token: anoma_apps::cli::context::WalletAddress::new("XAN".into()), + amount: Amount::from(10100000), + }; + tx::submit_transfer(ctx, args).await; match cli::anoma_client_cli() { cli::AnomaClient::WithContext(cmd_box) => { let (cmd, ctx) = *cmd_box; @@ -16,6 +50,7 @@ pub async fn main() -> Result<()> { tx::submit_custom(ctx, args).await; } Sub::TxTransfer(TxTransfer(args)) => { + println!("{:?}", args); tx::submit_transfer(ctx, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index e1112dd8d1..a3d2ddece9 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,6 +1,8 @@ pub mod gossip; pub mod rpc; pub mod signing; +pub mod tendermint_rpc_types; mod tendermint_websocket_client; +mod tm_jsonrpc_client; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 635fa52be0..af79b6e09a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -45,7 +45,7 @@ use tendermint_rpc_abci::{Order, SubscriptionClient, WebSocketClient}; use tendermint_stable::abci::Code; use crate::cli::{self, args, Context}; -use crate::client::tx::TxResponse; +use crate::client::tendermint_rpc_types::TxResponse; use crate::node::ledger::rpc::Path; /// Query the epoch of the last committed block @@ -1217,7 +1217,6 @@ pub async fn query_tx_response( /// Lookup the results of applying the specified transaction to the /// blockchain. - pub async fn query_result(_ctx: Context, args: args::QueryResult) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 5a3068eb63..ffe50b66d8 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -3,15 +3,21 @@ use std::rc::Rc; +use anoma::proto::Tx; use anoma::types::address::{Address, ImplicitAddress}; use anoma::types::key::*; +use anoma::types::storage::Epoch; +use anoma::types::transaction::{hash_tx, Fee, WrapperTx}; +use borsh::BorshSerialize; #[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; +use crate::cli::context::WalletAddress; +use crate::cli::{self, args, Context}; +use crate::client::tendermint_rpc_types::TxBroadcastData; use crate::wallet::Wallet; /// Find the public key for the given address and try to load the keypair @@ -65,3 +71,91 @@ pub async fn find_keypair( } } } + +/// Sign a transaction with a given signing key or public key of a given signer. +/// If no explicit signer given, use the `default`. If no `default` is given, +/// panics. +/// +/// If this is not a dry run, the tx is put in a wrapper and returned along with +/// hashes needed for monitoring the tx on chain. +/// +/// If it is a dry run, it is not put in a wrapper, but returned as is. +pub async fn sign_tx( + mut ctx: Context, + tx: Tx, + args: &args::Tx, + default: Option<&WalletAddress>, +) -> (Context, TxBroadcastData) { + let (tx, keypair) = if let Some(signing_key) = &args.signing_key { + let signing_key = ctx.get_cached(signing_key); + (tx.sign(&signing_key), signing_key) + } else if let Some(signer) = args.signer.as_ref().or(default) { + let signer = ctx.get(signer); + let signing_key = + find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) + .await; + (tx.sign(&signing_key), signing_key) + } else { + panic!( + "All transactions must be signed; please either specify the key \ + or the address from which to look up the signing key." + ); + }; + let epoch = rpc::query_epoch(args::Query { + ledger_address: args.ledger_address.clone(), + }) + .await; + let broadcast_data = if args.dry_run { + TxBroadcastData::DryRun(tx) + } else { + sign_wrapper(&ctx, args, epoch, tx, &keypair).await + }; + (ctx, broadcast_data) +} + +/// Create a wrapper tx from a normal tx. Get the hash of the +/// wrapper and its payload which is needed for monitoring its +/// progress on chain. +pub async fn sign_wrapper( + ctx: &Context, + args: &args::Tx, + epoch: Epoch, + tx: Tx, + keypair: &common::SecretKey, +) -> TxBroadcastData { + let tx = { + WrapperTx::new( + Fee { + amount: args.fee_amount, + token: ctx.get(&args.fee_token), + }, + keypair, + epoch, + args.gas_limit.clone(), + tx, + // TODO: Actually use the fetched encryption key + Default::default(), + ) + }; + + // 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() + }; + // 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 + }; + TxBroadcastData::Wrapper { + tx: tx + .sign(keypair) + .expect("Wrapper tx signing keypair should be correct"), + wrapper_hash, + decrypted_hash, + } +} diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs new file mode 100644 index 0000000000..c80389ef22 --- /dev/null +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -0,0 +1,348 @@ +use anoma::proto::Tx; +use anoma::types::address::Address; +use jsonpath_lib as jsonpath; +use serde::Serialize; + +use crate::cli::safe_exit; +#[cfg(not(feature = "ABCI"))] +use crate::node::ledger::events::Attributes; + +/// Data needed for broadcasting a tx and +/// monitoring its progress on chain +/// +/// Txs may be either a dry run or else +/// they should be encrypted and included +/// in a wrapper. +pub enum TxBroadcastData { + DryRun(Tx), + Wrapper { + tx: Tx, + wrapper_hash: String, + decrypted_hash: Option, + }, +} + +/// A parsed event from tendermint relating to a transaction +#[derive(Debug, Serialize)] +pub struct TxResponse { + pub info: String, + pub log: String, + pub height: String, + pub hash: String, + pub code: String, + pub gas_used: String, + pub initialized_accounts: Vec
, +} + +impl TxResponse { + /// Find a tx with a given hash from the the websocket subscription + /// to Tendermint events. + pub fn find_tx(json: serde_json::Value, tx_hash: &str) -> Self { + 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 { + if let Ok(hash) = + selector(&format!("$.events.['{}.hash'][{}]", evt_key, index)) + { + let hash = hash[0].clone(); + if hash == tx_hash_json { + break hash; + } else { + index += 1; + } + } else { + eprintln!( + "Couldn't find tx with hash {} in the event string {}", + tx_hash, json + ); + safe_exit(1) + } + }; + let info = + selector(&format!("$.events.['{}.info'][{}]", evt_key, index)) + .unwrap(); + let log = selector(&format!("$.events.['{}.log'][{}]", evt_key, index)) + .unwrap(); + let height = + selector(&format!("$.events.['{}.height'][{}]", evt_key, index)) + .unwrap(); + let code = + selector(&format!("$.events.['{}.code'][{}]", evt_key, index)) + .unwrap(); + let gas_used = + selector(&format!("$.events.['{}.gas_used'][{}]", evt_key, index)) + .unwrap(); + let initialized_accounts = selector(&format!( + "$.events.['{}.initialized_accounts'][{}]", + evt_key, index + )); + let initialized_accounts = match initialized_accounts { + Ok(values) if !values.is_empty() => { + // In a response, the initialized accounts are encoded as e.g.: + // ``` + // "applied.initialized_accounts": Array([ + // String( + // "[\"atest1...\"]", + // ), + // ]), + // ... + // So we need to decode the inner string first ... + let raw: String = + serde_json::from_value(values[0].clone()).unwrap(); + // ... and then decode the vec from the array inside the string + serde_json::from_str(&raw).unwrap() + } + _ => vec![], + }; + TxResponse { + info: serde_json::from_value(info[0].clone()).unwrap(), + log: serde_json::from_value(log[0].clone()).unwrap(), + height: serde_json::from_value(height[0].clone()).unwrap(), + hash: serde_json::from_value(hash).unwrap(), + code: serde_json::from_value(code[0].clone()).unwrap(), + gas_used: serde_json::from_value(gas_used[0].clone()).unwrap(), + initialized_accounts, + } + } +} + +#[cfg(not(feature = "ABCI"))] +mod params { + use std::convert::TryFrom; + use std::time::Duration; + + use serde::ser::SerializeTuple; + use serde::{Deserialize, Serializer}; + use serde_json::value::RawValue; + use tendermint_rpc::query::Query; + + use super::*; + + /// Opaque type for ordering events. Set by Tendermint + #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] + #[serde(transparent)] + pub struct Cursor(String); + + impl From for Cursor { + fn from(cursor: String) -> Self { + Cursor(cursor) + } + } + + /// Struct used for querying Tendermint's event logs + #[derive(Debug)] + pub struct EventParams { + /// The filter an event must satisfy in order to + /// be returned + pub filter: Query, + /// The maximum number of eligible results to return. + /// If zero or negative, the server will report a default number. + pub max_results: u64, + /// Return only items after this cursor. If empty, the limit is just + /// before the the beginning of the event log + pub after: Cursor, + /// Return only items before this cursor. If empty, the limit is just + /// after the head of the event log. + before: Cursor, + /// Wait for up to this long for events to be available. + pub wait_time: Duration, + } + + /// Struct to help serialize [`EventParams`] + #[derive(Serialize)] + struct Filter { + filter: String, + } + + impl Serialize for EventParams { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut ser = serializer.serialize_tuple(5)?; + ser.serialize_element(&Filter { + filter: self.filter.to_string(), + })?; + ser.serialize_element(&self.max_results)?; + ser.serialize_element(self.after.0.as_str())?; + ser.serialize_element(self.before.0.as_str())?; + ser.serialize_element(&self.wait_time.as_nanos())?; + ser.end() + } + } + + impl EventParams { + /// Initialize a new set of [`EventParams`] + pub fn new( + filter: Query, + max_results: u64, + wait_time: Duration, + ) -> Self { + Self { + filter, + max_results, + after: Default::default(), + before: Default::default(), + wait_time, + } + } + + /// Convert this into a list of raw json values to give to the + /// JSON RPC client. + pub fn to_params(&self) -> [Box; 5] { + [ + RawValue::from_string(format!( + "{{\"filter\": \"{}\"}}", + self.filter.to_string() + )) + .unwrap(), + RawValue::from_string(self.max_results.to_string()).unwrap(), + RawValue::from_string(format!("\"{}\"", self.after.0)).unwrap(), + RawValue::from_string(format!("\"{}\"", self.before.0)) + .unwrap(), + RawValue::from_string(self.wait_time.as_nanos().to_string()) + .unwrap(), + ] + } + } + + /// A reply from Tendermint for events matching the given [`EventParams`] + #[derive(Serialize, Deserialize)] + pub struct EventReply { + /// The items matching the request parameters, from newest + /// to oldest, if any were available within the timeout. + pub items: Vec, + /// This is true if there is at least one older matching item + /// available in the log that was not returned. + #[allow(dead_code)] + more: bool, + /// The cursor of the oldest item in the log at the time of this reply, + /// or "" if the log is empty. + #[allow(dead_code)] + oldest: Cursor, + /// The cursor of the newest item in the log at the time of this reply, + /// or "" if the log is empty. + pub newest: Cursor, + } + + /// An event returned from Tendermint + #[derive(Debug, PartialEq, Serialize, Deserialize)] + pub struct EventItem { + /// this specifies where in the event log this event is + #[allow(dead_code)] + pub cursor: Cursor, + /// The event type + #[allow(dead_code)] + pub event: String, + /// The raw event value + pub data: EventData, + } + + /// Raw data of an event returned from Tendermint + #[derive(Debug, PartialEq, Serialize, Deserialize)] + pub struct EventData { + pub r#type: String, + pub value: serde_json::Value, + } + + /// Parse the JSON payload received from the `events` JSON-RPC + /// endpoint of Tendermint. + /// + /// 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 { + println!("{}", serde_json::to_string_pretty(&reply).unwrap()); + let mut event = if let Some(event) = + reply.items.iter().find_map(|event| { + if let Some(attrs) = + Attributes::try_from(&event.data.value).ok() + { + match attrs.get("hash") { + Some(hash) if hash == tx_hash => Some(attrs), + _ => None, + } + } else { + None + } + }) { + event + } else { + return None; + }; + + let info = event.take("info").unwrap(); + let log = event.take("log").unwrap(); + let height = event.take("height").unwrap(); + let hash = event.take("hash").unwrap(); + let code = event.take("code").unwrap(); + let gas_used = + event.take("gas_used").unwrap_or_else(|| String::from("0")); + let initialized_accounts = event.take("initialized_accounts"); + let initialized_accounts = match initialized_accounts { + Some(values) => serde_json::from_str(&values).unwrap(), + _ => vec![], + }; + + Some(TxResponse { + info, + log, + height, + hash, + code, + gas_used, + initialized_accounts, + }) + } + + #[cfg(test)] + mod test_rpc_types { + use tendermint_rpc::query::EventType; + + use super::*; + + /// Test that [`EventParams`] is serialized correctly + #[test] + fn test_serialize_event_params() { + let params = EventParams { + filter: Query::from(EventType::NewBlock), + max_results: 5, + after: Cursor("16CCC798FB5F4670-0123".into()), + before: Default::default(), + wait_time: Duration::from_secs(59), + }; + assert_eq!( + serde_json::to_string(¶ms).expect("Test failed"), + r#"[{"filter":"tm.event = 'NewBlock'"},5,"16CCC798FB5F4670-0123","",59000000000]"# + ) + } + + /// Test that [`EventParams`] are parsed into a params array correctly + #[test] + fn test_to_params() { + let params = EventParams { + filter: Query::from(EventType::NewBlock), + max_results: 5, + after: Cursor("16CCC798FB5F4670-0123".into()), + before: Default::default(), + wait_time: Duration::from_secs(59), + }; + let args = params.to_params(); + assert_eq!(args[0].get(), r#"{"filter": "tm.event = 'NewBlock'"}"#); + assert_eq!(args[1].get(), "5"); + assert_eq!(args[2].get(), "\"16CCC798FB5F4670-0123\""); + assert_eq!(args[3].get(), "\"\""); + assert_eq!(args[4].get(), "59000000000"); + } + } +} + +#[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 8e5a937afb..fdb933ca16 100644 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ b/apps/src/lib/client/tendermint_websocket_client.rs @@ -11,8 +11,6 @@ use tendermint_config::net::Address; #[cfg(feature = "ABCI")] use tendermint_config_abci::net::Address; #[cfg(not(feature = "ABCI"))] -use tendermint_rpc::query::Query; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::{ Client, Error as RpcError, Request, Response, SimpleRequest, }; @@ -189,7 +187,7 @@ 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` @@ -199,6 +197,7 @@ use rpc_types::{RpcResponse, RpcSubscription, SubscribeType}; type Websocket = Arc>>; type ResponseQueue = Arc>>; +#[cfg(feature = "ABCI")] struct Subscription { id: String, query: Query, @@ -206,6 +205,7 @@ struct Subscription { pub struct TendermintWebsocketClient { websocket: Websocket, + #[cfg(feature = "ABCI")] subscribed: Option, received_responses: ResponseQueue, connection_timeout: Duration, @@ -224,6 +224,7 @@ 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 @@ -237,11 +238,15 @@ impl TendermintWebsocketClient { pub fn close(&mut self) { // Even in the case of errors, this will be shutdown let _ = self.websocket.lock().unwrap().shutdown(); - self.subscribed = None; + #[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 @@ -271,6 +276,7 @@ impl TendermintWebsocketClient { /// 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( @@ -286,6 +292,7 @@ impl TendermintWebsocketClient { /// 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, .. }) => { @@ -321,6 +328,7 @@ impl TendermintWebsocketClient { /// 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, @@ -469,20 +477,12 @@ fn get_id(req_json: &str) -> Result { /// 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(test)] +#[cfg(all(test, feature = "ABCI"))] mod test_tendermint_websocket_client { use std::time::Duration; use anoma::types::transaction::hash_tx as hash_tx_bytes; use serde::{Deserialize, Serialize}; - #[cfg(not(feature = "ABCI"))] - use tendermint::abci::transaction; - #[cfg(not(feature = "ABCI"))] - use tendermint_rpc::endpoint::abci_info::AbciInfo; - #[cfg(not(feature = "ABCI"))] - use tendermint_rpc::query::{EventType, Query}; - #[cfg(not(feature = "ABCI"))] - use tendermint_rpc::Client; #[cfg(feature = "ABCI")] use tendermint_rpc_abci::endpoint::abci_info::AbciInfo; #[cfg(feature = "ABCI")] diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs new file mode 100644 index 0000000000..736af0c072 --- /dev/null +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -0,0 +1,267 @@ +#[cfg(not(feature = "ABCI"))] +mod tm_jsonrpc { + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::ops::{Deref, DerefMut}; + + use curl::easy::{Easy2, Handler, WriteError}; + use serde::{Deserialize, Serialize}; + use tendermint_rpc::query::Query; + use thiserror::Error; + + use crate::client::tendermint_rpc_types::{ + parse, Cursor, EventParams, EventReply, TxResponse, + }; + + const JSONRPC_PORT: u16 = 26657; + + #[derive(Error, Debug)] + pub enum Error { + #[error("Error in sending JSON RPC request to Tendermint: {0}")] + SendError(curl::Error), + #[error("Received an error response from Tendermint: {0:?}")] + RpcError(tendermint_rpc::response_error::ResponseError), + #[error("Received malformed JSON response from Tendermint")] + MalformedJson, + #[error("Received an empty response from Tendermint")] + EmptyResponse, + #[error("Could not deserialize JSON response: {0}")] + DeserializeError(serde_json::Error), + #[error("Could not find event for the given hash: {0}")] + NotFound(String), + } + + /// 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, + } + } + } + + /// 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::RpcError(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); + + 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 latest event returned; used for paging through the event log + newest: Cursor, + /// The hash of the tx whose corresponding event is being searched for. + hash: &'a str, + } + + impl<'a> Deref for Client<'a> { + type Target = Easy2; + + 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> 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, + newest: Default::default(), + hash, + }; + client.reset(); + client + } + + /// Send a request to Tendermint + /// This pages through the event log 20 events at a time + /// until either + /// 1. The event is found and thus returned + /// 2. The end of the log is reached + pub fn send(&mut self) -> Result { + loop { + // prepare the client to send another request + // self.reset(); + // send off the request + self.perform().map_err(Error::SendError)?; + // deserialize response + let response: Response = serde_json::from_slice(self.get_ref().0.as_slice()) + .map_err(Error::DeserializeError)?; + let response = response.into_result()?; + + // reached the end of the logs, break out. + if self.newest == response.newest { + return Err(Error::NotFound(self.hash.to_string())); + } + + // update the newest cursor seen + //self.newest = response.newest.clone(); + + // search for the event in the response and return + // it if found. Else request the next chunk of results + if let Some(result) = parse(response, self.hash) { + return Ok(result); + } + } + } + + /// Reset the client so that it can send it's request again. + /// Used if the event being searched for is not found yet. + fn reset(&mut self) { + self.inner.reset(); + let url = self.url.clone(); + self.url(url).unwrap(); + self.post(true).unwrap(); + + // look only at events after the newest seen + self.request.params.after = self.newest.clone(); + // craft the body of the request + let request_body = serde_json::to_string(&self.request).unwrap(); + println!("{}", &request_body); + 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(); + } + } + + /// 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( + filter: Query, + tx_hash: &str, + ) -> Result { + std::thread::sleep(std::time::Duration::from_secs(5)); + let url = SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + JSONRPC_PORT, + ) + .to_string(); + // craft the body of the request + let request = Request::from(EventParams::new( + filter, + 50, + std::time::Duration::from_secs(60), + )); + // construct a curl client + let mut client = Client::new(&url, request, tx_hash); + // perform the request + client.send() + } + + #[cfg(test)] + mod test_rpc_types { + use serde_json::json; + + 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, + "result":{ + "items": [{ + "cursor":"16f1b066717b4261-0060", + "event":"NewRoundStep", + "data":{ + "type":"tendermint/event/RoundState", + "value":{ + "height":"17416", + "round":0, + "step":"RoundStepCommit" + } + } + }], + "more":true, + "oldest":"16f1b065029b23d0-0001", + "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" + }), + } + }] + ) + } + } +} + +#[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 95012c1d83..f9ecd95433 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -9,20 +9,15 @@ use anoma::types::address::{xan as m1t, Address}; use anoma::types::governance::{OfflineProposal, Proposal}; 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; use anoma::types::transaction::nft::{CreateNft, MintNft}; -use anoma::types::transaction::{ - hash_tx, pos, Fee, InitAccount, InitValidator, UpdateVp, WrapperTx, -}; +use anoma::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use anoma::types::{address, token}; use anoma::{ledger, vm}; use async_std::io::{self, WriteExt}; use borsh::BorshSerialize; use itertools::Either::*; -use jsonpath_lib as jsonpath; -use serde::Serialize; #[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; #[cfg(feature = "ABCI")] @@ -40,14 +35,17 @@ use tendermint_rpc_abci::query::{EventType, Query}; #[cfg(feature = "ABCI")] use tendermint_rpc_abci::{Client, HttpClient}; -use super::{rpc, signing}; +use super::rpc; +use super::signing::sign_tx; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; +use crate::client::signing::find_keypair; +use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::client::tendermint_websocket_client::{ - Error, TendermintWebsocketClient, WebSocketAddress, + Error as WsError, TendermintWebsocketClient, WebSocketAddress, }; #[cfg(not(feature = "ABCI"))] -use crate::node::ledger::events::{Attributes, EventType as TmEventType}; +use crate::client::tm_jsonrpc_client::{fetch_event, Error}; use crate::node::ledger::tendermint_node; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -63,21 +61,6 @@ const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; const VP_NFT: &str = "vp_nft.wasm"; -/// Data needed for broadcasting a tx and -/// monitoring its progress on chain -/// -/// Txs may be either a dry run or else -/// they should be encrypted and included -/// in a wrapper. -pub enum TxBroadcastData { - DryRun(Tx), - Wrapper { - tx: Tx, - wrapper_hash: String, - decrypted_hash: Option, - }, -} - pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); let data = args.data_path.map(|data_path| { @@ -539,7 +522,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { if args.offline { let signer = ctx.get(&signer); - let signing_key = signing::find_keypair( + let signing_key = find_keypair( &mut ctx.wallet, &signer, args.tx.ledger_address.clone(), @@ -790,97 +773,6 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { process_tx(ctx, &args.tx, tx, Some(default_signer)).await; } -/// Sign a transaction with a given signing key or public key of a given signer. -/// If no explicit signer given, use the `default`. If no `default` is given, -/// panics. -/// -/// If this is not a dry run, the tx is put in a wrapper and returned along with -/// hashes needed for monitoring the tx on chain. -/// -/// If it is a dry run, it is not put in a wrapper, but returned as is. -async fn sign_tx( - mut ctx: Context, - tx: Tx, - args: &args::Tx, - default: Option<&WalletAddress>, -) -> (Context, TxBroadcastData) { - let (tx, keypair) = if let Some(signing_key) = &args.signing_key { - let signing_key = ctx.get_cached(signing_key); - (tx.sign(&signing_key), signing_key) - } else if let Some(signer) = args.signer.as_ref().or(default) { - let signer = ctx.get(signer); - let signing_key = signing::find_keypair( - &mut ctx.wallet, - &signer, - args.ledger_address.clone(), - ) - .await; - (tx.sign(&signing_key), signing_key) - } else { - panic!( - "All transactions must be signed; please either specify the key \ - or the address from which to look up the signing key." - ); - }; - let epoch = rpc::query_epoch(args::Query { - ledger_address: args.ledger_address.clone(), - }) - .await; - let broadcast_data = if args.dry_run { - TxBroadcastData::DryRun(tx) - } else { - sign_wrapper(&ctx, args, epoch, tx, &keypair).await - }; - (ctx, broadcast_data) -} - -/// Create a wrapper tx from a normal tx. Get the hash of the -/// wrapper and its payload which is needed for monitoring its -/// progress on chain. -async fn sign_wrapper( - ctx: &Context, - args: &args::Tx, - epoch: Epoch, - tx: Tx, - keypair: &common::SecretKey, -) -> TxBroadcastData { - let tx = { - WrapperTx::new( - Fee { - amount: args.fee_amount, - token: ctx.get(&args.fee_token), - }, - keypair, - epoch, - args.gas_limit.clone(), - tx, - // TODO: Actually use the fetched encryption key - Default::default(), - ) - }; - - // 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() - }; - // 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 - }; - TxBroadcastData::Wrapper { - tx: tx - .sign(keypair) - .expect("Wrapper tx signing keypair should be correct"), - wrapper_hash, - decrypted_hash, - } -} - /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( @@ -923,7 +815,14 @@ async fn process_tx( match result { Right(Ok(result)) => (ctx, result.initialized_accounts), Left(Ok(_)) => (ctx, Vec::default()), - Right(Err(err)) | Left(Err(err)) => { + Right(Err(err)) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } + Left(Err(err)) => { eprintln!( "Encountered error while broadcasting transaction: {}", err @@ -1000,7 +899,7 @@ async fn save_initialized_accounts( pub async fn broadcast_tx( address: TendermintAddress, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result { let (tx, wrapper_tx_hash, _decrypted_tx_hash) = match to_broadcast { TxBroadcastData::Wrapper { tx, @@ -1017,7 +916,7 @@ pub async fn broadcast_tx( let response = wrapper_tx_subscription .broadcast_tx_sync(tx.to_bytes().into()) .await - .map_err(|err| Error::Response(format!("{:?}", err)))?; + .map_err(|err| WsError::Response(format!("{:?}", err)))?; wrapper_tx_subscription.close(); @@ -1034,7 +933,7 @@ pub async fn broadcast_tx( println!("Transaction hash: {:?}", wrapper_tx_hash); Ok(response) } else { - Err(Error::Response(response.log.to_string())) + Err(WsError::Response(response.log.to_string())) } } @@ -1046,6 +945,65 @@ 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, +) -> Result { + // the data for finding the relevant events + 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"), + }; + + // the filters for finding the relevant events + let wrapper_query = Query::from(EventType::NewBlock) + .and_eq("accepted.hash", wrapper_hash.as_str()); + let tx_query = Query::from(EventType::NewBlock) + .and_eq("applied.hash", decrypted_hash.as_ref().unwrap().as_str()); + + // broadcast the tx + if let Err(err) = broadcast_tx(address, &to_broadcast).await { + eprintln!("Encountered error while broadcasting transaction: {}", err); + safe_exit(1) + } + + // get the event for the wrapper tx + let response = fetch_event(wrapper_query, wrapper_hash.as_str()).await?; + println!( + "Transaction accepted with result: {}", + serde_json::to_string_pretty(&response).unwrap() + ); + + // The transaction is now on chain. We wait for it to be decrypted + // and applied + if response.code == 0.to_string() { + let response = + fetch_event(tx_query, decrypted_hash.as_ref().unwrap().as_str()) + .await?; + println!( + "Transaction applied with result: {}", + serde_json::to_string_pretty(&response).unwrap() + ); + Ok(response) + } else { + 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, @@ -1067,60 +1025,13 @@ pub async fn submit_tx( // // Note that the `applied.hash` key comes from a custom event // created by the shell - #[cfg(not(feature = "ABCI"))] - let query_key = "accepted.hash"; - #[cfg(feature = "ABCI")] - let query_key = "applied.hash"; let query = Query::from(EventType::NewBlock) - .and_eq(query_key, wrapper_hash.as_str()); + .and_eq("applied.hash", wrapper_hash.as_str()); wrapper_tx_subscription.subscribe(query)?; - // If we are using ABCI++, we also subscribe to the event emitted - // when the encrypted payload makes its way onto the blockchain - #[cfg(not(feature = "ABCI"))] - let mut decrypted_tx_subscription = { - let mut decrypted_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - None, - )?; - let query = Query::from(EventType::NewBlock) - .and_eq("applied.hash", _decrypted_hash.as_ref().unwrap().as_str()); - decrypted_tx_subscription.subscribe(query)?; - decrypted_tx_subscription - }; // Broadcast the supplied transaction broadcast_tx(address, &to_broadcast).await?; - #[cfg(not(feature = "ABCI"))] - let parsed = { - let parsed = parse( - wrapper_tx_subscription.receive_response()?, - TmEventType::Accepted, - wrapper_hash, - ); - println!( - "Transaction accepted with result: {}", - serde_json::to_string_pretty(&parsed).unwrap() - ); - // The transaction is now on chain. We wait for it to be decrypted - // and applied - if parsed.code == 0.to_string() { - let parsed = parse( - decrypted_tx_subscription.receive_response()?, - TmEventType::Applied, - _decrypted_hash.as_ref().unwrap(), - ); - println!( - "Transaction applied with result: {}", - serde_json::to_string_pretty(&parsed).unwrap() - ); - Ok(parsed) - } else { - Ok(parsed) - } - }; - - #[cfg(feature = "ABCI")] let parsed = { let parsed = TxResponse::find_tx( wrapper_tx_subscription.receive_response()?, @@ -1135,145 +1046,5 @@ pub async fn submit_tx( wrapper_tx_subscription.unsubscribe()?; wrapper_tx_subscription.close(); - #[cfg(not(feature = "ABCI"))] - { - decrypted_tx_subscription.unsubscribe()?; - decrypted_tx_subscription.close(); - } - parsed } - -#[derive(Debug, Serialize)] -pub struct TxResponse { - pub info: String, - pub log: String, - pub height: String, - pub hash: String, - pub code: String, - pub gas_used: String, - pub initialized_accounts: Vec
, -} - -/// Parse the JSON payload received from a subscription -/// -/// Searches for custom events emitted from the ledger and converts -/// them back to thin wrapper around a hashmap for further parsing. -#[cfg(not(feature = "ABCI"))] -fn parse( - json: serde_json::Value, - event_type: TmEventType, - tx_hash: &str, -) -> TxResponse { - let mut selector = jsonpath::selector(&json); - let mut event = - selector(&format!("$.events.[?(@.type=='{}')]", event_type)) - .unwrap() - .iter() - .filter_map(|event| { - let attrs = Attributes::from(*event); - match attrs.get("hash") { - Some(hash) if hash == tx_hash => Some(attrs), - _ => None, - } - }) - .collect::>() - .remove(0); - - let info = event.take("info").unwrap(); - let log = event.take("log").unwrap(); - let height = event.take("height").unwrap(); - let hash = event.take("hash").unwrap(); - let code = event.take("code").unwrap(); - let gas_used = event.take("gas_used").unwrap_or_else(|| String::from("0")); - let initialized_accounts = event.take("initialized_accounts"); - let initialized_accounts = match initialized_accounts { - Some(values) => serde_json::from_str(&values).unwrap(), - _ => vec![], - }; - TxResponse { - info, - log, - height, - hash, - code, - gas_used, - initialized_accounts, - } -} - -impl TxResponse { - pub fn find_tx(json: serde_json::Value, tx_hash: &str) -> Self { - 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 { - if let Ok(hash) = - selector(&format!("$.events.['{}.hash'][{}]", evt_key, index)) - { - let hash = hash[0].clone(); - if hash == tx_hash_json { - break hash; - } else { - index += 1; - } - } else { - eprintln!( - "Couldn't find tx with hash {} in the event string {}", - tx_hash, json - ); - safe_exit(1) - } - }; - let info = - selector(&format!("$.events.['{}.info'][{}]", evt_key, index)) - .unwrap(); - let log = selector(&format!("$.events.['{}.log'][{}]", evt_key, index)) - .unwrap(); - let height = - selector(&format!("$.events.['{}.height'][{}]", evt_key, index)) - .unwrap(); - let code = - selector(&format!("$.events.['{}.code'][{}]", evt_key, index)) - .unwrap(); - let gas_used = - selector(&format!("$.events.['{}.gas_used'][{}]", evt_key, index)) - .unwrap(); - let initialized_accounts = selector(&format!( - "$.events.['{}.initialized_accounts'][{}]", - evt_key, index - )); - let initialized_accounts = match initialized_accounts { - Ok(values) if !values.is_empty() => { - // In a response, the initialized accounts are encoded as e.g.: - // ``` - // "applied.initialized_accounts": Array([ - // String( - // "[\"atest1...\"]", - // ), - // ]), - // ... - // So we need to decode the inner string first ... - let raw: String = - serde_json::from_value(values[0].clone()).unwrap(); - // ... and then decode the vec from the array inside the string - serde_json::from_str(&raw).unwrap() - } - _ => vec![], - }; - TxResponse { - info: serde_json::from_value(info[0].clone()).unwrap(), - log: serde_json::from_value(log[0].clone()).unwrap(), - height: serde_json::from_value(height[0].clone()).unwrap(), - hash: serde_json::from_value(hash).unwrap(), - code: serde_json::from_value(code[0].clone()).unwrap(), - gas_used: serde_json::from_value(gas_used[0].clone()).unwrap(), - initialized_accounts, - } - } -} diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 2a80953290..c38a850aac 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::convert::TryFrom; use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; @@ -9,6 +10,7 @@ use borsh::BorshSerialize; use tendermint_proto::abci::EventAttribute; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::EventAttribute; +use thiserror::Error; /// Custom events that can be queried from Tendermint /// using a websocket client @@ -195,31 +197,52 @@ impl Attributes { } } -impl From<&serde_json::Value> for Attributes { - fn from(json: &serde_json::Value) -> Self { +#[derive(Error, Debug)] +pub enum Error { + #[error("Json missing `attributes` field")] + MissingAttributes, + #[error("Attributes missing key: {0}")] + MissingKey(String), + #[error("Attributes missing value: {0}")] + MissingValue(String), +} + +impl TryFrom<&serde_json::Value> for Attributes { + type Error = Error; + + fn try_from(json: &serde_json::Value) -> Result { let mut attributes = HashMap::new(); let attrs: Vec = serde_json::from_value( json.get("attributes") - .expect("Tendermint event missing attributes") + .ok_or(Error::MissingAttributes)? .clone(), ) .unwrap(); + for attr in attrs { attributes.insert( serde_json::from_value( attr.get("key") - .expect("Attributes JSON missing key") + .ok_or_else(|| { + Error::MissingKey( + serde_json::to_string(&attr).unwrap(), + ) + })? .clone(), ) .unwrap(), serde_json::from_value( attr.get("value") - .expect("Attributes JSON missing value") + .ok_or_else(|| { + Error::MissingValue( + serde_json::to_string(&attr).unwrap(), + ) + })? .clone(), ) .unwrap(), ); } - Attributes(attributes) + Ok(Attributes(attributes)) } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1550941d9e..bd72edce22 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -47,13 +47,13 @@ use borsh::BorshSerialize; 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::abci::response_verify_vote_extension::VerifyStatus; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::public_key; #[cfg(not(feature = "ABCI"))] use tendermint_proto::types::ConsensusParams; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index bdc2572514..dbe52496c8 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -4,9 +4,12 @@ use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; +#[cfg(feature = "ABCI")] use anoma::types::hash::Hash; -use anoma::types::transaction::hash_tx; +#[cfg(feature = "ABCI")] use anoma::types::storage::BlockHash; +#[cfg(feature = "ABCI")] +use anoma::types::transaction::hash_tx; use futures::future::FutureExt; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestBeginBlock; @@ -24,7 +27,6 @@ use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; - /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used /// by tendermint and the shell's interface. @@ -73,9 +75,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 + let bytes: Vec = self + .processed_txs .iter() .map(|processed| processed.tx.clone()) .flatten() diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 7170b3f588..85cee2b245 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -379,6 +379,16 @@ async fn update_tendermint_config( config.instrumentation.namespace = tendermint_config.instrumentation_namespace; + // setup the events log + #[cfg(not(feature = "ABCI"))] + { + // keep events for one minute + config.rpc.event_log_window_size = + std::time::Duration::from_secs(59).into(); + // we do not limit the size of the events log + config.rpc.event_log_max_items = 0; + } + let mut file = OpenOptions::new() .write(true) .truncate(true) @@ -396,8 +406,7 @@ async fn write_tm_genesis( home_dir: impl AsRef, chain_id: ChainId, genesis_time: DateTimeUtc, - #[cfg(not(feature = "ABCI"))] - config: &config::Tendermint, + #[cfg(not(feature = "ABCI"))] config: &config::Tendermint, ) { let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("genesis.json"); @@ -420,7 +429,8 @@ async fn write_tm_genesis( .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() diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 31324461ca..177092b600 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -28,7 +28,8 @@ use super::gossip::rpc::matchmakers::{ }; use crate::cli::args; use crate::client::rpc; -use crate::client::tx::{broadcast_tx, TxBroadcastData}; +use crate::client::tendermint_rpc_types::TxBroadcastData; +use crate::client::tx::broadcast_tx; use crate::{cli, config, wasm_loader}; /// Run a matchmaker diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 9c834296cd..e4d882cc46 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -576,11 +576,9 @@ where Some(h) => Ok(TmConsensusState { root: CommitmentRoot::from_bytes(h.hash.as_slice()), timestamp: h.time.try_into().unwrap(), - next_validators_hash: h - .next_validators_hash - .to_vec() - .try_into() - .unwrap(), + next_validators_hash: tendermint::Hash::Sha256( + *h.next_validators_hash, + ), } .wrap_any()), None => Err(Ics02Error::missing_raw_header()), diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index de4c6a367a..43c4e134a2 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -288,6 +288,17 @@ impl From for Error { } } +/// A dummy header used for testing +#[cfg(any(feature = "test", feature = "testing"))] +pub fn get_dummy_header() -> crate::types::storage::Header { + use crate::tendermint::time::Time as TmTime; + crate::types::storage::Header { + hash: crate::types::hash::Hash([0; 32]), + time: TmTime::now().try_into().unwrap(), + next_validators_hash: crate::types::hash::Hash([0; 32]), + } +} + #[cfg(test)] mod tests { use core::time::Duration; @@ -334,9 +345,9 @@ mod tests { use crate::ibc_proto::cosmos::base::v1beta1::Coin; use prost::Message; use sha2::Digest; - use crate::tendermint::time::Time as TmTime; use crate::tendermint_proto::Protobuf; + use super::get_dummy_header; use super::super::handler::{ commitment_prefix, init_connection, make_create_client_event, make_open_ack_channel_event, make_open_ack_connection_event, @@ -426,14 +437,6 @@ mod tests { (storage, write_log) } - fn get_dummy_header() -> crate::types::storage::Header { - crate::types::storage::Header { - hash: crate::types::hash::Hash([0; 32]), - time: TmTime::now().try_into().unwrap(), - next_validators_hash: crate::types::hash::Hash([0; 32]), - } - } - fn get_connection_id() -> ConnectionId { ConnectionId::new(0) } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index e7d6e37cf5..2603f7210c 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1555,10 +1555,11 @@ where .map_err(TxRuntimeError::StorageError)?; Ok(match header { Some(h) => { - let time = - h.time.to_rfc3339() - .try_to_vec() - .map_err(TxRuntimeError::EncodingError)?; + let time = h + .time + .to_rfc3339() + .try_to_vec() + .map_err(TxRuntimeError::EncodingError)?; let len: i64 = time .len() .try_into() diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 38f56361a5..8ac4fe16f9 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -54,12 +54,13 @@ pub use anoma::ledger::ibc::storage::{ consensus_state_key, next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; -use anoma::ledger::ibc::vp::{Ibc, IbcToken}; +use anoma::ledger::ibc::vp::{ + get_dummy_header as tm_dummy_header, Ibc, IbcToken, +}; use anoma::ledger::native_vp::{Ctx, NativeVp}; use anoma::ledger::storage::mockdb::MockDB; use anoma::ledger::storage::Sha256Hasher; use anoma::proto::Tx; -use anoma::tendermint::time::Time as TmTime; use anoma::tendermint_proto::Protobuf; use anoma::types::address::{self, Address, InternalAddress}; use anoma::types::ibc::data::FungibleTokenPacketData; @@ -260,14 +261,6 @@ pub fn init_storage() -> (Address, Address) { (token, account) } -pub fn tm_dummy_header() -> anoma::types::storage::Header { - anoma::types::storage::Header { - hash: anoma::types::hash::Hash([0; 32]), - time: TmTime::now().try_into().unwrap(), - next_validators_hash: anoma::types::hash::Hash([0; 32]), - } -} - pub fn prepare_client() -> (ClientId, AnyClientState, HashMap>) { let mut writes = HashMap::new(); From 401d36d55c2be08d4e41061cce9405301fd981ea Mon Sep 17 00:00:00 2001 From: R2D2 Date: Sat, 28 May 2022 21:41:08 +0200 Subject: [PATCH 0007/1995] [feat]: Incorporated the new event log for abci++. This fixed the last e2e test --- Cargo.lock | 265 ++++++++++++------ apps/src/bin/anoma-client/cli.rs | 35 --- apps/src/lib/client/rpc.rs | 2 - apps/src/lib/client/tendermint_rpc_types.rs | 101 ++++--- apps/src/lib/client/tm_jsonrpc_client.rs | 113 ++++---- apps/src/lib/client/tx.rs | 24 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 3 +- shared/src/ledger/ibc/vp/client.rs | 4 +- shared/src/ledger/ibc/vp/mod.rs | 1 + shared/src/types/hash.rs | 10 + tests/src/e2e/ledger_tests.rs | 2 + tests/src/vm_host_env/mod.rs | 12 +- 12 files changed, 314 insertions(+), 258 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff2ae8af42..a78038581f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.6", "once_cell", "version_check 0.9.4", ] @@ -541,7 +541,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.2", + "socket2 0.4.4", "waker-fn", "winapi 0.3.9", ] @@ -1184,9 +1184,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc00842eed744b858222c4c9faf7243aafc6d33f92f96935263ef4d8a41ce21" +checksum = "bf6b561dcf059c85bbe388e0a7b0a1469acb3934cc0cfa148613a830629e3049" dependencies = [ "glob", "libc", @@ -1556,7 +1556,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.2", + "socket2 0.4.4", "winapi 0.3.9", ] @@ -1840,9 +1840,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d916019f70ae3a1faa1195685e290287f39207d38e6dfee727197cffcc002214" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "serde 1.0.137", "signature", @@ -2080,14 +2080,14 @@ dependencies = [ [[package]] name = "file-lock" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f90befe02a5389806504fc9fa78681fe950b7e56940e3a5e1515a7b7b86b35" +checksum = "6d68ace7c2694114c6d6b1047f87f8422cb84ab21e3166728c1af5a77e2e5325" dependencies = [ "cc", "libc", "mktemp", - "nix 0.23.1", + "nix 0.24.1", ] [[package]] @@ -2375,9 +2375,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2528,7 +2528,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.2", "tracing 0.1.34", ] @@ -2670,9 +2670,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 1.1.0", "http", @@ -2727,7 +2727,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.9", - "socket2 0.4.2", + "socket2 0.4.4", "tokio", "tower-service", "tracing 0.1.34", @@ -2965,9 +2965,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" dependencies = [ "autocfg 1.1.0", "hashbrown 0.11.2", @@ -3068,9 +3068,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "jobserver" @@ -3103,9 +3103,9 @@ dependencies = [ [[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 = "kernel32-sys" @@ -3171,9 +3171,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" @@ -3391,7 +3391,7 @@ dependencies = [ "log 0.4.17", "rand 0.8.5", "smallvec 1.8.0", - "socket2 0.4.2", + "socket2 0.4.4", "void", ] @@ -3554,7 +3554,7 @@ dependencies = [ "libc", "libp2p-core", "log 0.4.17", - "socket2 0.4.2", + "socket2 0.4.4", ] [[package]] @@ -3966,6 +3966,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log 0.4.17", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + [[package]] name = "mio-extras" version = "2.0.6" @@ -4159,15 +4171,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" dependencies = [ "bitflags", - "cc", "cfg-if 1.0.0", "libc", - "memoffset", ] [[package]] @@ -4369,9 +4379,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "opaque-debug" @@ -4437,7 +4447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.5", + "getrandom 0.2.6", "subtle 2.4.1", "zeroize", ] @@ -4520,6 +4530,16 @@ dependencies = [ "parking_lot_core 0.8.5", ] +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api 0.4.7", + "parking_lot_core 0.9.3", +] + [[package]] name = "parking_lot_core" version = "0.6.2" @@ -4549,6 +4569,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.13", + "smallvec 1.8.0", + "windows-sys", +] + [[package]] name = "paste" version = "1.0.7" @@ -4629,9 +4662,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "51b305cc4569dd4e8765bab46261f67ef5d4d11a4b6e745100ee5dad8948b46c" dependencies = [ "fixedbitset 0.4.1", "indexmap", @@ -4827,11 +4860,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -4923,7 +4956,7 @@ dependencies = [ "lazy_static 1.4.0", "log 0.4.17", "multimap", - "petgraph 0.6.0", + "petgraph 0.6.1", "prost 0.9.0", "prost-types 0.9.0", "regex", @@ -5143,7 +5176,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.6", ] [[package]] @@ -5246,9 +5279,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -5295,7 +5328,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.6", "redox_syscall 0.2.13", "thiserror", ] @@ -5313,9 +5346,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -5333,9 +5366,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "region" @@ -5616,9 +5649,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "safe-proc-macro2" @@ -5693,12 +5726,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static 1.4.0", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -6005,9 +6038,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -6086,9 +6119,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi 0.3.9", @@ -6245,13 +6278,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -6293,9 +6326,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -6314,7 +6347,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6370,7 +6403,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde 1.0.137", @@ -6396,7 +6429,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", @@ -6422,7 +6455,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6456,14 +6489,14 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.5", + "getrandom 0.2.6", "http", "hyper 0.14.18", "hyper-proxy", @@ -6496,7 +6529,7 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.5", + "getrandom 0.2.6", "http", "hyper 0.14.18", "hyper-proxy", @@ -6522,7 +6555,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#7863f72c0f6e1945536cd69a6aec2e15c3f7f438" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6700,19 +6733,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.16.1" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.7.14", + "mio 0.8.3", "num_cpus", "once_cell", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project-lite 0.2.9", "signal-hook-registry", + "socket2 0.4.4", "tokio-macros", "winapi 0.3.9", ] @@ -6871,9 +6905,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 1.1.0", "futures-core", @@ -6885,9 +6919,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ "bytes 1.1.0", "futures-core", @@ -6929,7 +6963,7 @@ dependencies = [ "prost-derive 0.9.0", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -6964,7 +6998,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.2", "tower-layer", "tower-service", "tracing 0.1.34", @@ -6982,7 +7016,7 @@ dependencies = [ "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tracing 0.1.30", "tracing-tower", @@ -7000,7 +7034,7 @@ dependencies = [ "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.9", + "tokio-util 0.6.10", "tower", "tracing 0.1.30", "tracing-tower", @@ -7311,6 +7345,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -7422,7 +7462,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.5", + "getrandom 0.2.6", ] [[package]] @@ -7539,6 +7579,12 @@ 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.80" @@ -7858,9 +7904,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "40.0.0" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb4f48a8b083dbc50e291e430afb8f524092bb00428957bcc63f49f856c64ac" +checksum = "f882898b8b817cc4edc16aa3692fdc087b356edc8cc0c2164f5b5181e31c3870" dependencies = [ "leb128", "memchr", @@ -7869,9 +7915,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0401b6395ce0db91629a75b29597ccb66ea29950af9fc859f1bb3a736609c76e" +checksum = "48b3b9b3e39e66c7fd3f8be785e74444d216260f491e93369e317ed6482ff80f" dependencies = [ "wast", ] @@ -7977,9 +8023,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 1.4.0", @@ -8035,6 +8081,49 @@ 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 = "winreg" version = "0.6.2" diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 6128f5dfc0..44cab0cf4c 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -1,45 +1,11 @@ //! Anoma client CLI. -use anoma::types::token::Amount; use anoma_apps::cli; use anoma_apps::cli::cmds::*; use anoma_apps::client::{gossip, rpc, tx, utils}; use color_eyre::eyre::Result; pub async fn main() -> Result<()> { - use std::str::FromStr; - let global_args = crate::cli::cli::args::Global { - chain_id: None, - base_dir: std::path::PathBuf::from_str(".anoma").unwrap(), - wasm_dir: None, - mode: Some(anoma_apps::config::TendermintMode::Full), - }; - let ctx = anoma_apps::cli::Context::new(global_args); - let args = anoma_apps::cli::args::TxTransfer { - tx: anoma_apps::cli::args::Tx { - dry_run: false, - force: false, - broadcast_only: false, - ledger_address: tendermint_config::net::Address::Tcp { - peer_id: None, - host: "127.0.0.1".into(), - port: 26657, - }, - initialized_account_alias: None, - fee_amount: Default::default(), - fee_token: anoma_apps::cli::context::WalletAddress::new( - "XAN".into(), - ), - gas_limit: 0.into(), - signing_key: None, - signer: None, - }, - source: anoma_apps::cli::context::WalletAddress::new("Bertha".into()), - target: anoma_apps::cli::context::WalletAddress::new("Albert".into()), - token: anoma_apps::cli::context::WalletAddress::new("XAN".into()), - amount: Amount::from(10100000), - }; - tx::submit_transfer(ctx, args).await; match cli::anoma_client_cli() { cli::AnomaClient::WithContext(cmd_box) => { let (cmd, ctx) = *cmd_box; @@ -50,7 +16,6 @@ pub async fn main() -> Result<()> { tx::submit_custom(ctx, args).await; } Sub::TxTransfer(TxTransfer(args)) => { - println!("{:?}", args); tx::submit_transfer(ctx, args).await; } Sub::TxUpdateVp(TxUpdateVp(args)) => { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index af79b6e09a..2c20a5341f 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1120,7 +1120,6 @@ impl TxEventQuery { } /// Transaction event queries are semantically a subset of general queries - impl From for Query { fn from(tx_query: TxEventQuery) -> Self { match tx_query { @@ -1135,7 +1134,6 @@ impl From for Query { } /// Lookup the full response accompanying the specified transaction event - pub async fn query_tx_response( ledger_address: &TendermintAddress, tx_query: TxEventQuery, diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index c80389ef22..7019cc0427 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -2,17 +2,39 @@ use anoma::proto::Tx; use anoma::types::address::Address; use jsonpath_lib as jsonpath; 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 +#[derive(Error, Debug)] +pub enum Error { + #[error("Invalid address given to JSON RPC client: {0}")] + Address(String), + #[error("Error in sending JSON RPC request to Tendermint: {0}")] + Send(curl::Error), + #[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")] + MalformedJson, + #[error("Received an empty response from Tendermint")] + EmptyResponse, + #[error("Could not deserialize JSON response: {0}")] + Deserialize(serde_json::Error), + #[error("Could not find event for the given hash: {0}")] + NotFound(String), +} + /// Data needed for broadcasting a tx and /// monitoring its progress on chain /// /// Txs may be either a dry run or else /// they should be encrypted and included /// in a wrapper. +#[derive(Clone)] pub enum TxBroadcastData { DryRun(Tx), Wrapper { @@ -119,7 +141,6 @@ mod params { use serde::ser::SerializeTuple; use serde::{Deserialize, Serializer}; - use serde_json::value::RawValue; use tendermint_rpc::query::Query; use super::*; @@ -157,7 +178,7 @@ mod params { /// Struct to help serialize [`EventParams`] #[derive(Serialize)] struct Filter { - filter: String, + query: String, } impl Serialize for EventParams { @@ -167,7 +188,7 @@ mod params { { let mut ser = serializer.serialize_tuple(5)?; ser.serialize_element(&Filter { - filter: self.filter.to_string(), + query: self.filter.to_string(), })?; ser.serialize_element(&self.max_results)?; ser.serialize_element(self.after.0.as_str())?; @@ -192,24 +213,6 @@ mod params { wait_time, } } - - /// Convert this into a list of raw json values to give to the - /// JSON RPC client. - pub fn to_params(&self) -> [Box; 5] { - [ - RawValue::from_string(format!( - "{{\"filter\": \"{}\"}}", - self.filter.to_string() - )) - .unwrap(), - RawValue::from_string(self.max_results.to_string()).unwrap(), - RawValue::from_string(format!("\"{}\"", self.after.0)).unwrap(), - RawValue::from_string(format!("\"{}\"", self.before.0)) - .unwrap(), - RawValue::from_string(self.wait_time.as_nanos().to_string()) - .unwrap(), - ] - } } /// A reply from Tendermint for events matching the given [`EventParams`] @@ -238,7 +241,6 @@ mod params { #[allow(dead_code)] pub cursor: Cursor, /// The event type - #[allow(dead_code)] pub event: String, /// The raw event value pub data: EventData, @@ -259,12 +261,27 @@ mod params { /// Returns none if the event is not found. #[cfg(not(feature = "ABCI"))] pub fn parse(reply: EventReply, tx_hash: &str) -> Option { - println!("{}", serde_json::to_string_pretty(&reply).unwrap()); - let mut event = if let Some(event) = - reply.items.iter().find_map(|event| { - if let Some(attrs) = - Attributes::try_from(&event.data.value).ok() - { + let mut event = reply + .items + .iter() + .filter_map(|event| { + if event.event == *"NewBlockHeader" { + let events: Option> = + event.data.value.get("result_finalize_block").map( + |res| match res.get("events") { + Some(v) => serde_json::from_value(v.clone()) + .unwrap_or_default(), + None => vec![], + }, + ); + events + } else { + None + } + }) + .flatten() + .find_map(|attr| { + if let Ok(attrs) = Attributes::try_from(&attr) { match attrs.get("hash") { Some(hash) if hash == tx_hash => Some(attrs), _ => None, @@ -272,11 +289,7 @@ mod params { } else { None } - }) { - event - } else { - return None; - }; + })?; let info = event.take("info").unwrap(); let log = event.take("log").unwrap(); @@ -312,7 +325,7 @@ mod params { #[test] fn test_serialize_event_params() { let params = EventParams { - filter: Query::from(EventType::NewBlock), + filter: Query::from(EventType::NewBlockHeader), max_results: 5, after: Cursor("16CCC798FB5F4670-0123".into()), before: Default::default(), @@ -320,27 +333,9 @@ mod params { }; assert_eq!( serde_json::to_string(¶ms).expect("Test failed"), - r#"[{"filter":"tm.event = 'NewBlock'"},5,"16CCC798FB5F4670-0123","",59000000000]"# + r#"[{"query":"tm.event = 'NewBlockHeader'"},5,"16CCC798FB5F4670-0123","",59000000000]"# ) } - - /// Test that [`EventParams`] are parsed into a params array correctly - #[test] - fn test_to_params() { - let params = EventParams { - filter: Query::from(EventType::NewBlock), - max_results: 5, - after: Cursor("16CCC798FB5F4670-0123".into()), - before: Default::default(), - wait_time: Duration::from_secs(59), - }; - let args = params.to_params(); - assert_eq!(args[0].get(), r#"{"filter": "tm.event = 'NewBlock'"}"#); - assert_eq!(args[1].get(), "5"); - assert_eq!(args[2].get(), "\"16CCC798FB5F4670-0123\""); - assert_eq!(args[3].get(), "\"\""); - assert_eq!(args[4].get(), "59000000000"); - } } } diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs index 736af0c072..2c03cd96ae 100644 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -1,33 +1,41 @@ #[cfg(not(feature = "ABCI"))] mod tm_jsonrpc { - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + 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 thiserror::Error; use crate::client::tendermint_rpc_types::{ - parse, Cursor, EventParams, EventReply, TxResponse, + parse, Error, EventParams, EventReply, TxResponse, }; - const JSONRPC_PORT: u16 = 26657; + pub struct JsonRpcAddress<'a> { + host: &'a str, + port: u16, + } + + impl<'a> TryFrom<&'a TendermintAddress> for JsonRpcAddress<'a> { + type Error = Error; - #[derive(Error, Debug)] - pub enum Error { - #[error("Error in sending JSON RPC request to Tendermint: {0}")] - SendError(curl::Error), - #[error("Received an error response from Tendermint: {0:?}")] - RpcError(tendermint_rpc::response_error::ResponseError), - #[error("Received malformed JSON response from Tendermint")] - MalformedJson, - #[error("Received an empty response from Tendermint")] - EmptyResponse, - #[error("Could not deserialize JSON response: {0}")] - DeserializeError(serde_json::Error), - #[error("Could not find event for the given hash: {0}")] - NotFound(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) + } } /// The body of a json rpc request @@ -68,7 +76,7 @@ mod tm_jsonrpc { /// Convert the response into a result type pub fn into_result(self) -> Result { if let Some(e) = self.error { - Err(Error::RpcError(e)) + Err(Error::Rpc(e)) } else if let Some(result) = self.result { Ok(result) } else { @@ -96,8 +104,6 @@ mod tm_jsonrpc { url: &'a str, /// The request body request: Request, - /// The latest event returned; used for paging through the event log - newest: Cursor, /// The hash of the tx whose corresponding event is being searched for. hash: &'a str, } @@ -123,7 +129,6 @@ mod tm_jsonrpc { inner: Easy2::new(Collector::default()), url, request, - newest: Default::default(), hash, }; client.reset(); @@ -131,50 +136,39 @@ mod tm_jsonrpc { } /// Send a request to Tendermint - /// This pages through the event log 20 events at a time - /// until either - /// 1. The event is found and thus returned - /// 2. The end of the log is reached + /// + /// Takes the 10 newest block header events and searches for + /// the relevant event among them. pub fn send(&mut self) -> Result { - loop { - // prepare the client to send another request - // self.reset(); - // send off the request - self.perform().map_err(Error::SendError)?; - // deserialize response - let response: Response = serde_json::from_slice(self.get_ref().0.as_slice()) - .map_err(Error::DeserializeError)?; - let response = response.into_result()?; - - // reached the end of the logs, break out. - if self.newest == response.newest { - return Err(Error::NotFound(self.hash.to_string())); - } - - // update the newest cursor seen - //self.newest = response.newest.clone(); - - // search for the event in the response and return - // it if found. Else request the next chunk of results - if let Some(result) = parse(response, self.hash) { - return Ok(result); + // send off the request + // this loop is here because if commit timeouts + // become too long, sometimes we get back empty responses. + for _ in 0..10 { + if self.perform().is_ok() { + break; } } + + // 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())) } - /// Reset the client so that it can send it's request again. - /// Used if the event being searched for is not found yet. + /// Reset the client fn reset(&mut self) { self.inner.reset(); - let url = self.url.clone(); + let url = self.url; self.url(url).unwrap(); self.post(true).unwrap(); - // look only at events after the newest seen - self.request.params.after = self.newest.clone(); // craft the body of the request let request_body = serde_json::to_string(&self.request).unwrap(); - println!("{}", &request_body); self.post_field_size(request_body.as_bytes().len() as u64) .unwrap(); // update the request and serialize to bytes @@ -188,23 +182,18 @@ mod tm_jsonrpc { /// 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 { - std::thread::sleep(std::time::Duration::from_secs(5)); - let url = SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - JSONRPC_PORT, - ) - .to_string(); // craft the body of the request let request = Request::from(EventParams::new( filter, - 50, + 10, std::time::Duration::from_secs(60), )); // construct a curl client - let mut client = Client::new(&url, request, tx_hash); + let mut client = Client::new(address, request, tx_hash); // perform the request client.send() } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f9ecd95433..63489f9cc5 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -40,12 +40,14 @@ use super::signing::sign_tx; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::find_keypair; +#[cfg(not(feature = "ABCI"))] +use crate::client::tendermint_rpc_types::Error; use crate::client::tendermint_rpc_types::{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, Error}; +use crate::client::tm_jsonrpc_client::{fetch_event, JsonRpcAddress}; use crate::node::ledger::tendermint_node; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -959,11 +961,12 @@ pub async fn submit_tx( } => (tx, wrapper_hash, decrypted_hash), _ => panic!("Cannot broadcast a dry-run transaction"), }; + let url = JsonRpcAddress::try_from(&address)?.to_string(); // the filters for finding the relevant events - let wrapper_query = Query::from(EventType::NewBlock) + let wrapper_query = Query::from(EventType::NewBlockHeader) .and_eq("accepted.hash", wrapper_hash.as_str()); - let tx_query = Query::from(EventType::NewBlock) + let tx_query = Query::from(EventType::NewBlockHeader) .and_eq("applied.hash", decrypted_hash.as_ref().unwrap().as_str()); // broadcast the tx @@ -973,7 +976,8 @@ pub async fn submit_tx( } // get the event for the wrapper tx - let response = fetch_event(wrapper_query, wrapper_hash.as_str()).await?; + let response = + fetch_event(&url, wrapper_query, wrapper_hash.as_str()).await?; println!( "Transaction accepted with result: {}", serde_json::to_string_pretty(&response).unwrap() @@ -982,9 +986,13 @@ pub async fn submit_tx( // The transaction is now on chain. We wait for it to be decrypted // and applied if response.code == 0.to_string() { - let response = - fetch_event(tx_query, decrypted_hash.as_ref().unwrap().as_str()) - .await?; + // get the event for the inner tx + let response = fetch_event( + &url, + tx_query, + decrypted_hash.as_ref().unwrap().as_str(), + ) + .await?; println!( "Transaction applied with result: {}", serde_json::to_string_pretty(&response).unwrap() @@ -1007,7 +1015,7 @@ pub async fn submit_tx( pub async fn submit_tx( address: TendermintAddress, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result { let (_, wrapper_hash, _decrypted_hash) = match &to_broadcast { TxBroadcastData::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 dbe52496c8..ce3696fa9c 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -81,8 +81,7 @@ impl AbcippShim { let bytes: Vec = self .processed_txs .iter() - .map(|processed| processed.tx.clone()) - .flatten() + .flat_map(|processed| processed.tx.clone()) .collect(); hash_tx(bytes.as_slice()) } diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index e4d882cc46..4b89e1ce30 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -576,9 +576,7 @@ where Some(h) => Ok(TmConsensusState { root: CommitmentRoot::from_bytes(h.hash.as_slice()), timestamp: h.time.try_into().unwrap(), - next_validators_hash: tendermint::Hash::Sha256( - *h.next_validators_hash, - ), + next_validators_hash: h.next_validators_hash.into(), } .wrap_any()), None => Err(Ics02Error::missing_raw_header()), diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 43c4e134a2..b6bd15e43b 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -346,6 +346,7 @@ mod tests { use prost::Message; use sha2::Digest; use crate::tendermint_proto::Protobuf; + use crate::tendermint::time::Time as TmTime; use super::get_dummy_header; use super::super::handler::{ diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index c7ae683f25..358c21bce0 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -7,8 +7,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; #[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 @@ -83,3 +87,9 @@ impl From for transaction::Hash { Self::new(hash.0) } } + +impl From for TmHash { + fn from(hash: Hash) -> Self { + TmHash::Sha256(hash.0) + } +} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ec858892fa..cad446dcba 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -932,6 +932,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { for task in tasks.into_iter() { task.join().unwrap()?; } + // Wait to commit a block + ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; Ok(()) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 10d879d402..001b70b84f 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -21,7 +21,9 @@ mod tests { use anoma::ibc::tx_msg::Msg; use anoma::ledger::ibc::handler::IbcActions; - use anoma::ledger::ibc::vp::Error as IbcError; + use anoma::ledger::ibc::vp::{ + get_dummy_header as tm_dummy_header, Error as IbcError, + }; use anoma::proto::{SignedTxData, Tx}; use anoma::tendermint_proto::Protobuf; use anoma::types::key::*; @@ -556,7 +558,7 @@ mod tests { env.storage .begin_block(BlockHash::default(), BlockHeight(2)) .unwrap(); - env.storage.set_header(ibc::tm_dummy_header()).unwrap(); + env.storage.set_header(tm_dummy_header()).unwrap(); // Start an invalid transaction tx_host_env::set(env); @@ -636,7 +638,7 @@ mod tests { env.storage .begin_block(BlockHash::default(), BlockHeight(3)) .unwrap(); - env.storage.set_header(ibc::tm_dummy_header()).unwrap(); + env.storage.set_header(tm_dummy_header()).unwrap(); // Start a transaction to upgrade the client tx_host_env::set(env); @@ -745,7 +747,7 @@ mod tests { // Commit env.commit_tx_and_block(); // set a block header again - env.storage.set_header(ibc::tm_dummy_header()).unwrap(); + env.storage.set_header(tm_dummy_header()).unwrap(); // Start the next transaction for ConnectionOpenAck tx_host_env::set(env); @@ -815,7 +817,7 @@ mod tests { // Commit env.commit_tx_and_block(); // set a block header again - env.storage.set_header(ibc::tm_dummy_header()).unwrap(); + env.storage.set_header(tm_dummy_header()).unwrap(); // Start the next transaction for ConnectionOpenConfirm tx_host_env::set(env); From 31b21413f82180b02aa38197e341a9a11bfbe67b Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 31 May 2022 13:59:05 +0200 Subject: [PATCH 0008/1995] [fix]: Formatting --- apps/src/lib/client/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b55af60502..5fc760da94 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1851,4 +1851,4 @@ pub async fn get_delegators_delegation( } } delegation_addresses -} \ No newline at end of file +} From caf4a984e271e4ab9fcd30f26a5d951ff3f07a69 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 31 May 2022 14:07:03 +0200 Subject: [PATCH 0009/1995] [fix]: Broken intra-doc link fixed --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index bc0739e911..353a039b90 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2,7 +2,7 @@ //! //! 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_proposal`] must be also reverted (unless we can +//! (ABCI++), [`Shell::process_and_decode_proposal`] must be also reverted (unless we can //! simply overwrite them in the next block). //! More info in . mod finalize_block; From 48a85c9971303db3af7c5d2339befb3afb4d275d Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 31 May 2022 14:18:34 +0200 Subject: [PATCH 0010/1995] [fix]: Goddamn formatting --- apps/src/lib/node/ledger/shell/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 353a039b90..9b787a32cb 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -2,8 +2,8 @@ //! //! 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 (unless we can -//! simply overwrite them in the next block). +//! (ABCI++), [`Shell::process_and_decode_proposal`] must be also reverted +//! (unless we can simply overwrite them in the next block). //! More info in . mod finalize_block; mod init_chain; From 89a568c4b6fafc28e923e5d5de23e25166887569 Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Tue, 31 May 2022 13:37:22 +0000 Subject: [PATCH 0011/1995] [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 f656eb1173..aee8ba5b87 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.1abb84a382128d3fe93d51a291c5a7d9b47e14d234dbc6d3bb3298de190828c5.wasm", - "tx_from_intent.wasm": "tx_from_intent.4dc9d3cb4b8792cf58c856046d02f6002f573aadecbf46c80d95f58f9acc41d5.wasm", - "tx_ibc.wasm": "tx_ibc.240be82111286251348198b43d2d7d5277bb40c4aebbfa8cd5743037e255fea8.wasm", - "tx_init_account.wasm": "tx_init_account.2b73e1eba72ba1e17b4caa0d359488ad26f5025b71e87d4cbb97deb17a5754fc.wasm", - "tx_init_nft.wasm": "tx_init_nft.f4041fb0825bfc81d583dbd595fa2bbf4a510a6d83ae292608f3f09ae65eef6b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3a1103d541d403c4130378a8ad142d8775369d5e5f4aba0b27de16479db1e726.wasm", - "tx_init_validator.wasm": "tx_init_validator.a1c4c2ea1efb493f2c7e9317caa25d77036fb6c3d38cf8bd04f9e0b7151f3bb5.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d159c016d23b8bc101d317e4bbb7755ebdb90934bb9d75431aa8ea4af5a13cd5.wasm", - "tx_transfer.wasm": "tx_transfer.c4e71785be2ea33e50657da72a88311e3ef3db76bb62ea42d6415d718fe9e66f.wasm", - "tx_unbond.wasm": "tx_unbond.95c3d79373575ac2c4dbd487d26a2b6fe0095854228cbbaf4cc54571641447c8.wasm", + "tx_bond.wasm": "tx_bond.41e2c388d4faeb392ee0d542cbcc40c53bb3d02387dafd5dee227568017260f2.wasm", + "tx_from_intent.wasm": "tx_from_intent.c9974919cadba3b78bd074e04b159cc279c46854c1eb4c076337210da4d4280c.wasm", + "tx_ibc.wasm": "tx_ibc.dca0fe7cf60b75b2019b61cf40c6015f3e943a67aeb771e506e6b3666ebacab4.wasm", + "tx_init_account.wasm": "tx_init_account.584d69a87d1d30c1eba8ea8b266601455887620a6fa958d3361ef9d63c7059f7.wasm", + "tx_init_nft.wasm": "tx_init_nft.a057b74c9225b45f3140047517961fec3e7a9b5fcb17403d754f2d252e540aae.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.639aaf8d9aac6cbaafe98a0a3044bf4f45be783a0619429b4120946519056ad9.wasm", + "tx_init_validator.wasm": "tx_init_validator.a06d2e84e9f145f6127e1685d14852ac52e4ebcdb441fc3be6e57baa182a2041.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.a4385cc1a7d69c2f0b1178dc6a3ca696b4be7f50b4fcca149a53923c1e91ca96.wasm", + "tx_transfer.wasm": "tx_transfer.025fd6c8fefb0564dfdb72ba190eb9ea8b5e560116cd7523fed4a15f558b6f0a.wasm", + "tx_unbond.wasm": "tx_unbond.1b1a596bb2ec90eaa449354fa085a983b972c42bf083e8c1eddcfaa924de3c33.wasm", "tx_update_vp.wasm": "tx_update_vp.b3c0998d2cae4e2ad7e4e94589d7911db58e27b9ee982cbd96becec6a74b3b31.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0b77d1192b508e17bd7df37f9a8b0b6811a7c45567c4dfe906ba843c3ebf90de.wasm", - "tx_withdraw.wasm": "tx_withdraw.808cb7db08b62e35c6635c0d017b1cfa869b43161e33185e7a16341f3f5b3167.wasm", - "vp_nft.wasm": "vp_nft.6a5a6acc16e405836f5e01d3128e90d89d1dbdd31b8e06047349d1e1eb16b38b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a8cc244c12783aa73b178f9c9022ac279043db3f7ba4baa4c413ba9824947430.wasm", - "vp_token.wasm": "vp_token.d8cd46bc3ef8d98e7983d2c8fbc91722532aa50621495642ee942aa05c545a33.wasm", - "vp_user.wasm": "vp_user.41eb8fb2c109b6ec520b6a8bff213447d1a52daeb8057ace18dfddbbdd861ac2.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.09f1a654ca29163a826c246b50fea8a012207ad28898c51cc6d2c21f6487312b.wasm", + "tx_withdraw.wasm": "tx_withdraw.7905cea246426fdf903e6a9f21493a4ce73a51fef014aeeaf2ce82e68f8be7ea.wasm", + "vp_nft.wasm": "vp_nft.6651f17a81280644ddd0719b90b0680c3c366c00167a48f10226b1a3a6cd8c50.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.81e9e1cc7a98adf8a345388ddd92e0040287841ce4137461746d318bf2a2f7a3.wasm", + "vp_token.wasm": "vp_token.11a89528e431c2071a6bfe3e2727ffc2286c156297bdd2252a8017912513892a.wasm", + "vp_user.wasm": "vp_user.e827d0f6afcfd3dee2a833f7e19d5590c1d58e377a2861435435fa35d95cbad1.wasm" } \ No newline at end of file From d57a74cfbf060b06b6d41e65eedc36f2741eafba Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 1 Jun 2022 11:54:06 +0200 Subject: [PATCH 0012/1995] [docs]: Updated the changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 231d79d798..d1e7f59d7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,20 @@ - Ledger: Change the storage hasher to SHA256 ([#968](https://github.com/anoma/anoma/issues/968)) +- Ledger: Updated the version of Tendermint used for ABCI++ ([#1088](https://github.com/anoma/anoma/pull/1088)) + - Add full support for ProcessProposal and FinalizeBlock + - Updated the shims + - Updated `tendermint-rs`, `ibc-rs`, and `tower-abci` deps + - Updated the proto definitions + - Added Tendermint's new method of a BFT timestamping + - Updated the format of Tendermint's new config + - Fixed booting up the tendermint node in the ledger with correct settings + - Refactored storage to account for the fact that tendermint no longer passes in block headers +- Client: Configured Tendermints new event log and JSON RPC API for events querying ([#1088](https://github.com/anoma/anoma/pull/1088)) + - Added necessary config parameters to our tendermint node's configuration + - Wrote a jsonrpc client for querying tendermint's event logs + - Refactored how txs are submitted in the client when the `ABCI-plus-plus` feature is + set to use jsonrpc calls instead of websockets. ### IMPROVEMENTS From 856e3895b2c1c74f471cb4a60e06685d393cc6a2 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 1 Jun 2022 12:22:06 +0200 Subject: [PATCH 0013/1995] [fix]: Removed unnecessary deps --- Cargo.lock | 47 +++++++++++++++++------------------------------ apps/Cargo.toml | 3 +-- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff33e3c627..0470c2c0cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,6 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", - "bincode", "bit-set", "blake2b-rs", "borsh", @@ -513,17 +512,16 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] @@ -555,15 +553,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" @@ -2116,13 +2105,11 @@ checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", "libz-sys", "miniz_oxide", ] @@ -2965,9 +2952,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg 1.1.0", "hashbrown 0.11.2", @@ -3927,9 +3914,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] @@ -4532,9 +4519,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api 0.4.7", "parking_lot_core 0.9.3", @@ -4662,9 +4649,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b305cc4569dd4e8765bab46261f67ef5d4d11a4b6e745100ee5dad8948b46c" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset 0.4.1", "indexmap", @@ -4956,7 +4943,7 @@ dependencies = [ "lazy_static 1.4.0", "log 0.4.17", "multimap", - "petgraph 0.6.1", + "petgraph 0.6.2", "prost 0.9.0", "prost-types 0.9.0", "regex", @@ -5545,9 +5532,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.23.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" +checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", @@ -6743,7 +6730,7 @@ dependencies = [ "mio 0.8.3", "num_cpus", "once_cell", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", "socket2 0.4.4", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ba031e93ca..f9387b50c1 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -73,7 +73,6 @@ base64 = "0.13.0" bech32 = "0.8.0" blake2b-rs = "0.2.0" borsh = "0.9.0" -bincode = "1.3.3" byte-unit = "4.0.13" byteorder = "1.4.2" # https://github.com/clap-rs/clap/issues/1037 @@ -109,7 +108,7 @@ rand = {version = "0.8", default-features = false} rand_core = {version = "0.6", default-features = false} rayon = "=1.5.1" regex = "1.4.5" -reqwest = {version = "0.11.4", features = ["json"]} +reqwest = "0.11.4" rlimit = "0.5.4" rocksdb = "0.16.0" rpassword = "5.0.1" From 843c96ac52208549f38182e0dddc6988f715c212 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 1 Jun 2022 14:01:24 +0200 Subject: [PATCH 0014/1995] [fix]: Fixed changelogs. Updated dep version in Cargo.lock file --- .../1088-updating-to-latest-abcipp.md | 14 ++++++++++++++ CHANGELOG.md | 14 -------------- Cargo.lock | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 .changelog/unreleased/improvements/1088-updating-to-latest-abcipp.md diff --git a/.changelog/unreleased/improvements/1088-updating-to-latest-abcipp.md b/.changelog/unreleased/improvements/1088-updating-to-latest-abcipp.md new file mode 100644 index 0000000000..3a65bc82cd --- /dev/null +++ b/.changelog/unreleased/improvements/1088-updating-to-latest-abcipp.md @@ -0,0 +1,14 @@ +- Ledger: Updated the version of Tendermint used for ABCI++ ([#1088](https://github.com/anoma/anoma/pull/1088)) + - Add full support for ProcessProposal and FinalizeBlock + - Updated the shims + - Updated `tendermint-rs`, `ibc-rs`, and `tower-abci` deps + - Updated the proto definitions + - Added Tendermint's new method of a BFT timestamping + - Updated the format of Tendermint's new config + - Fixed booting up the tendermint node in the ledger with correct settings + - Refactored storage to account for the fact that tendermint no longer passes in block headers +- Client: Configured Tendermints new event log and JSON RPC API for events querying ([#1088](https://github.com/anoma/anoma/pull/1088)) + - Added necessary config parameters to our tendermint node's configuration + - Wrote a jsonrpc client for querying tendermint's event logs + - Refactored how txs are submitted in the client when the `ABCI-plus-plus` feature is + set to use jsonrpc calls instead of websockets. diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e7f59d7b..231d79d798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,20 +13,6 @@ - Ledger: Change the storage hasher to SHA256 ([#968](https://github.com/anoma/anoma/issues/968)) -- Ledger: Updated the version of Tendermint used for ABCI++ ([#1088](https://github.com/anoma/anoma/pull/1088)) - - Add full support for ProcessProposal and FinalizeBlock - - Updated the shims - - Updated `tendermint-rs`, `ibc-rs`, and `tower-abci` deps - - Updated the proto definitions - - Added Tendermint's new method of a BFT timestamping - - Updated the format of Tendermint's new config - - Fixed booting up the tendermint node in the ledger with correct settings - - Refactored storage to account for the fact that tendermint no longer passes in block headers -- Client: Configured Tendermints new event log and JSON RPC API for events querying ([#1088](https://github.com/anoma/anoma/pull/1088)) - - Added necessary config parameters to our tendermint node's configuration - - Wrote a jsonrpc client for querying tendermint's event logs - - Refactored how txs are submitted in the client when the `ABCI-plus-plus` feature is - set to use jsonrpc calls instead of websockets. ### IMPROVEMENTS diff --git a/Cargo.lock b/Cargo.lock index 0470c2c0cc..8dacf2b776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,16 +512,17 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.1.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" dependencies = [ "async-channel", "async-executor", "async-io", - "async-lock", + "async-mutex", "blocking", "futures-lite", + "num_cpus", "once_cell", ] @@ -553,6 +554,15 @@ 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" From 270854115e5da63b979fac282ae1c8540d14b25b Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 2 Jun 2022 11:21:18 +0200 Subject: [PATCH 0015/1995] [fix]: Fixed a bug in e2e test. Fixed emitting events that don't come from specific txs --- apps/src/lib/node/ledger/events.rs | 14 ++++++++++++++ .../src/lib/node/ledger/shims/abcipp_shim_types.rs | 3 +++ tests/src/e2e/gossip_tests.rs | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 5c19afd301..612da0a7d8 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -13,11 +13,20 @@ use tendermint_proto::abci::EventAttribute; use tendermint_proto_abci::abci::EventAttribute; use thiserror::Error; +/// Indicates if an event is emitted do to +/// an individual Tx or the nature of a finalized block +#[derive(Clone, Debug)] +pub enum EventLevel { + Block, + Tx, +} + /// Custom events that can be queried from Tendermint /// using a websocket client #[derive(Clone, Debug)] pub struct Event { pub event_type: EventType, + pub level: EventLevel, pub attributes: HashMap, } @@ -68,6 +77,7 @@ impl Event { TxType::Wrapper(wrapper) => { let mut event = Event { event_type: EventType::Accepted, + level: EventLevel::Tx, attributes: HashMap::new(), }; event["hash"] = if !cfg!(feature = "ABCI") { @@ -85,6 +95,7 @@ impl Event { TxType::Decrypted(decrypted) => { let mut event = Event { event_type: EventType::Applied, + level: EventLevel::Tx, attributes: HashMap::new(), }; event["hash"] = decrypted.hash_commitment().to_string(); @@ -93,6 +104,7 @@ impl Event { tx @ TxType::Protocol(_) => { let mut event = Event { event_type: EventType::Applied, + level: EventLevel::Tx, attributes: HashMap::new(), }; event["hash"] = hash_tx( @@ -142,6 +154,7 @@ impl From for Event { fn from(ibc_event: IbcEvent) -> Self { Self { event_type: EventType::Ibc(ibc_event.event_type), + level: EventLevel::Tx, attributes: ibc_event.attributes, } } @@ -151,6 +164,7 @@ impl From for Event { fn from(proposal_event: ProposalEvent) -> Self { Self { event_type: EventType::Proposal, + level: EventLevel::Block, attributes: proposal_event.attributes, } } 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 9f0a93fb37..07461583e4 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -292,6 +292,8 @@ pub mod shim { use tower_abci_old::response; use crate::node::ledger::events::Event; + #[cfg(not(feature = "ABCI"))] + use crate::node::ledger::events::EventLevel; #[derive(Debug, Default)] pub struct VerifyHeader; @@ -340,6 +342,7 @@ pub mod shim { tx_results: resp .events .iter() + .filter(|event| matches!(event.level, EventLevel::Tx)) .map(|event| ExecTxResult { code: event .get("code") diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs index ff6e346c51..db37049198 100644 --- a/tests/src/e2e/gossip_tests.rs +++ b/tests/src/e2e/gossip_tests.rs @@ -323,7 +323,7 @@ fn match_intents() -> Result<()> { ))?; // check that the all VPs accept the transaction - ledger.exp_string("all VPs accepted transaction")?; + ledger.exp_string("all VPs accepted apply_tx storage modification")?; Ok(()) } From 85e70431981e6dc0239a8282b55a725c2dc50573 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 2 Jun 2022 11:22:54 +0200 Subject: [PATCH 0016/1995] Update apps/src/lib/node/ledger/shell/finalize_block.rs Co-authored-by: Tomas Zemanovic --- apps/src/lib/node/ledger/shell/finalize_block.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e7e703788a..367a34982e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -446,7 +446,6 @@ where .header .as_ref() .expect("Header must have been set in prepare_proposal."); - let height = self.storage.last_height + 1; let time = header.time; let new_epoch = self .storage From 7b1ce2a321c96c69e78d9870766289cc03b11a05 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 2 Jun 2022 11:23:12 +0200 Subject: [PATCH 0017/1995] Update shared/src/types/storage.rs Co-authored-by: Tomas Zemanovic --- 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 256e1028a6..241aca5cf4 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -182,7 +182,7 @@ pub struct Header { impl Header { /// The number of bytes when this header is encoded pub fn encoded_len(&self) -> usize { - self.try_to_vec().map(|ser| ser.len()).unwrap() + self.try_to_vec().unwrap().len() } } From eae2a2f4495635bedc7f4d1fdacba605e41bac04 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 2 Jun 2022 12:03:13 +0200 Subject: [PATCH 0018/1995] [fix]: Incorporated in review changes --- apps/src/lib/client/tendermint_rpc_types.rs | 4 ++-- apps/src/lib/client/tm_jsonrpc_client.rs | 25 +++++++++++++++------ apps/src/lib/client/tx.rs | 19 +++++++++++----- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 7019cc0427..8fd2a8c396 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -13,8 +13,8 @@ use crate::node::ledger::events::Attributes; pub enum Error { #[error("Invalid address given to JSON RPC client: {0}")] Address(String), - #[error("Error in sending JSON RPC request to Tendermint: {0}")] - Send(curl::Error), + #[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), diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs index 2c03cd96ae..12bd6d45b3 100644 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -13,6 +13,11 @@ mod tm_jsonrpc { 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; + pub struct JsonRpcAddress<'a> { host: &'a str, port: u16, @@ -131,7 +136,7 @@ mod tm_jsonrpc { request, hash, }; - client.reset(); + client.initialize(); client } @@ -143,11 +148,17 @@ mod tm_jsonrpc { // send off the request // this loop is here because if commit timeouts // become too long, sometimes we get back empty responses. - for _ in 0..10 { - if self.perform().is_ok() { - break; + 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 = @@ -160,8 +171,8 @@ mod tm_jsonrpc { .ok_or_else(|| Error::NotFound(self.hash.to_string())) } - /// Reset the client - fn reset(&mut self) { + /// Initialize the curl client from the fields of `Client` + fn initialize(&mut self) { self.inner.reset(); let url = self.url; self.url(url).unwrap(); @@ -189,7 +200,7 @@ mod tm_jsonrpc { // craft the body of the request let request = Request::from(EventParams::new( filter, - 10, + NUM_EVENTS, std::time::Duration::from_secs(60), )); // construct a curl client diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2e75b4505c..bf4824de2f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -54,6 +54,9 @@ use crate::client::tendermint_websocket_client::{ 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"; const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; @@ -1162,15 +1165,17 @@ pub async fn submit_tx( wrapper_hash, decrypted_hash, } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), + TxBroadcastData::DryRun(_) => { + panic!("Cannot broadcast a dry-run transaction") + } }; let url = JsonRpcAddress::try_from(&address)?.to_string(); // the filters for finding the relevant events let wrapper_query = Query::from(EventType::NewBlockHeader) - .and_eq("accepted.hash", wrapper_hash.as_str()); + .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); let tx_query = Query::from(EventType::NewBlockHeader) - .and_eq("applied.hash", decrypted_hash.as_ref().unwrap().as_str()); + .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_ref().unwrap().as_str()); // broadcast the tx if let Err(err) = broadcast_tx(address, &to_broadcast).await { @@ -1202,6 +1207,10 @@ pub async fn submit_tx( ); Ok(response) } else { + tracing::warn!( + "Received an error from the associated wrapper tx: {}", + response.code + ); Ok(response) } } @@ -1234,10 +1243,10 @@ pub async fn submit_tx( // It is better to subscribe to the transaction before it is broadcast // - // Note that the `applied.hash` key comes from a custom event + // 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.hash", wrapper_hash.as_str()); + .and_eq(APPLIED_QUERY_KEY, wrapper_hash.as_str()); wrapper_tx_subscription.subscribe(query)?; // Broadcast the supplied transaction From e3255530e4c8188333a64cd42a7fff9ccf6b6f47 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 19 May 2022 18:59:28 +0200 Subject: [PATCH 0019/1995] rust 1.60.0 --- docker/anoma-build/Dockerfile | 2 +- docker/anoma-wasm/Dockerfile | 4 ++-- rust-nightly-version | 2 +- rust-toolchain.toml | 2 +- vm_env/src/imports.rs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docker/anoma-build/Dockerfile b/docker/anoma-build/Dockerfile index e3df07a807..124d4ee7f4 100644 --- a/docker/anoma-build/Dockerfile +++ b/docker/anoma-build/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu:20.04 -ARG RUST_VERSION=1.58.1 +ARG RUST_VERSION=1.60.0 WORKDIR /var/build ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && apt-get install -y \ diff --git a/docker/anoma-wasm/Dockerfile b/docker/anoma-wasm/Dockerfile index b7112e0f6b..e7dc1b533d 100644 --- a/docker/anoma-wasm/Dockerfile +++ b/docker/anoma-wasm/Dockerfile @@ -1,12 +1,12 @@ # This docker is used for deterministic wasm builds # The version should be matching the version set in wasm/rust-toolchain.toml -FROM rust:1.58.1 +FROM rust:1.60.0 WORKDIR /usr/local/rust/wasm # The version should be matching the version set above -RUN rustup toolchain install 1.58.1 --component rustc cargo rust-std rust-docs rls rust-analysis rustfmt +RUN rustup toolchain install 1.60.0 --component rustc cargo rust-std rust-docs rls rust-analysis rustfmt RUN rustup target add wasm32-unknown-unknown # Download binaryen and verify checksum diff --git a/rust-nightly-version b/rust-nightly-version index 63af764ba6..d6298cbfe9 100644 --- a/rust-nightly-version +++ b/rust-nightly-version @@ -1 +1 @@ -nightly-2022-01-27 +nightly-2022-04-13 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 2952cb0c04..9c96069094 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.58.1" +channel = "1.60.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] diff --git a/vm_env/src/imports.rs b/vm_env/src/imports.rs index 6d405d420d..e472d49d0c 100644 --- a/vm_env/src/imports.rs +++ b/vm_env/src/imports.rs @@ -274,8 +274,8 @@ 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. + // 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 @@ -579,8 +579,8 @@ pub mod vp { 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. + // 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 From aeb566f6a0d2bbf59d2f939b2f5dde76b1cc9f71 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 20 May 2022 12:26:04 +0200 Subject: [PATCH 0020/1995] update rust toolchain for wasm --- wasm/rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/rust-toolchain.toml b/wasm/rust-toolchain.toml index 1658669dd5..c0fd31e981 100644 --- a/wasm/rust-toolchain.toml +++ b/wasm/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.58.1" +channel = "1.60.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-analysis"] From 9f320dff32d166436ac3ba4fd3b38a3926c8ceb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 16:35:30 +0200 Subject: [PATCH 0021/1995] update Rust stable to 1.61.0 and nightly-2022-05-20 --- docker/anoma-build/Dockerfile | 2 +- docker/anoma-wasm/Dockerfile | 4 ++-- rust-nightly-version | 2 +- rust-toolchain.toml | 2 +- wasm/rust-toolchain.toml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/anoma-build/Dockerfile b/docker/anoma-build/Dockerfile index 124d4ee7f4..c85a8aedde 100644 --- a/docker/anoma-build/Dockerfile +++ b/docker/anoma-build/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu:20.04 -ARG RUST_VERSION=1.60.0 +ARG RUST_VERSION=1.61.0 WORKDIR /var/build ENV DEBIAN_FRONTEND noninteractive RUN apt-get update && apt-get install -y \ diff --git a/docker/anoma-wasm/Dockerfile b/docker/anoma-wasm/Dockerfile index e7dc1b533d..1cf0f0bebf 100644 --- a/docker/anoma-wasm/Dockerfile +++ b/docker/anoma-wasm/Dockerfile @@ -1,12 +1,12 @@ # This docker is used for deterministic wasm builds # The version should be matching the version set in wasm/rust-toolchain.toml -FROM rust:1.60.0 +FROM rust:1.61.0 WORKDIR /usr/local/rust/wasm # The version should be matching the version set above -RUN rustup toolchain install 1.60.0 --component rustc cargo rust-std rust-docs rls rust-analysis rustfmt +RUN rustup toolchain install 1.61.0 --component rustc cargo rust-std rust-docs rls rust-analysis rustfmt RUN rustup target add wasm32-unknown-unknown # Download binaryen and verify checksum diff --git a/rust-nightly-version b/rust-nightly-version index d6298cbfe9..e6c378230a 100644 --- a/rust-nightly-version +++ b/rust-nightly-version @@ -1 +1 @@ -nightly-2022-04-13 +nightly-2022-05-20 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9c96069094..f7e58b63ce 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.60.0" +channel = "1.61.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] diff --git a/wasm/rust-toolchain.toml b/wasm/rust-toolchain.toml index c0fd31e981..6ee193d7cb 100644 --- a/wasm/rust-toolchain.toml +++ b/wasm/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.60.0" +channel = "1.61.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-analysis"] From 7328a13243f34a1b18df24d70192188e1c25609f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 16:36:17 +0200 Subject: [PATCH 0022/1995] apps/build: remove unused unit value let-binding --- apps/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/build.rs b/apps/build.rs index 90ebd9fc2e..808f2dd820 100644 --- a/apps/build.rs +++ b/apps/build.rs @@ -35,7 +35,7 @@ fn main() { File::create("./version.rs").expect("cannot write version"); let pre = "pub fn anoma_version() -> &'static str { \""; let post = "\" }"; - let _result = match version_string { + match version_string { Some(version_string) => { version_rs .write_all(pre.as_bytes()) From 5363b2650f653c176b18bd24904ab318ce299d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 17:30:58 +0200 Subject: [PATCH 0023/1995] apps/ledger: remove unused unit value let-binding --- apps/src/lib/node/ledger/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 38217031ba..a010e13526 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -360,11 +360,10 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { sender: abort_send_for_broadcaster, who: "Broadcaster", }; - let res = broadcaster.run(bc_abort_recv).await; + broadcaster.run(bc_abort_recv).await; tracing::info!("Broadcaster is no longer running."); drop(aborter); - res }), bc_abort_send, )) @@ -536,7 +535,7 @@ async fn wait_for_abort( let mut sigterm = signal(SignalKind::terminate()).unwrap(); let mut sighup = signal(SignalKind::hangup()).unwrap(); let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - let _ = tokio::select! { + tokio::select! { signal = tokio::signal::ctrl_c() => { match signal { Ok(()) => tracing::info!("Received interrupt signal, exiting..."), From fa994ac7d012be27f4bd771498e9c94baf6840f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 16:27:30 +0000 Subject: [PATCH 0024/1995] wasm: update checksums for Rust v1.61.0 --- wasm/checksums.json | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 3984a94c53..84ef2e8109 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,21 @@ { - "tx_bond.wasm": "tx_bond.f706f6f697ada02fa3bf01d40dec25ccb8e0dc45b5ea472f2e8a1fb36e69b992.wasm", - "tx_from_intent.wasm": "tx_from_intent.b9ba032cdc6e707ff5c1a7f1a608ca63d4c730ba40cb1dd59c87ca2a390505e6.wasm", - "tx_ibc.wasm": "tx_ibc.803b9cb87f3f10df5bc021fd307478435fec4baf0f14555569e5ab3cc27c516f.wasm", - "tx_init_account.wasm": "tx_init_account.28fc66058ea006e0381329d3d020e0e9865989ea0c0bc553ce5bf47099cd82c2.wasm", - "tx_init_nft.wasm": "tx_init_nft.e7a7f2695dc019fdfc92d94c58dde7cb2ee22de05cc539e47ffa64309f30826d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.da3599a07012f8d7fcf23eec953d6f4691c112f796e80cf9de5f5e01ea2c7425.wasm", - "tx_init_validator.wasm": "tx_init_validator.05a3f08ea92778a831f5777506f920c159d94358e998c0bf22f1f65f3f309b46.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.77ba4f49f20766fd695aa6b50e2390b3c0dc961228bb9b68a314f22679df066e.wasm", - "tx_transfer.wasm": "tx_transfer.ac51a46af5da24a6953cfea0faa564a8f977a1aaffbba3bd92eef8211a7a0a90.wasm", - "tx_unbond.wasm": "tx_unbond.0f90f820258fe803bd914f19eacfd9c07f9e7098e6e0fb89e638689fddb46d7f.wasm", - "tx_update_vp.wasm": "tx_update_vp.3b34d4b0da7709422d70a875db7f0f60e328e086de268cf625b5780e15ff1e11.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.fcaf1d80bd030200c561160483cd19270549a435e8f46cd641930fceecc58019.wasm", - "tx_withdraw.wasm": "tx_withdraw.84fa9ba930597713d1f5bdc8cd78f7d2216065471ea598742db3e61693e46515.wasm", - "vp_nft.wasm": "vp_nft.97ce38cef4522806d037e9c3a2788e1fdd1e2d55c4b668a6596af17286392706.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d9c8be0069d2e5b11c97ac1ca593cbe98dde3e562a0bc36f1987357e3a37eb86.wasm", - "vp_token.wasm": "vp_token.bf30cf29caacd9e105c0e068b6ca4bd4264d16d8d34ea9190a33b5c0c594bd20.wasm", - "vp_user.wasm": "vp_user.7f8470e91bce7acb38121391d16481d3e2394a7fee97dabde1d33318b8f65836.wasm" + "tx_bond.wasm": "tx_bond.d4a9bdca7e9ae33561b2b5faf3546cc81ea749e97dec0165ee094dbbbfe61c8c.wasm", + "tx_from_intent.wasm": "tx_from_intent.aaa7020e6c7f2430ed3e9dc269cbf5afa3dcedcd174adf19e202597038876442.wasm", + "tx_ibc.wasm": "tx_ibc.a1c5a40a64fb0b4c3af5f3e5f882e3e3b20d0a8a10f26702daa0b4bd21d10d55.wasm", + "tx_init_account.wasm": "tx_init_account.ac55bcfd7e216366d8ba58f0e3349d8dec55f894e51331cbb97235d05fa1dae0.wasm", + "tx_init_nft.wasm": "tx_init_nft.187b682a12b56ee0d71597beb249627137dc05342fd2e1c6226c066014ac2c3e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.9d4157594223b1e3b09d1af876889925d9043d8babcb2c6ee1013b8b14e4ebb3.wasm", + "tx_init_validator.wasm": "tx_init_validator.2b53d8411df772944f0845bf563f2e8f74b0f71dd6003b736c861c0e8f6b74a1.wasm", + "tx_masp.wasm": "tx_masp.096006e9509f8fc47257f62e455bad9974228a45b8511e439a27424289c74c9e.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.31c2ed8eb4b94f181b5315885e61813ce32eb2e2c36b5168ce0662d1a479397f.wasm", + "tx_transfer.wasm": "tx_transfer.aa8a212c588bc7690ea2083f3165b76da11cd0fe0771452f07c52a105a065c5e.wasm", + "tx_unbond.wasm": "tx_unbond.631ea36350197a293e98cb89c49652107957d8bb5e34b0a7cf8609e3443d0eb1.wasm", + "tx_update_vp.wasm": "tx_update_vp.23a6a4f18e826c67708b32b98be67c7c241fd1478424196ae47f972c7a1334e0.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.b4bfb16efa7ce5f1cee6026db05a2c9f3a9115c5e53ae5792a3507116224b95f.wasm", + "tx_withdraw.wasm": "tx_withdraw.067a3d923fb25d3d98dbde207258238561ffbd32ed99f7958675948f580f1fd2.wasm", + "vp_masp.wasm": "vp_masp.ba0a6a8a822db8ad5cf922267fa003b4666f2d9d3e43d049c2c1e3b8a1294e59.wasm", + "vp_nft.wasm": "vp_nft.251a6b6510b4d023ccaf384e26b80718164f8cdd7ad54d77bb9f8dc341456d86.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.36efa05a322717d5448d653ac0a41b51a298533bd591d8f1d3fe738c9a2038c7.wasm", + "vp_token.wasm": "vp_token.5cc783ce9f0cd1c2c88c97830903ac86af785df6a11b943b03557b93e6395da0.wasm", + "vp_user.wasm": "vp_user.56aa2659a370fe1dfdd3f808ed5ec3c5b55e045b66d90e5da20a8abbad53cbe8.wasm" } \ No newline at end of file From b32f2174b3ee2c12ddbf4aa5eaa3acdfe9712be3 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Sat, 11 Jun 2022 22:07:09 +0200 Subject: [PATCH 0025/1995] [feat]: Added a new eth fullnode subprocess and a watcher than can parse events relevant to the eth bridge. Still WIP --- Cargo.lock | 582 ++++++++++++++- apps/Cargo.toml | 6 +- apps/src/lib/config/mod.rs | 10 + .../lib/node/ledger/ethereum_node/events.rs | 673 ++++++++++++++++++ apps/src/lib/node/ledger/ethereum_node/mod.rs | 429 +++++++++++ .../node/ledger/ethereum_node/test_tools.rs | 148 ++++ apps/src/lib/node/ledger/mod.rs | 164 +++-- apps/src/lib/node/ledger/shell/mod.rs | 36 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 5 +- 9 files changed, 1992 insertions(+), 61 deletions(-) create mode 100644 apps/src/lib/node/ledger/ethereum_node/events.rs create mode 100644 apps/src/lib/node/ledger/ethereum_node/mod.rs create mode 100644 apps/src/lib/node/ledger/ethereum_node/test_tools.rs diff --git a/Cargo.lock b/Cargo.lock index 8dacf2b776..1cb481a926 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log 0.4.17", + "memchr", + "pin-project-lite 0.2.9", + "tokio", + "tokio-util 0.7.2", +] + +[[package]] +name = "actix-http" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.13.0", + "bitflags", + "bytes 1.1.0", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags 0.3.2", + "local-channel", + "log 0.4.17", + "mime 0.3.16", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", + "rand 0.8.5", + "sha-1 0.10.0", + "smallvec 1.8.0", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite 0.2.9", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log 0.4.17", + "openssl", + "pin-project-lite 0.2.9", + "tokio-openssl", + "tokio-util 0.7.2", +] + +[[package]] +name = "actix-utils" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" +dependencies = [ + "local-waker", + "pin-project-lite 0.2.9", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -155,6 +258,7 @@ dependencies = [ "derivative", "directories", "ed25519-consensus", + "ethabi", "eyre", "ferveo", "ferveo-common", @@ -171,6 +275,7 @@ dependencies = [ "message-io", "num-derive", "num-traits 0.2.15", + "num256", "num_cpus", "once_cell", "orion", @@ -217,6 +322,7 @@ dependencies = [ "tracing 0.1.34", "tracing-log", "tracing-subscriber 0.3.11", + "web30", "websocket", "winapi 0.3.9", ] @@ -367,7 +473,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "paste", "rustc_version 0.3.3", @@ -390,7 +496,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "quote", "syn", @@ -730,6 +836,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c60c44fbf3c8cee365e86b97d706e513b733c4eeb16437b45b88d2fffe889a" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash", + "base64 0.13.0", + "bytes 1.1.0", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log 0.4.17", + "mime 0.3.16", + "openssl", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", + "rand 0.8.5", + "serde 1.0.137", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.65" @@ -831,6 +971,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.9.2" @@ -1010,6 +1162,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "byte-tools" version = "0.3.1" @@ -1074,6 +1232,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "bytestring" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +dependencies = [ + "bytes 1.1.0", +] + [[package]] name = "cache-padded" version = "1.2.0" @@ -1225,6 +1392,24 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clarity" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" +dependencies = [ + "lazy_static 1.4.0", + "num-bigint 0.4.3", + "num-traits 0.2.15", + "num256", + "secp256k1", + "serde 1.0.137", + "serde-rlp", + "serde_bytes", + "serde_derive", + "sha3 0.10.1", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1307,6 +1492,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1723,8 +1914,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn", ] @@ -1968,6 +2161,16 @@ dependencies = [ "log 0.4.17", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +dependencies = [ + "traitobject", + "typeable", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1990,6 +2193,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethabi" +version = "17.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69517146dfab88e9238c00c724fd8e277951c3cc6f22b016d72f422a832213e" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde 1.0.137", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -2053,7 +2300,7 @@ dependencies = [ "itertools 0.10.3", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde 1.0.137", @@ -2101,6 +2348,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.2.0" @@ -2212,6 +2471,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2696,7 +2961,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags", + "language-tags 0.2.2", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -2885,7 +3150,7 @@ dependencies = [ "prost 0.9.0", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2954,6 +3219,44 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde 1.0.137", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -3129,6 +3432,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "0.2.11" @@ -3470,7 +3779,7 @@ dependencies = [ "pin-project 1.0.10", "rand 0.7.3", "salsa20", - "sha3", + "sha3 0.9.1", ] [[package]] @@ -3671,6 +3980,24 @@ dependencies = [ "serde_test", ] +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.3.4" @@ -4231,17 +4558,42 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits 0.2.15", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-complex 0.4.1", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.0", + "num-traits 0.2.15", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.1.0", + "num-integer", "num-traits 0.2.15", ] @@ -4254,6 +4606,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", + "serde 1.0.137", ] [[package]] @@ -4307,6 +4660,18 @@ dependencies = [ "num-traits 0.2.15", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.1.0", + "num-bigint 0.2.6", + "num-integer", + "num-traits 0.2.15", +] + [[package]] name = "num-rational" version = "0.4.0" @@ -4314,7 +4679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg 1.1.0", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", ] @@ -4337,6 +4702,20 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static 1.4.0", + "num 0.4.0", + "num-derive", + "num-traits 0.2.15", + "serde 1.0.137", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -4487,6 +4866,32 @@ dependencies = [ "url 2.2.2", ] +[[package]] +name = "parity-scale-codec" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.137", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-send-wrapper" version = "0.1.0" @@ -4812,6 +5217,19 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -5070,6 +5488,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -5514,6 +5938,16 @@ dependencies = [ "libc", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes 1.1.0", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.16.0" @@ -5563,6 +5997,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -5753,6 +6193,24 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.6.1" @@ -5842,6 +6300,18 @@ dependencies = [ "serde 0.8.23", ] +[[package]] +name = "serde-rlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" +dependencies = [ + "byteorder", + "error", + "num 0.2.1", + "serde 1.0.137", +] + [[package]] name = "serde_bytes" version = "0.11.6" @@ -6012,6 +6482,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -6310,6 +6790,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.38" @@ -6713,6 +7199,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -6811,6 +7306,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -7949,6 +8456,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.18.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2673ff03d2a8e648f806f100ed70ca3ca69db42dad7178501557b70d549dbb1" +dependencies = [ + "awc", + "clarity", + "futures 0.3.21", + "lazy_static 1.4.0", + "log 0.4.17", + "num 0.4.0", + "num256", + "serde 1.0.137", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -8149,6 +8675,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "1.2.0" @@ -8212,3 +8747,32 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zstd" +version = "0.10.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.6+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +dependencies = [ + "cc", + "libc", +] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f9387b50c1..b467ed629c 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -39,7 +39,7 @@ name = "anomaw" path = "src/bin/anoma-wallet/main.rs" [features] -default = ["std", "ABCI"] +default = ["std", "ABCI", "eth-fullnode"] dev = ["anoma/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies @@ -62,6 +62,7 @@ ABCI-plus-plus = [ "anoma/ibc-vp", ] testing = ["dev"] +eth-fullnode = [] [dependencies] anoma = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "rand"]} @@ -83,6 +84,7 @@ curl = "0.4.43" derivative = "2.2.0" directories = "4.0.1" ed25519-consensus = "1.2.0" +ethabi = "17.0.0" ferveo = {git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} eyre = "0.6.5" @@ -96,6 +98,7 @@ libc = "0.2.97" libloading = "0.7.2" libp2p = "0.38.0" message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} +num256 = "0.3.5" num-derive = "0.3.3" num-traits = "0.2.14" num_cpus = "1.13.0" @@ -143,6 +146,7 @@ tower-abci-old = {package = "tower-abci", git = "https://github.com/heliaxdev/to tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} +web30 = "0.18.10" websocket = "0.26.2" winapi = "0.3.9" diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 7c31ef43d8..838821e203 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -44,6 +44,8 @@ pub const FILENAME: &str = "config.toml"; pub const TENDERMINT_DIR: &str = "tendermint"; /// Chain-specific Anoma DB. Nested in chain dirs. pub const DB_DIR: &str = "db"; +/// Websocket address for Ethereum fullnode RPC +pub const ETHEREUM_URL: &str = "ws://127.0.0.1:8546"; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { @@ -107,6 +109,8 @@ pub struct Shell { db_dir: PathBuf, /// Use the [`Ledger::tendermint_dir()`] method to read the value. tendermint_dir: PathBuf, + /// Use the [`Ledger::ethereum_url()`] method to read the value. + ethereum_url: String, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -178,6 +182,7 @@ impl Ledger { tx_wasm_compilation_cache_bytes: None, db_dir: DB_DIR.into(), tendermint_dir: TENDERMINT_DIR.into(), + ethereum_url: ETHEREUM_URL.into(), }, tendermint: Tendermint { rpc_address: SocketAddr::new( @@ -218,6 +223,11 @@ impl Ledger { pub fn tendermint_dir(&self) -> PathBuf { self.shell.tendermint_dir(&self.chain_id) } + + /// Get the websocket url for the Ethereum fullnode + pub fn ethereum_url(&self) -> String { + self.shell.ethereum_url.clone() + } } impl Shell { diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs new file mode 100644 index 0000000000..2f51aaabad --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -0,0 +1,673 @@ +use std::convert::TryInto; +use std::str::FromStr; + +use anoma::types::address::Address; +use anoma::types::token::Amount; +use ethabi::param_type::ParamType; +use ethabi::token::Token; +use ethabi::{decode, encode, Uint}; +use num256::Uint256; + +use super::{Error, Result}; + +pub mod signatures { + pub const TRANSFER_TO_NAMADA_SIG: &str = + "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; + pub const TRANSFER_TO_ERC_SIG: &str = + "TransferToErc(uint256,address[],address[],uint256[],uint32)"; + pub const VALIDATOR_SET_UPDATE_SIG: &str = + "ValidatorSetUpdate(uint256,bytes32,bytes32)"; + pub const NEW_CONTRACT_SIG: &str = "NewContract(string,address)"; + pub const UPGRADED_CONTRACT_SIG: &str = "UpgradedContract(string,address)"; + pub const UPDATE_BRIDGE_WHITELIST_SIG: &str = + "UpdateBridgeWhiteList(uint256,address[],uint256[])"; + pub const SIGNATURES: [&str; 6] = [ + TRANSFER_TO_NAMADA_SIG, + TRANSFER_TO_ERC_SIG, + VALIDATOR_SET_UPDATE_SIG, + NEW_CONTRACT_SIG, + UPGRADED_CONTRACT_SIG, + UPDATE_BRIDGE_WHITELIST_SIG, + ]; + + /// Used to determine which smart contract address + /// a signature belongs to + pub enum SigType { + Bridge, + Governance, + } + + impl From<&str> for SigType { + fn from(sig: &str) -> Self { + match sig { + TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ERC_SIG => SigType::Bridge, + _ => SigType::Governance, + } + } + } +} + +/// Representation of address on Ethereum +#[derive(Clone, Debug, PartialEq)] +pub struct EthAddress(pub [u8; 20]); + +/// A Keccak hash +#[derive(Clone, Debug, PartialEq)] +pub struct KeccakHash(pub [u8; 32]); + +/// An Ethereum event to be processed by the Anoma ledger +pub enum EthereumEvent { + TransfersToNamada(Vec), + TransfersToErc(Vec), + ValidatorSetUpdate { + nonce: Uint, + bridge_validator_hash: KeccakHash, + governance_validator_hash: KeccakHash, + }, + NewContract { + name: String, + address: EthAddress, + }, + UpgradedContract { + name: String, + address: EthAddress, + }, + UpdateBridgeWhitelist { + nonce: Uint, + whitelist: Vec, + }, +} + +/// An event waiting for a certain number of confirmations +/// before being sent to the ledger +pub struct PendingEvent { + confirmations: Uint256, + block_height: Uint256, + pub event: EthereumEvent, +} + +impl PendingEvent { + /// Decodes bytes into an [`EthereumEvent`] based on the signature. + /// This is is turned into a [`PendingEvent`] along with the block + /// height passed in here. + /// + /// If the event contains a confirmations field, + /// this is passed to the corresponding [`PendingEvent`] field, + /// otherwise a default is used. + pub fn decode( + signature: &str, + block_height: Uint256, + data: &[u8], + ) -> Result { + match signature { + signatures::TRANSFER_TO_NAMADA_SIG => { + RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToNamada(txs.transfers), + }) + } + signatures::TRANSFER_TO_ERC_SIG => { + RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToErc(txs.transfers), + }) + } + signatures::VALIDATOR_SET_UPDATE_SIG => { + ValidatorSetUpdate::decode(data).map( + |ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + }| PendingEvent { + confirmations: super::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + }, + }, + ) + } + signatures::NEW_CONTRACT_SIG => RawChangedContract::decode(data) + .map(|RawChangedContract { name, address }| PendingEvent { + confirmations: super::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::NewContract { name, address }, + }), + signatures::UPGRADED_CONTRACT_SIG => RawChangedContract::decode( + data, + ) + .map(|RawChangedContract { name, address }| PendingEvent { + confirmations: super::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpgradedContract { name, address }, + }), + signatures::UPDATE_BRIDGE_WHITELIST_SIG => { + UpdateBridgeWhitelist::decode(data).map( + |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { + confirmations: super::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpdateBridgeWhitelist { + nonce, + whitelist, + }, + }, + ) + } + _ => unreachable!(), + } + } + + /// Check if the minimum number of confirmations has been + /// reached at the input block height. + pub fn is_confirmed(&self, height: &Uint256) -> bool { + &self.confirmations >= height - &self.block_height + } +} + +/// Type of address to transfer to on Anoma +enum TargetAddressType { + Native, + Erc20, +} + +/// Trait for determining target address type +trait TargetAddress { + fn address_type() -> TargetAddressType; + fn into_token(&self) -> Token; +} + +impl TargetAddress for Address { + fn address_type() -> TargetAddressType { + TargetAddressType::Native + } + fn into_token(&self) -> Token { + Token::String(self.encode()) + } +} + +impl TargetAddress for EthAddress { + fn address_type() -> TargetAddressType { + TargetAddressType::Erc20 + } + fn into_token(&self) -> Token { + Token::Address(self.0.into()) + } +} + +/// An event transferring some kind of value from Ethereum to Anoma +pub struct RawTransferToNamada { + amount: Amount, + source: EthAddress, + target: T, +} + +/// A batch of RawTransferToNamadas from an Ethereum event +pub struct RawTransfersToNamada { + transfers: Vec>, + nonce: Uint, + confirmations: u32, +} + +/// Type aliases +pub type TransferToNamada = RawTransferToNamada
; +pub type TransferToErc = RawTransferToNamada; + +/// Event emitted with the validator set changes +struct ValidatorSetUpdate { + nonce: Uint, + bridge_validator_hash: KeccakHash, + governance_validator_hash: KeccakHash, +} + +/// Event indicating a new smart contract has been +/// deployed or upgraded on Ethereum +struct RawChangedContract { + name: String, + address: EthAddress, +} + +/// struct for whitelisting a token from Ethereum. +/// Includes the address of issuing contract and +/// a cap on the max amount of this token allowed to be +/// held by the bridge. +pub struct TokenWhitelist { + token: EthAddress, + cap: Amount, +} + +/// Event for whitelisting new tokens and their +/// rate limits +struct UpdateBridgeWhitelist { + nonce: Uint, + whitelist: Vec, +} + +impl RawTransfersToNamada { + fn decode(data: &[u8]) -> Result { + let name = match T::address_type() { + TargetAddressType::Native => "TransferToNamada", + TargetAddressType::Erc20 => "TransferToErc", + }; + + let [nonce, sources, targets, amounts, confs]: [Token; 5] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + match T::address_type() { + TargetAddressType::Native => { + ParamType::Array(Box::new(ParamType::String)) + } + TargetAddressType::Erc20 => { + ParamType::Array(Box::new(ParamType::Address)) + } + }, + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode(format!( + "{} signature should contain five types", + name + )) + })?; + + let sources = sources.parse_eth_address_array()?; + let targets: Vec = match T::address_type() { + TargetAddressType::Native => targets.parse_address_array()?, + TargetAddressType::Erc20 => targets.parse_eth_address_array()?, + }; + let amounts = amounts.parse_amount_array()?; + if sources.len() != amounts.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ + transfer amounts" + .into(), + )) + } else if targets.len() != sources.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ + target addresses" + .into(), + )) + } else { + Ok(RawTransfersToNamada { + transfers: sources + .into_iter() + .zip(targets.into_iter()) + .zip(amounts.into_iter()) + .map(|((source, target), amount)| RawTransferToNamada { + source, + target, + amount, + }) + .collect(), + nonce: nonce.parse_uint256()?, + confirmations: confs.parse_u32()?, + }) + } + } + + fn encode(self) -> Vec { + let Self { + transfers, + nonce, + confirmations, + } = self; + + let amounts: Vec = transfers.iter() + .map(|RawTransferToNamada{amount, ..}| Token::Uint(u64::from(*amount).into())) + .collect(); + let (sources, targets): (Vec, Vec) = transfers.into_iter() + .map(|RawTransferToNamada{source, target, ..}| + (Token::Address(source.0.into()), target.into_token()) + ) + .unzip(); + encode(&[ + Token::Uint(nonce), + Token::Array(sources), + Token::Array(targets), + Token::Array(amounts), + Token::Uint(confirmations.into()), + ]) + } +} + +impl ValidatorSetUpdate { + fn decode(data: &[u8]) -> Result { + let [nonce, bridge_validator_hash, goverance_validator_hash]: [Token; + 3] = decode( + &[ + ParamType::Uint(256), + ParamType::FixedBytes(32), + ParamType::FixedBytes(32), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "ValidatorSetUpdate signature should contain three types" + .into(), + ) + })?; + + Ok(Self { + nonce: nonce.parse_uint256()?, + bridge_validator_hash: bridge_validator_hash.parse_keccak()?, + governance_validator_hash: goverance_validator_hash + .parse_keccak()?, + }) + } + + fn encode(self) -> Vec { + let ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + } = self; + + encode(&[ + Token::Uint(nonce), + Token::FixedBytes(bridge_validator_hash.0.into()), + Token::FixedBytes(governance_validator_hash.0.into()), + ]) + } +} + +impl RawChangedContract { + fn decode(data: &[u8]) -> Result { + let [name, address]: [Token; 2] = + decode(&[ParamType::String, ParamType::Address], data) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "ContractUpdate signature should contain two types" + .into(), + ) + })?; + + Ok(Self { + name: name.parse_string()?, + address: address.parse_eth_address()?, + }) + } + + fn encode(self) -> Vec { + let RawChangedContract { + name, + address, + } = self; + encode(&[Token::String(name), Token::Address(address.0.into())]) + } +} + +impl UpdateBridgeWhitelist { + fn decode(data: &[u8]) -> Result { + let [nonce, tokens, caps]: [Token; 3] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "UpdatedBridgeWhitelist signature should contain three types" + .into(), + ) + })?; + + let tokens = tokens.parse_eth_address_array()?; + let caps = caps.parse_amount_array()?; + if tokens.len() != caps.len() { + Err(Error::Decode( + "UpdatedBridgeWhitelist received different number of token \ + address and token caps" + .into(), + )) + } else { + Ok(Self { + nonce: nonce.parse_uint256()?, + whitelist: tokens + .into_iter() + .zip(caps.into_iter()) + .map(|(token, cap)| TokenWhitelist { token, cap }) + .collect(), + }) + } + } + + fn encode(self) -> Vec { + let UpdateBridgeWhitelist { + nonce, + whitelist, + } = self; + + let (tokens, caps): (Vec, Vec) = whitelist.into_iter() + .map(| TokenWhitelist{token, cap} | + (Token::Address(token.0.into()), Token::Uint(u64::from(cap).into())) + ) + .unzip(); + encode(&[Token::Uint(nonce), Token::Array(tokens), Token::Array(caps)]) + } +} + +/// Trait to add parsing methods to `Token`, which is a +/// foreign type +trait Parse { + fn parse_eth_address(self) -> Result; + fn parse_address(self) -> Result
; + fn parse_amount(self) -> Result; + fn parse_u32(self) -> Result; + fn parse_uint256(self) -> Result; + fn parse_bool(self) -> Result; + fn parse_string(self) -> Result; + fn parse_keccak(self) -> Result; + fn parse_amount_array(self) -> Result>; + fn parse_eth_address_array(self) -> Result>; + fn parse_address_array(self) -> Result>; + fn parse_string_array(self) -> Result>; +} + +impl Parse for Token { + fn parse_eth_address(self) -> Result { + if let Token::Address(addr) = self { + Ok(EthAddress(addr.0)) + } else { + Err(Error::Decode(format!( + "Expected type `Address`, got {:?}", + self + ))) + } + } + + fn parse_address(self) -> Result
{ + if let Token::String(addr) = self { + Address::from_str(&addr) + .map_err(|err| Error::Decode(format!("{:?}", err))) + } else { + Err(Error::Decode(format!( + "Expected type `String`, got {:?}", + self + ))) + } + } + + fn parse_amount(self) -> Result { + if let Token::Uint(amount) = self { + Ok(Amount::from(amount.as_u64())) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } + } + + fn parse_u32(self) -> Result { + if let Token::Uint(amount) = self { + Ok(amount.as_u32()) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } + } + + fn parse_uint256(self) -> Result { + if let Token::Uint(uint) = self { + Ok(uint) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } + } + + fn parse_bool(self) -> Result { + if let Token::Bool(b) = self { + Ok(b) + } else { + Err(Error::Decode(format!( + "Expected type `bool`, got {:?}", + self + ))) + } + } + + fn parse_string(self) -> Result { + if let Token::String(string) = self { + Ok(string) + } else { + Err(Error::Decode(format!( + "Expected type `String`, got {:?}", + self + ))) + } + } + + fn parse_keccak(self) -> Result { + if let Token::FixedBytes(bytes) = self { + let bytes = bytes.try_into().map_err(Error::Decode( + "Expect 32 bytes for a Keccak hash".into(), + ))?; + Ok(KeccakHash(bytes)) + } else { + Err(Error::Decode(format!( + "Expected type `FixedBytes`, got {:?}", + self + ))) + } + } + + fn parse_amount_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut amounts = vec![]; + for token in array.into_iter() { + let amount = token.parse_amount()?; + amounts.push(amount); + } + Ok(amounts) + } + + fn parse_eth_address_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut addrs = vec![]; + for token in array.into_iter() { + let addr = token.parse_eth_address()?; + addrs.push(addr); + } + Ok(addrs) + } + + fn parse_address_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut addrs = vec![]; + for token in array.into_iter() { + let addr = token.parse_address()?; + addrs.push(addr); + } + Ok(addrs) + } + + fn parse_string_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut strings = vec![]; + for token in array.into_iter() { + let string = token.parse_string()?; + strings.push(string); + } + Ok(strings) + } +} + +#[cfg(test)] +mod test_events { + use super::*; + + /// For each of the basic types, test that roundtrip + /// encoding - decoding is a no-op + #[test] + fn test_round_trips() { + let erc = EthAddress([1; 20]); + let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") + .expect("Test failed"); + let amount = Amount::from(42u64); + let confs = 50u32; + let uint = Uint::from(42u64); + let boolean = true; + let string = String::from("test"); + let keccak = KeccakHash([2; 32]); + + assert_eq!( + decode( + &[ParamType::Address], + encode(&[Token::Address(erc.0.into())]).as_slice()) + .expect("Test failed"), + erc + ) + } +} \ No newline at end of file diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs new file mode 100644 index 0000000000..4cde012f39 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -0,0 +1,429 @@ +pub mod events; +pub mod test_tools; + +use std::ffi::OsString; +use std::sync::Arc; + +use events::{EthAddress, EthereumEvent}; +use thiserror::Error; +use tokio::sync::Mutex; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::oneshot::{channel, Receiver, Sender}; +use tokio::task; +use web30::client::Web3; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Failed to start Ethereum fullnode: {0}")] + StartUp(std::io::Error), + #[error("{0}")] + Runtime(String), + #[error( + "The receiver of the Ethereum relayer messages unexpectedly dropped" + )] + RelayerReceiverDropped, + #[error("Web3 server unexpectedly stopped")] + Web3Server, + #[error( + "Could not read Ethereum network to connect to from env var: {0:?}" + )] + EthereumNetwork(OsString), + #[error("Could not decode Ethereum event: {0}")] + Decode(String), +} + +pub type Result = std::result::Result; + +/// Minimum number of confirmations needed to trust an Ethereum branch +const MIN_CONFIRMATIONS: u64 = 50; + +/// Dummy addresses for smart contracts +const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); +const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); + +/// Run the Ethereum fullnode as well as a relayer +/// that sends RPC streams to the ledger. If either +/// stops or an abort signal is sent, these processes +/// are halted. +pub async fn run( + url: &str, + mut sender: UnboundedSender, + abort_recv: Receiver>, +) -> Result<()> { + // start up the ethereum node. + let mut ethereum_node = EthereumNode::new(url).await?; + let (shutdown_send, shutdown_recv) = tokio::sync::oneshot::channel(); + + let (request, recv) = tokio::sync::mpsc::unbounded_channel(); + + tokio::select! { + // run the ethereum fullnode + status = ethereum_node.wait() => status, + // wait for an abort signal + resp_sender = abort_recv => { + match resp_sender { + Ok(resp_sender) => { + tracing::info!("Shutting down Ethereum fullnode..."); + shutdown_send.send(()).unwrap(); + ethereum_node.kill().await; + resp_sender.send(()).unwrap(); + }, + Err(err) => { + tracing::error!("The Ethereum abort sender has unexpectedly dropped: {}", err); + tracing::info!("Shutting down Ethereum fullnode..."); + shutdown_send.send(()).unwrap(); + ethereum_node.kill().await; + } + } + Ok(()) + } + resp = toki + } +} + +#[cfg(feature = "eth-fullnode")] +/// Tools for running a geth fullnode process +pub mod eth_fullnode { + use std::sync::Arc; + + use tokio::process::{Child, Command}; + use tokio::sync::Mutex; + use web30::client::Web3; + + use super::{Error, Result}; + + /// A handle to a running geth process + pub struct EthereumNode { + process: Child, + } + + /// Read from environment variable which Ethereum + /// network to connect to. Defaults to mainnet if + /// no variable is set. + /// + /// Returns an error if the env var is defined but not + /// a valid unicode + fn get_eth_network() -> Result> { + match std::env::var("ETHEREUM_NETWORK") { + Ok(path) => { + tracing::info!("Connecting to Ethereum network: {}", &path); + Ok(Some(path)) + } + Err(std::env::VarError::NotPresent) => { + tracing::info!("Connecting to Ethereum mainnet"); + Ok(None) + } + Err(std::env::VarError::NotUnicode(msg)) => { + Err(Error::EthereumNetwork(msg)) + } + } + } + + impl EthereumNode { + /// Starts the geth process and returns a handle to it. + /// + /// First looks up which network to connect to from an env var. + /// It then starts the process and waits for it to finish + /// syncing. + pub async fn new(url: &str) -> Result { + // the geth fullnode process + let network = get_eth_network()?; + let args = match &network { + Some(network) => vec![ + "--syncmode", + "snap", + network.as_str(), + "--ws", + "--ws.api", + "eth", + ], + None => vec!["--syncmode", "snap", "--ws", "--ws.api", "eth"], + }; + let ethereum_node = Command::new("geth") + .args(&args) + .kill_on_drop(true) + .spawn() + .map_err(Error::StartUp)?; + tracing::info!("Ethereum fullnode started"); + + // it takes a brief amount of time to open up the websocket on + // geth's end + let client = Arc::new(Mutex::new(Web3::new(url, std::time::Duration::from_secs(5)))); + + loop { + match client.eth_syncing().await { + Ok(true) => {} + Ok(false) => { + tracing::info!("Finished syncing"); + break; + } + Err(err) => { + tracing::error!( + "Encountered an error while syncing: {}", + err + ); + } + } + } + + Ok(Self { + process: ethereum_node, + }) + } + + /// Wait for the process to finish. If it does, + /// return the status. + pub async fn wait(&mut self) -> Result<()> { + match self.process.wait().await { + Ok(status) => { + if status.success() { + Ok(()) + } else { + Err(Error::Runtime(status.to_string())) + } + } + Err(err) => Err(Error::Runtime(err.to_string())), + } + } + + /// Stop the geth process + pub async fn kill(&mut self) { + self.process.kill().await.unwrap(); + } + } +} + +#[cfg(feature = "eth-fullnode")] +pub use eth_fullnode::EthereumNode; +#[cfg(not(feature = "eth-fullnode"))] +pub use test_tools::mock_eth_fullnode::EthereumNode; + +/// Tools for polling the Geth fullnode asynchronously +#[cfg(feature = "eth-fullnode")] +pub mod ethereum_relayer { + use events::{signatures, PendingEvent}; + use num256::Uint256; + use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, UnboundedReceiver}; + use web30::client::Web3; + + use super::*; + + /// Types of requests that can be sent to the Web3 server + #[derive(Clone)] + enum Web3Cmd { + /// Requests the latest block seen by the full node + LatestBlock, + /// Find events with [`MIN_CONFIRMATIONS`] of confirmations + /// and adds them to the pending queue + FetchEvents{ + block_height: Uint256, + signature: &'static str, + }, + /// Request to shutdown the server + Shutdown, + } + + /// A request to the Web3 server. Includes the request body as well + /// as a one shot channel to return the response + struct Web3Request { + /// Request body + request: Web3Cmd, + /// Channel for sending response + reply: Sender + } + + /// A reply from the Web3 server. A return value of None + /// signifies a failure to connect to the underlying full node. + enum Web3Reply { + /// The latest block height + LatestBlock(Option), + /// A set of events from a block + Events(Option>), + /// Acknowledgment of shutdown + Shutdown, + } + + /// Runs a loop that receives requests from an inbound channel. + /// These requests include a channel for sending replies. + /// The server handles the request and sends a reply over the supplied + /// channel. + /// + /// If any channel fails to send, the server shuts down. + pub async fn web3_server( + url: &str, + mint_contract: EthAddress, + governance_contract: EthAddress, + mut channel: UnboundedReceiver, + ) { + let client = Arc::new(Mutex::new(Web3::new(url, std::time::Duration::from_secs(3)))); + // If a none is received, the sending channel has hung up, + // so the server stops. + while let Some( + Web3Request{ + request, + reply + } + ) = channel.recv().await { + // handle request by type + if match request { + Web3Cmd::LatestBlock => reply.send( + Web3Reply::LatestBlock(client + .lock() + .await + .eth_block_number() + .await + .ok()) + ), + Web3Cmd::FetchEvents{block_height, signature} => { + let addr = match signatures::SigType::from(signature) { + signatures::SigType::Bridge => mint_contract.0.clone().into(), + signatures::SigType::Governance => governance_contract.0.clone().into(), + }; + let events = client + .lock() + .await + .check_for_events( + block_height.clone(), + Some(block_height.clone()), + vec![addr], + vec![signature], + ) + .await + .ok() + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + signature, + block_height.clone(), + log.data.0.as_slice(), + ).ok() + }) + .collect::>()} + ); + reply.send( + Web3Reply::Events(events) + ) + } + Web3Cmd::Shutdown => { + reply.send(Web3Reply::Shutdown); + // shutdown the server without waiting to see if + // reply sent successfully. + return ; + } + }.is_err() { + // could not send reply because the receiver hung up, + // so the server stops + return; + } + } + } + + /// Check which events in the queue have reached their + /// required number of confirmations and send them + /// to the ledger. + /// + /// If the ledger's receiver has dropped, this will + /// propagate an error here. + fn process_queue( + latest_block: &Uint256, + pending: &mut Vec, + sender: &mut UnboundedSender + ) -> Result<()> { + let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); + std::mem::swap(&mut pending_tmp, pending); + for item in pending_tmp.into_iter() { + if item.is_confirmed(latest_block) { + sender + .send(item.event) + .map(Error::RelayerReceiverDropped)?; + } else { + pending.push(item); + } + } + Ok(()) + } + + /// Sends a request to the Web3 server and awaits a response. If the server has + /// stopped, returns an error. + fn process_request(request: &UnboundedSender, cmd: Web3Cmd) -> Result { + let (reply, mut recv) = channel(); + request.send( + Web3Request { + request: cmd, + reply, + } + ) + .map_err(|_| Error::Web3Server)?; + loop { + match recv.try_recv() { + Ok(reply) => return Ok(reply), + Err(tokio::sync::oneshot::error::TryRecvError::Closed) => return Err(Error::Web3Server), + _ => {} + } + } + } + + /// Starts a Web3 server that allows this method to talk to the Ethereum + /// full node. + /// + /// Continuously polls for events that are [`MIN_CONFIRMATIONS`] blocks old + /// and when they have met their confirmation target, forwards them to the + /// leder. + /// + /// On receiving a shutdown command, exit with an Ok(()). If the server shutsdown + /// are the ledger hangs up its end of the channel, shuts down Anoma. + pub async fn run_relayer( + url: &str, + mint_contract: EthAddress, + governance_contract: EthAddress, + mut sender: UnboundedSender, + mut shutdown: Receiver<()>, + ) -> Result<()> { + let (request, recv) = unbounded_channel(); + // start the server + + let mut latest_block: Uint256 = Default::default(); + let mut pending: Vec = Vec::new(); + // the main loop to watch for new events + loop { + // get the latest block height + let height = loop { + if let Web3Reply::LatestBlock(Some(height)) = process_request(&request, Web3Cmd::LatestBlock)? { + break height + } + if shutdown.try_recv().is_ok() { + let _ = process_request(&request, Web3Cmd::Shutdown); + return Ok(()) + } + }; + latest_block = height; + + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // get events corresponding to each signature + for sig in signatures::SIGNATURES { + let mut events = loop { + let cmd = Web3Cmd::FetchEvents { + block_height: block_to_check.clone(), + signature: sig + }; + if let Web3Reply::Events(Some(events)) = process_request(&request, cmd.clone())? { + break events; + } + if shutdown.try_recv().is_ok() { + let _ = process_request(&request, Web3Cmd::Shutdown); + return Ok(()) + } + }; + + // add events to queue and forward to ledger if confirmed + pending.append(&mut events); + process_queue(&latest_block, &mut pending, &mut sender)?; + } + } + } + +} + +#[cfg(feature = "eth-fullnode")] +pub use ethereum_relayer::{web3_server, run_relayer}; diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs new file mode 100644 index 0000000000..f1a3940486 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -0,0 +1,148 @@ +use super::*; + +#[cfg(not(feature = "eth-fullnode"))] +/// tools for running a mock ethereum fullnode process +pub mod mock_eth_fullnode { + use anoma::types::hash::Hash; + use tokio::sync::mpsc::UnboundedSender; + + use super::Result; + + pub struct EthereumNode; + + impl EthereumNode { + pub async fn new(_: &str) -> Result { + Ok(Self {}) + } + + pub async fn wait(&mut self) -> Result<()> { + std::future::pending().await + } + + pub async fn kill(&mut self) {} + } +} + +#[cfg(not(feature = "eth-fullnode"))] +pub mod mock_web3_client { + use std::fmt::Debug; + use num256::Uint256; + use tokio::sync::mpsc::{channel, Sender, Receiver}; + use web30::types::Log; + + use super::{Error, Result}; + use super::events::signatures::*; + + /// Commands we can send to the mock client + pub enum TestCmd { + Normal, + Unresponsive, + NewHeight(Uint256), + NewEvent(MockEventType, Vec) + } + + /// The type of events supported + #[derive(PartialEq)] + pub enum MockEventType { + TransferToNamada, + TransferToErc, + ValSetUpdate, + NewContract, + UpgradedContract, + BridgeWhitelist, + } + + /// A mock of a web3 api connected to an ethereum fullnode. + /// It is not connected to a full node and is fully controllable + /// via a channel to allow us to mock different behavior for + /// testing purposes. + pub struct Web3 { + cmd_channel: Receiver, + active: bool, + latest_block_height: Uint256, + events: Vec<(MockEventType, Vec)> + } + + impl Web3 { + /// Return a new client and a separate sender + /// to send in admin commands + pub fn new() -> (Sender, Self) { + // we can only send one command at a time. + let (cmd_sender, cmd_channel) = channel(1); + ( + cmd_sender, + Self { + cmd_channel, + active: true, + latest_block_height: Default::default(), + events: vec![], + } + ) + } + + /// Check and apply new incoming commands + fn check_cmd_channel(&mut self) { + if let Ok(cmd) = self.cmd_channel.try_recv() { + match cmd { + TestCmd::Normal => self.active = true, + TestCmd::Unresponsive => self.active = false, + TestCmd::NewHeight(height) => self.latest_block_height = height, + TestCmd::NewEvent(ty, data) => self.events.push((ty, data)), + } + } + } + + /// Gets the latest block number send in from the + /// command channel if we have not set the client to + /// act unresponsive. + pub async fn eth_block_number(&mut self) -> Result { + self.check_cmd_channel(); + if self.active { + Ok(self.latest_block_height.clone()) + } else { + Err(Error::Runtime("Uh oh, I'm not responding".into())) + } + } + + /// Gets the events (for the appropriate signature) that + /// have been added from the command channel unless the + /// client has not been set to act unresponsive. + pub async fn check_for_events( + &mut self, + _: Uint256, + _: Option, + _: impl Debug, + mut events: Vec<&str>, + ) -> Result> { + self.check_cmd_channel(); + if self.active { + let ty = match events.remove(0) { + TRANSFER_TO_NAMADA_SIG => MockEventType::TransferToNamada, + TRANSFER_TO_ERC_SIG => MockEventType::TransferToErc, + VALIDATOR_SET_UPDATE_SIG => MockEventType::ValSetUpdate, + NEW_CONTRACT_SIG => MockEventType::NewContract, + UPGRADED_CONTRACT_SIG => MockEventType::UpgradedContract, + UPDATE_BRIDGE_WHITELIST_SIG => MockEventType::BridgeWhitelist, + _ => return Ok(vec![]) + }; + let mut logs = vec![]; + let mut events = vec![]; + std::mem::swap(&mut self.events, &mut events); + for (event_ty, data) in events.into_iter() { + if &event_ty == &ty { + logs.push(Log{ + data: data.into(), + ..Default::default() + }); + } else { + self.events.push((event_ty, data)); + } + } + Ok(logs) + } else { + Err(Error::Runtime("Uh oh, I'm not responding".into())) + } + } + } +} + diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7773789362..22df6951c4 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,4 +1,5 @@ mod broadcaster; +mod ethereum_node; pub mod events; pub mod protocol; pub mod rpc; @@ -289,6 +290,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { rocksdb::Cache::new_lru_cache(block_cache_size_bytes as usize).unwrap(); let tendermint_dir = config.tendermint_dir(); + let ethereum_url = config.ethereum_url(); 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(); @@ -302,11 +304,89 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Channel for signalling shut down from the shell or from Tendermint let (abort_send, abort_recv) = tokio::sync::mpsc::unbounded_channel::<&'static str>(); + // Channels for the Ethereum relayer to send new Ethereum block headers + // and smart contract logs to the ledger + let (eth_relayer_sender, mut eth_relayer_receiver) = + tokio::sync::mpsc::unbounded_channel(); // 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 (ethereum_node, broadcaster) = if matches!( + config.tendermint.tendermint_mode, + TendermintMode::Validator + ) { + // Start Ethereum fullnode + // Channel for signalling shut down to Tendermint process + let (eth_abort_send, eth_abort_recv) = + tokio::sync::oneshot::channel::>(); + let abort_send_for_eth = abort_send.clone(); + let ethereum_node = tokio::spawn(async move { + // On panic or exit, the `Drop` of `AbortSender` will send abort + // message + let aborter = Aborter { + sender: abort_send_for_eth, + who: "Ethereum", + }; + + let res = ethereum_node::run( + ðereum_url, + eth_relayer_sender, + eth_abort_recv, + ) + .map_err(Error::Ethereum) + .await; + tracing::info!("Ethereum fullnode is no longer running."); + + drop(aborter); + res + }); + // wait for the Ethereum fullnode to finish syncing. + eth_relayer_receiver.recv().await.unwrap(); + + // Shutdown ethereum_node via a message to ensure that the child process + // is properly cleaned-up. + let (eth_abort_resp_send, eth_abort_resp_recv) = + tokio::sync::oneshot::channel::<()>(); + let abort_send_for_broadcaster = abort_send.clone(); + + // Channel for signalling shut down to broadcaster + let (bc_abort_send, bc_abort_recv) = + tokio::sync::oneshot::channel::<()>(); + + ( + Some(( + ethereum_node, + eth_abort_send, + eth_abort_resp_send, + eth_abort_resp_recv, + )), + Some(( + tokio::spawn(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", + }; + let res = broadcaster.run(bc_abort_recv).await; + tracing::info!("Broadcaster is no longer running."); + + drop(aborter); + res + }), + bc_abort_send, + )), + ) + } else { + (None, None) + }; + // Channel for signalling shut down to Tendermint process let (tm_abort_send, tm_abort_recv) = tokio::sync::oneshot::channel::>(); @@ -333,50 +413,16 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tracing::info!("Tendermint node is no longer running."); drop(aborter); - if res.is_err() { - tracing::error!("{:?}", &res); - } res }); - 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::<()>(); - let abort_send_for_broadcaster = abort_send.clone(); - Some(( - tokio::spawn(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", - }; - let res = broadcaster.run(bc_abort_recv).await; - tracing::info!("Broadcaster is no longer running."); - - drop(aborter); - res - }), - bc_abort_send, - )) - } else { - None - }; - // Construct our ABCI application. let ledger_address = config.shell.ledger_address; let (shell, abci_service) = AbcippShim::new( config, wasm_dir, broadcaster_sender, + eth_relayer_receiver, &db_cache, vp_wasm_compilation_cache, tx_wasm_compilation_cache, @@ -431,26 +477,50 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { } } - 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) - } - 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, ())) + let res = if let ( + Some(( + ethereum_node, + eth_abort_send, + eth_abort_resp_send, + eth_abort_resp_recv, + )), + Some((broadcaster, bc_abort_send)), + ) = (ethereum_node, broadcaster) + { + // Ask to shutdown tendermint node cleanly. Ignore error, which can + // happen if the tendermint_node task has already finished. + if let Ok(()) = eth_abort_send.send(eth_abort_resp_send) { + match eth_abort_resp_recv.await { + Ok(()) => {} + Err(err) => { + tracing::error!( + "Failed to receive a response from Ethereum: {}", + err + ); + } + } } + // request the broadcaster shutdown + let _ = bc_abort_send.send(()); + tokio::try_join!(tendermint_node, ethereum_node, abci, broadcaster) + } else { + // if we are not a validator, the broadcaster service and Ethereum + // fullnode are not active. Thus, we fill in their return values + // with () + tokio::try_join!(tendermint_node, abci) + .map(|results| (results.0, Ok(()), results.1, ())) }; + match res { - Ok((tendermint_res, abci_res, _)) => { + Ok((tendermint_res, eth_res, abci_res, _)) => { // we ignore errors on user-initiated shutdown if aborted { if let Err(err) = tendermint_res { tracing::error!("Tendermint error: {}", err); } + if let Err(err) = eth_res { + tracing::error!("Ethereum error: {}", err); + } if let Err(err) = abci_res { tracing::error!("ABCI error: {}", err); } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9b787a32cb..5802a5d026 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -63,7 +63,7 @@ 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; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; #[cfg(not(feature = "ABCI"))] use tower_abci::{request, response}; #[cfg(feature = "ABCI")] @@ -71,12 +71,13 @@ use tower_abci_old::{request, response}; use super::rpc; use crate::config::{genesis, TendermintMode}; +use crate::node::ledger::ethereum_node::events::EthereumEvent; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{protocol, storage, tendermint_node}; #[allow(unused_imports)] -use crate::wallet::ValidatorData; +use crate::wallet::{ValidatorData, ValidatorKeys}; use crate::{config, wallet}; fn key_to_tendermint( @@ -100,6 +101,8 @@ pub enum Error { GasOverflow, #[error("{0}")] Tendermint(tendermint_node::Error), + #[error("{0}")] + Ethereum(super::ethereum_node::Error), #[error("Server error: {0}")] TowerServer(String), #[error("{0}")] @@ -164,6 +167,7 @@ pub(super) enum ShellMode { Validator { data: ValidatorData, broadcast_sender: UnboundedSender>, + ethereum_recv: UnboundedReceiver, }, Full, Seed, @@ -178,6 +182,23 @@ impl ShellMode { _ => None, } } + + pub fn get_protocol_key(&self) -> Option<&common::SecretKey> { + match &self { + ShellMode::Validator { + data: + ValidatorData { + keys: + ValidatorKeys { + protocol_keypair, .. + }, + .. + }, + .. + } => Some(protocol_keypair), + _ => None, + } + } } #[derive(Clone, Debug)] @@ -234,6 +255,7 @@ where config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, + ethereum_recv: UnboundedReceiver, db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -284,6 +306,7 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, + ethereum_recv, }) .expect( "Validator data should have been stored in the \ @@ -302,6 +325,7 @@ where }, }, broadcast_sender, + ethereum_recv, } } } @@ -751,6 +775,8 @@ mod test_utils { /// receives any protocol txs sent by the shell. pub fn new() -> (Self, UnboundedReceiver>) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + let (_eth_sender, eth_receiver) = + tokio::sync::mpsc::unbounded_channel(); let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB @@ -764,6 +790,7 @@ mod test_utils { ), top_level_directory().join("wasm"), sender, + eth_receiver, None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, @@ -883,6 +910,7 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); // we have to use RocksDB for this test let (sender, _) = tokio::sync::mpsc::unbounded_channel(); + let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let mut shell = Shell::::new( @@ -893,6 +921,7 @@ mod test_utils { ), top_level_directory().join("wasm"), sender.clone(), + receiver, None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, @@ -941,7 +970,7 @@ mod test_utils { // Drop the shell std::mem::drop(shell); - + let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); // Reboot the shell and check that the queue was restored from DB let shell = Shell::::new( config::Ledger::new( @@ -951,6 +980,7 @@ mod test_utils { ), top_level_directory().join("wasm"), sender, + receiver, None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index ce3696fa9c..4cde93e5de 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -13,7 +13,7 @@ use anoma::types::transaction::hash_tx; use futures::future::FutureExt; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestBeginBlock; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::{UnboundedSender, UnboundedReceiver}; use tower::Service; #[cfg(not(feature = "ABCI"))] use tower_abci::{BoxError, Request as Req, Response as Resp}; @@ -26,6 +26,7 @@ use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; +use crate::node::ledger::ethereum_node::events::EthereumEvent; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -49,6 +50,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, + ethereum_recv: UnboundedReceiver, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -62,6 +64,7 @@ impl AbcippShim { config, wasm_dir, broadcast_sender, + ethereum_recv, Some(db_cache), vp_wasm_compilation_cache, tx_wasm_compilation_cache, From c9ad392df34ace318474da2b180516fa9773c13a Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 21 Jun 2022 13:36:26 +0200 Subject: [PATCH 0026/1995] [feat]: Updated to latest abci++ --- Cargo.lock | 197 ++++++++++++-------------- apps/src/lib/client/rpc.rs | 4 +- apps/src/lib/client/tx.rs | 9 +- apps/src/lib/node/ledger/shell/mod.rs | 9 +- 4 files changed, 104 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6af7d6dd2f..290210cb4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "ark-bls12-381" @@ -526,17 +526,16 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "fd8b508d585e01084059b60f06ade4cb7415cd2e4084b71dd1cb44e7d3fb9880" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] @@ -568,15 +567,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" @@ -596,16 +586,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.9", "futures-channel", "futures-core", "futures-io", @@ -614,7 +604,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -841,9 +830,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake2" @@ -1443,12 +1432,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", ] [[package]] @@ -1459,20 +1448,20 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.9", "memoffset", + "once_cell", "scopeguard", ] @@ -1489,12 +1478,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", + "once_cell", ] [[package]] @@ -2424,14 +2413,14 @@ 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", "js-sys", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2485,9 +2474,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -2605,11 +2594,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", ] @@ -2848,7 +2833,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "derive_more", @@ -2902,7 +2887,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "prost 0.9.0", @@ -3014,12 +2999,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "6c6392766afd7964e2531940894cffe4bd8d7d17dbc3c1c4857040fd4b33bdb3" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", + "hashbrown 0.12.1", "serde 1.0.137", ] @@ -3132,9 +3117,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -3915,7 +3900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", "integer-encoding", "lazy_static 1.4.0", "log 0.4.17", @@ -4208,22 +4193,21 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", - "memoffset", ] [[package]] name = "nix" -version = "0.21.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" dependencies = [ "bitflags", "cc", @@ -4317,7 +4301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint", - "num-complex 0.4.1", + "num-complex 0.4.2", "num-integer", "num-iter", "num-rational", @@ -4347,9 +4331,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", ] @@ -4523,7 +4507,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", ] @@ -5092,7 +5076,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69c28fcebfd842bfe19d69409fc321230ea8c1bebe31f274906485c761ce1917" dependencies = [ - "nix 0.21.2", + "nix 0.21.0", ] [[package]] @@ -5131,9 +5115,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d" dependencies = [ "proc-macro2", ] @@ -5241,7 +5225,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]] @@ -5350,7 +5334,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", "num_cpus", ] @@ -5393,7 +5377,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "redox_syscall 0.2.13", "thiserror", ] @@ -5467,9 +5451,9 @@ 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", @@ -5494,6 +5478,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5599,9 +5584,9 @@ 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", @@ -5812,9 +5797,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "security-framework" -version = "2.3.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -6271,9 +6256,9 @@ 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", ] @@ -6333,9 +6318,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -6402,7 +6387,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6486,7 +6471,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "flex-error", "serde 1.0.137", @@ -6512,7 +6497,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "derive_more", "flex-error", @@ -6538,7 +6523,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6589,14 +6574,14 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -6629,7 +6614,7 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -6655,7 +6640,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7099,9 +7084,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", @@ -7121,7 +7106,7 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#25a0c673ea6748730f86f7f20c933d0f07b50621" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#6cd24d2a91011635929bea5c1ae05d8b4f4a81a9" dependencies = [ "bytes 1.1.0", "futures 0.3.21", @@ -7171,9 +7156,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" @@ -7470,9 +7455,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -7585,7 +7570,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]] @@ -7710,9 +7695,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7720,9 +7705,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static 1.4.0", @@ -7735,9 +7720,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7747,9 +7732,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7757,9 +7742,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -7770,9 +7755,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "wasm-encoder" @@ -8069,7 +8054,7 @@ dependencies = [ "globset", "lazy_static 1.4.0", "log 0.4.17", - "nix 0.20.2", + "nix 0.20.0", "notify", "walkdir", "winapi 0.3.9", @@ -8077,9 +8062,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d85ff0b7c0..d0601ff365 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1582,7 +1582,7 @@ pub async fn get_proposal_votes( if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { let voter_address = gov_storage::get_voter_address(&key) - .expect("Vote key should contains the voting address.") + .expect("Vote key should contain the voting address.") .clone(); if vote.is_yay() && validators.contains(&voter_address) { let amount = @@ -1592,7 +1592,7 @@ pub async fn get_proposal_votes( let validator_address = gov_storage::get_vote_delegation_address(&key) .expect( - "Vote key should contains the delegation address.", + "Vote key should contain the delegation address.", ) .clone(); let delegator_token_amount = get_bond_amount_at( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0bc8b72954..f5a98b2b67 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1121,6 +1121,13 @@ pub async fn broadcast_tx( None, )?; + #[cfg(not(feature = "ABCI"))] + let response = wrapper_tx_subscription + .broadcast_tx(tx.to_bytes().into()) + .await + .map_err(|err| WsError::Response(format!("{:?}", err)))?; + + #[cfg(feature = "ABCI")] let response = wrapper_tx_subscription .broadcast_tx_sync(tx.to_bytes().into()) .await @@ -1141,7 +1148,7 @@ pub async fn broadcast_tx( println!("Transaction hash: {:?}", wrapper_tx_hash); Ok(response) } else { - Err(WsError::Response(response.log.to_string())) + Err(WsError::Response(serde_json::to_string(&response).unwrap())) } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9b787a32cb..3717c4d633 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -540,7 +540,6 @@ where /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { - let mut response = response::Commit::default(); // commit changes from the write-log to storage self.write_log .commit_block(&mut self.storage) @@ -559,8 +558,7 @@ where root, self.storage.last_height, ); - response.data = root.0; - response + response::Commit::default() } /// Validate a transaction request. On success, the transaction will @@ -573,10 +571,9 @@ where ) -> response::CheckTx { let mut response = response::CheckTx::default(); match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => response.log = String::from("Mempool validation passed"), - Err(msg) => { + Ok(_) => { }, + Err(_) => { response.code = 1; - response.log = msg.to_string(); } } response From 19c08bd065bbef856a05e06cc105bbb7ed52bc32 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 21 Jun 2022 15:00:29 +0200 Subject: [PATCH 0027/1995] [fix]: Fixed a bug in PoS parameter validation and in the response to the commit abci++ call --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- proof_of_stake/src/parameters.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3717c4d633..dd3373d77b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -571,7 +571,7 @@ where ) -> response::CheckTx { let mut response = response::CheckTx::default(); match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => { }, + Ok(_) => {} Err(_) => { response.code = 1; } diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index b7a146fdc6..84bd59d4a5 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -113,7 +113,7 @@ impl PosParams { } // Check that there is no more than 1 vote per token - if self.votes_per_token >= BasisPoints::new(10_000) { + if self.votes_per_token > BasisPoints::new(10_000) { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.votes_per_token, )) From 926cfa778c9004eff1a801b68804b20a86fb097c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 23 Jun 2022 16:45:08 +0200 Subject: [PATCH 0028/1995] [feat]: Added in a new oracle that listens to Geth and parses ethereum events. --- Cargo.lock | 402 +++++++++-------- apps/Cargo.toml | 2 +- apps/src/lib/client/rpc.rs | 1 + apps/src/lib/client/tx.rs | 11 +- .../lib/node/ledger/ethereum_node/events.rs | 421 +++++++++++------- apps/src/lib/node/ledger/ethereum_node/mod.rs | 328 ++------------ .../lib/node/ledger/ethereum_node/oracle.rs | 169 +++++++ .../node/ledger/ethereum_node/test_tools.rs | 24 +- apps/src/lib/node/ledger/mod.rs | 58 ++- .../lib/node/ledger/shell/finalize_block.rs | 16 +- apps/src/lib/node/ledger/shell/mod.rs | 15 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 8 +- shared/Cargo.toml | 3 +- 13 files changed, 782 insertions(+), 676 deletions(-) create mode 100644 apps/src/lib/node/ledger/ethereum_node/oracle.rs diff --git a/Cargo.lock b/Cargo.lock index 1cb481a926..defc554aea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,14 +16,14 @@ dependencies = [ "memchr", "pin-project-lite 0.2.9", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.3", ] [[package]] name = "actix-http" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +checksum = "bd2e9f6794b5826aff6df65e3a0d0127b271d1c03629c774238f3582e903d4e4" dependencies = [ "actix-codec", "actix-rt", @@ -45,13 +45,13 @@ dependencies = [ "itoa", "language-tags 0.3.2", "local-channel", - "log 0.4.17", "mime 0.3.16", "percent-encoding 2.1.0", "pin-project-lite 0.2.9", "rand 0.8.5", - "sha-1 0.10.0", + "sha1", "smallvec 1.8.0", + "tracing 0.1.35", "zstd", ] @@ -92,7 +92,7 @@ dependencies = [ "openssl", "pin-project-lite 0.2.9", "tokio-openssl", - "tokio-util 0.7.2", + "tokio-util 0.7.3", ] [[package]] @@ -161,7 +161,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", ] @@ -191,6 +191,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", "ferveo", "ferveo-common", "group-threshold-cryptography", @@ -223,7 +224,7 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.3.11", "wasmer", "wasmer-cache", @@ -319,7 +320,7 @@ dependencies = [ "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-log", "tracing-subscriber 0.3.11", "web30", @@ -380,7 +381,7 @@ dependencies = [ "sha2 0.9.9", "tempfile", "test-log", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.3.11", ] @@ -421,9 +422,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "ark-bls12-381" @@ -618,14 +619,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", @@ -660,15 +661,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" @@ -688,16 +680,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.9", "futures-channel", "futures-core", "futures-io", @@ -706,7 +698,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -757,9 +748,9 @@ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -1158,9 +1149,9 @@ 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 = "byte-slice-cast" @@ -1234,9 +1225,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "bytestring" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" +checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" dependencies = [ "bytes 1.1.0", ] @@ -1447,7 +1438,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.26", + "tracing-core 0.1.27", "tracing-error", ] @@ -1594,12 +1585,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", ] [[package]] @@ -1610,20 +1601,20 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.9", "memoffset", + "once_cell", "scopeguard", ] @@ -1640,12 +1631,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", + "once_cell", ] [[package]] @@ -2195,9 +2186,9 @@ dependencies = [ [[package]] name = "ethabi" -version = "17.0.0" +version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69517146dfab88e9238c00c724fd8e277951c3cc6f22b016d72f422a832213e" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" dependencies = [ "ethereum-types", "hex", @@ -2637,14 +2628,14 @@ 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", "js-sys", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2698,9 +2689,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -2790,8 +2781,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.2", - "tracing 0.1.34", + "tokio-util 0.7.3", + "tracing 0.1.35", ] [[package]] @@ -2818,11 +2809,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", ] @@ -2921,9 +2908,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes 1.1.0", "fnv", @@ -2992,7 +2979,7 @@ dependencies = [ "socket2 0.4.4", "tokio", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", "want", ] @@ -3061,7 +3048,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "derive_more", @@ -3081,8 +3068,8 @@ dependencies = [ "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", - "tracing 0.1.34", + "time 0.3.11", + "tracing 0.1.35", ] [[package]] @@ -3108,14 +3095,14 @@ dependencies = [ "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.34", + "time 0.3.11", + "tracing 0.1.35", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "prost 0.9.0", @@ -3265,12 +3252,12 @@ 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", + "hashbrown 0.12.1", "serde 1.0.137", ] @@ -3383,9 +3370,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -4152,9 +4139,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" dependencies = [ "libc", ] @@ -4182,12 +4169,12 @@ dependencies = [ [[package]] name = "message-io" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6612f460798dabbdbb3d4643d9700a32291cb9a5637f07ad25428030fc06db" +checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", "integer-encoding", "lazy_static 1.4.0", "log 0.4.17", @@ -4292,9 +4279,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", @@ -4579,7 +4566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.1", + "num-complex 0.4.2", "num-integer", "num-iter", "num-rational 0.4.0", @@ -4621,9 +4608,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", ] @@ -4805,9 +4792,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.73" +version = "0.9.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" dependencies = [ "autocfg 1.1.0", "cc", @@ -4823,7 +4810,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", ] @@ -4868,9 +4855,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.1.2" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -4882,9 +4869,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -5275,9 +5262,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] @@ -5481,9 +5468,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -5597,7 +5584,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]] @@ -5706,7 +5693,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.9", "num_cpus", ] @@ -5749,7 +5736,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "redox_syscall 0.2.13", "thiserror", ] @@ -5823,9 +5810,9 @@ 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", @@ -5850,6 +5837,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5976,9 +5964,9 @@ 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", @@ -6027,7 +6015,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.9", + "semver 1.0.10", ] [[package]] @@ -6057,9 +6045,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" [[package]] name = "rusty-fork" @@ -6254,9 +6242,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" [[package]] name = "semver-parser" @@ -6434,6 +6422,17 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "sha1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -6694,22 +6693,23 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.20.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.20.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ - "heck 0.3.3", + "heck 0.4.0", "proc-macro2", "quote", + "rustversion", "syn", ] @@ -6755,9 +6755,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -6830,7 +6830,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6851,7 +6851,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", "zeroize", ] @@ -6879,14 +6879,14 @@ dependencies = [ "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", + "time 0.3.11", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "flex-error", "serde 1.0.137", @@ -6912,14 +6912,14 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "derive_more", "flex-error", "serde 1.0.137", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6932,13 +6932,13 @@ dependencies = [ "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", + "time 0.3.11", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6949,7 +6949,7 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6966,20 +6966,20 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.11", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -6994,9 +6994,9 @@ dependencies = [ "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "thiserror", - "time 0.3.9", + "time 0.3.11", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", "url 2.2.2", "uuid", "walkdir", @@ -7012,7 +7012,7 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -7027,9 +7027,9 @@ dependencies = [ "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", + "time 0.3.11", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", "url 2.2.2", "uuid", "walkdir", @@ -7038,7 +7038,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7047,7 +7047,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -7062,7 +7062,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -7184,9 +7184,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ "libc", "num_threads", @@ -7225,14 +7225,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.2" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -7287,9 +7287,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", @@ -7350,9 +7350,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 0.2.9", @@ -7423,16 +7423,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] @@ -7471,7 +7471,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -7489,9 +7489,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", @@ -7502,16 +7502,16 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.3", "tower-layer", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#25a0c673ea6748730f86f7f20c933d0f07b50621" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#6cd24d2a91011635929bea5c1ae05d8b4f4a81a9" dependencies = [ "bytes 1.1.0", "futures 0.3.21", @@ -7561,9 +7561,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" @@ -7578,15 +7578,15 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 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.26", + "tracing-core 0.1.27", ] [[package]] @@ -7620,11 +7620,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" dependencies = [ - "lazy_static 1.4.0", + "once_cell", "valuable", ] @@ -7634,7 +7634,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.2.25", ] @@ -7645,7 +7645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ "pin-project 1.0.10", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] @@ -7665,7 +7665,7 @@ checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static 1.4.0", "log 0.4.17", - "tracing-core 0.1.26", + "tracing-core 0.1.27", ] [[package]] @@ -7676,7 +7676,7 @@ checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", "thread_local 1.1.4", - "tracing-core 0.1.26", + "tracing-core 0.1.27", ] [[package]] @@ -7692,8 +7692,8 @@ dependencies = [ "sharded-slab", "smallvec 1.8.0", "thread_local 1.1.4", - "tracing 0.1.34", - "tracing-core 0.1.26", + "tracing 0.1.35", + "tracing-core 0.1.27", "tracing-log", ] @@ -7851,9 +7851,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -7966,7 +7966,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]] @@ -8091,9 +8091,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8101,9 +8101,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static 1.4.0", @@ -8116,9 +8116,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8128,9 +8128,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8138,9 +8138,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -8151,9 +8151,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-encoder" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +dependencies = [ + "leb128", +] [[package]] name = "wasm-timer" @@ -8242,7 +8251,7 @@ dependencies = [ "rayon", "smallvec 1.8.0", "target-lexicon", - "tracing 0.1.34", + "tracing 0.1.35", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -8317,7 +8326,7 @@ dependencies = [ "rkyv", "serde 1.0.137", "tempfile", - "tracing 0.1.34", + "tracing 0.1.35", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -8408,20 +8417,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "41.0.0" +version = "42.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f882898b8b817cc4edc16aa3692fdc087b356edc8cc0c2164f5b5181e31c3870" +checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b3b9b3e39e66c7fd3f8be785e74444d216260f491e93369e317ed6482ff80f" +checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" dependencies = [ "wast", ] @@ -8448,9 +8458,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", @@ -8458,9 +8468,9 @@ dependencies = [ [[package]] name = "web30" -version = "0.18.10" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2673ff03d2a8e648f806f100ed70ca3ca69db42dad7178501557b70d549dbb1" +checksum = "463b0185237bb5c10abf6ce7a5923b03503aea0bb2ff5d9ad930d790e69c0cb0" dependencies = [ "awc", "clarity", @@ -8750,18 +8760,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.10.2+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.6+zstd.1.5.2" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -8769,9 +8779,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b467ed629c..d023bc75d5 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -146,7 +146,7 @@ tower-abci-old = {package = "tower-abci", git = "https://github.com/heliaxdev/to tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} -web30 = "0.18.10" +web30 = "0.19.1" websocket = "0.26.2" winapi = "0.3.9" diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5fc760da94..d0601ff365 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1637,6 +1637,7 @@ pub async fn get_proposal_offline_votes( let file = File::open(&path).expect("Proposal file must exist."); let proposal_vote: OfflineVote = serde_json::from_reader(file) .expect("JSON was not well-formatted for offline vote."); + let key = pk_key(&proposal_vote.address); let public_key = query_storage_value(client, &key) .await diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index bf4824de2f..f5a98b2b67 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -41,9 +41,9 @@ use tendermint_rpc_abci::query::{EventType, Query}; use tendermint_rpc_abci::{Client, HttpClient}; use super::rpc; -use super::signing::{find_keypair, sign_tx}; 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}; @@ -1121,6 +1121,13 @@ pub async fn broadcast_tx( None, )?; + #[cfg(not(feature = "ABCI"))] + let response = wrapper_tx_subscription + .broadcast_tx(tx.to_bytes().into()) + .await + .map_err(|err| WsError::Response(format!("{:?}", err)))?; + + #[cfg(feature = "ABCI")] let response = wrapper_tx_subscription .broadcast_tx_sync(tx.to_bytes().into()) .await @@ -1141,7 +1148,7 @@ pub async fn broadcast_tx( println!("Transaction hash: {:?}", wrapper_tx_hash); Ok(response) } else { - Err(WsError::Response(response.log.to_string())) + Err(WsError::Response(serde_json::to_string(&response).unwrap())) } } diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 2f51aaabad..f34bed9ecc 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,4 +1,5 @@ use std::convert::TryInto; +use std::fmt::Debug; use std::str::FromStr; use anoma::types::address::Address; @@ -7,8 +8,16 @@ use ethabi::param_type::ParamType; use ethabi::token::Token; use ethabi::{decode, encode, Uint}; use num256::Uint256; +use thiserror::Error; +use itertools::Either; -use super::{Error, Result}; +#[derive(Error, Debug)] +pub enum Error { + #[error("Could not decode Ethereum event: {0}")] + Decode(String), +} + +pub type Result = std::result::Result; pub mod signatures { pub const TRANSFER_TO_NAMADA_SIG: &str = @@ -47,42 +56,14 @@ pub mod signatures { } } -/// Representation of address on Ethereum -#[derive(Clone, Debug, PartialEq)] -pub struct EthAddress(pub [u8; 20]); - -/// A Keccak hash -#[derive(Clone, Debug, PartialEq)] -pub struct KeccakHash(pub [u8; 32]); - -/// An Ethereum event to be processed by the Anoma ledger -pub enum EthereumEvent { - TransfersToNamada(Vec), - TransfersToErc(Vec), - ValidatorSetUpdate { - nonce: Uint, - bridge_validator_hash: KeccakHash, - governance_validator_hash: KeccakHash, - }, - NewContract { - name: String, - address: EthAddress, - }, - UpgradedContract { - name: String, - address: EthAddress, - }, - UpdateBridgeWhitelist { - nonce: Uint, - whitelist: Vec, - }, -} - /// An event waiting for a certain number of confirmations /// before being sent to the ledger pub struct PendingEvent { + /// number of confirmations to consider this event finalized confirmations: Uint256, + /// the block height from which this event originated block_height: Uint256, + /// the event itself pub event: EthereumEvent, } @@ -101,17 +82,19 @@ impl PendingEvent { ) -> Result { match signature { signatures::TRANSFER_TO_NAMADA_SIG => { - RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + RawTransfersToNamada::decode(data, TargetAddressType::Native) + .map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::TransfersToNamada(txs.transfers), + event: EthereumEvent::TransfersToNamada(txs.transfers.unwrap_left()), }) } signatures::TRANSFER_TO_ERC_SIG => { - RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + RawTransfersToNamada::decode(data, TargetAddressType::Erc20) + .map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::TransfersToErc(txs.transfers), + event: EthereumEvent::TransfersToErc(txs.transfers.unwrap_right()), }) } signatures::VALIDATOR_SET_UPDATE_SIG => { @@ -121,7 +104,7 @@ impl PendingEvent { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: super::MIN_CONFIRMATIONS.into(), + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -133,22 +116,22 @@ impl PendingEvent { } signatures::NEW_CONTRACT_SIG => RawChangedContract::decode(data) .map(|RawChangedContract { name, address }| PendingEvent { - confirmations: super::MIN_CONFIRMATIONS.into(), + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::NewContract { name, address }, }), signatures::UPGRADED_CONTRACT_SIG => RawChangedContract::decode( data, ) - .map(|RawChangedContract { name, address }| PendingEvent { - confirmations: super::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::UpgradedContract { name, address }, - }), + .map(|RawChangedContract { name, address }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpgradedContract { name, address }, + }), signatures::UPDATE_BRIDGE_WHITELIST_SIG => { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { - confirmations: super::MIN_CONFIRMATIONS.into(), + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpdateBridgeWhitelist { nonce, @@ -164,91 +147,167 @@ impl PendingEvent { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - &self.confirmations >= height - &self.block_height + self.confirmations >= height.clone() - self.block_height.clone() } } -/// Type of address to transfer to on Anoma -enum TargetAddressType { - Native, - Erc20, -} - -/// Trait for determining target address type -trait TargetAddress { - fn address_type() -> TargetAddressType; - fn into_token(&self) -> Token; -} +/// Representation of address on Ethereum +#[derive(Clone, Debug, PartialEq)] +pub struct EthAddress(pub [u8; 20]); -impl TargetAddress for Address { - fn address_type() -> TargetAddressType { - TargetAddressType::Native - } - fn into_token(&self) -> Token { - Token::String(self.encode()) - } -} +/// A Keccak hash +#[derive(Clone, Debug, PartialEq)] +pub struct KeccakHash(pub [u8; 32]); -impl TargetAddress for EthAddress { - fn address_type() -> TargetAddressType { - TargetAddressType::Erc20 - } - fn into_token(&self) -> Token { - Token::Address(self.0.into()) - } +/// An Ethereum event to be processed by the Anoma ledger +#[derive(Debug)] +pub enum EthereumEvent { + /// Event transferring batches of ether from Ethereum to wrapped ETH on Anoma + TransfersToNamada(Vec), + /// Event transferring a batch of ERC20 tokens from Ethereum to a wrapped + /// version on Anoma + TransfersToErc(Vec), + /// Event indication that the validator set has been updated + /// in the governance contract + ValidatorSetUpdate { + /// Monotonically increasing nonce + nonce: Uint, + /// Hash of the validators in the bridge contract + bridge_validator_hash: KeccakHash, + /// Hash of the validators in the governance contract + governance_validator_hash: KeccakHash, + }, + /// Event indication that a new smart contract has been + /// deployed + NewContract { + /// Name of the contract + name: String, + /// Address of the contract on Ethereum + address: EthAddress, + }, + /// Event indicating that a smart contract has been updated + UpgradedContract { + /// Name of the contract + name: String, + /// Address of the contract on Ethereum + address: EthAddress, + }, + /// Event indication a new Ethereum based token has been whitelisted for + /// transfer across the bridge + UpdateBridgeWhitelist { + /// Monotonically increasing nonce + nonce: Uint, + /// Tokens to be allowed to be transferred across the bridge + whitelist: Vec, + }, } /// An event transferring some kind of value from Ethereum to Anoma +#[derive(Debug)] pub struct RawTransferToNamada { - amount: Amount, - source: EthAddress, - target: T, + /// Quantity of ether in the transfer + pub amount: Amount, + /// Address paying the ether + pub source: EthAddress, + /// The address receiving wrapped assets on Anoma + pub target: T, } -/// A batch of RawTransferToNamadas from an Ethereum event -pub struct RawTransfersToNamada { - transfers: Vec>, - nonce: Uint, - confirmations: u32, -} - -/// Type aliases +/// Type alias for transferring ether to wrapped ETH pub type TransferToNamada = RawTransferToNamada
; +/// Type alias for transferring ERC20 to wrapped version on Anoma pub type TransferToErc = RawTransferToNamada; +/// struct for whitelisting a token from Ethereum. +/// Includes the address of issuing contract and +/// a cap on the max amount of this token allowed to be +/// held by the bridge. +#[derive(Debug)] +pub struct TokenWhitelist { + /// Address of Ethereum smart contract issuing token + pub token: EthAddress, + /// Maximum amount of token allowed on the bridge + pub cap: Amount, +} + +/// A batch of [`RawTransferToNamada`] from an Ethereum event +struct RawTransfersToNamada { + /// A list of transfers + pub transfers: Either, Vec>, + /// A monotonically increasing nonce + pub nonce: Uint, + /// The number of confirmations needed to consider this batch + /// finalized + pub confirmations: u32, +} + /// Event emitted with the validator set changes struct ValidatorSetUpdate { + /// A monotonically increasing nonce nonce: Uint, + /// Hash of the validators in the bridge contract bridge_validator_hash: KeccakHash, + /// Hash of the validators in the governance contract governance_validator_hash: KeccakHash, } /// Event indicating a new smart contract has been /// deployed or upgraded on Ethereum struct RawChangedContract { + /// Name of the contract name: String, + /// Address of the contract on Ethereum address: EthAddress, } -/// struct for whitelisting a token from Ethereum. -/// Includes the address of issuing contract and -/// a cap on the max amount of this token allowed to be -/// held by the bridge. -pub struct TokenWhitelist { - token: EthAddress, - cap: Amount, -} /// Event for whitelisting new tokens and their /// rate limits struct UpdateBridgeWhitelist { + /// A monotonically increasing nonce nonce: Uint, + /// Tokens to be allowed to be transferred across the bridge whitelist: Vec, } -impl RawTransfersToNamada { - fn decode(data: &[u8]) -> Result { - let name = match T::address_type() { +/// Type of address to transfer to on Anoma +enum TargetAddressType { + /// Output of the bridge will be wrapped ETH + Native, + /// Output of the bridge will be a wrapped ERC20 token + Erc20, +} + +/// Trait for determining target address type +trait TargetAddress: Debug { + fn address_type() -> TargetAddressType; + fn into_token(&self) -> Token; +} + +impl TargetAddress for Address { + fn address_type() -> TargetAddressType { + TargetAddressType::Native + } + + fn into_token(&self) -> Token { + Token::String(self.encode()) + } +} + +impl TargetAddress for EthAddress { + fn address_type() -> TargetAddressType { + TargetAddressType::Erc20 + } + fn into_token(&self) -> Token { + Token::Address(self.0.into()) + } +} + +impl RawTransfersToNamada { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`RawTransfersToNamada`] + fn decode(data: &[u8], address_type: TargetAddressType) -> Result { + let name = match address_type { TargetAddressType::Native => "TransferToNamada", TargetAddressType::Erc20 => "TransferToErc", }; @@ -257,7 +316,7 @@ impl RawTransfersToNamada { &[ ParamType::Uint(256), ParamType::Array(Box::new(ParamType::Address)), - match T::address_type() { + match address_type { TargetAddressType::Native => { ParamType::Array(Box::new(ParamType::String)) } @@ -280,58 +339,101 @@ impl RawTransfersToNamada { })?; let sources = sources.parse_eth_address_array()?; - let targets: Vec = match T::address_type() { - TargetAddressType::Native => targets.parse_address_array()?, - TargetAddressType::Erc20 => targets.parse_eth_address_array()?, + let targets: Either, Vec> = match address_type { + TargetAddressType::Native => Either::Left(targets.parse_address_array()?), + TargetAddressType::Erc20 => Either::Right(targets.parse_eth_address_array()?), }; let amounts = amounts.parse_amount_array()?; if sources.len() != amounts.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - transfer amounts" + transfer amounts" .into(), )) - } else if targets.len() != sources.len() { + } else if targets.as_ref().either(| l| l.len(), | r| r.len()) != sources.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - target addresses" + target addresses" .into(), )) } else { - Ok(RawTransfersToNamada { - transfers: sources - .into_iter() - .zip(targets.into_iter()) - .zip(amounts.into_iter()) - .map(|((source, target), amount)| RawTransferToNamada { - source, - target, - amount, - }) - .collect(), + let transfers = match targets { + Either::Left(targets) => Either::Left( + sources + .into_iter() + .zip(targets.into_iter()) + .zip(amounts.into_iter()) + .map(|((source, target), amount)| RawTransferToNamada { + source, + target, + amount, + }) + .collect() + ), + Either::Right(targets) => Either::Right( + sources + .into_iter() + .zip(targets.into_iter()) + .zip(amounts.into_iter()) + .map(|((source, target), amount)| RawTransferToNamada { + source, + target, + amount, + }) + .collect() + ) + }; + Ok(Self { + transfers, nonce: nonce.parse_uint256()?, confirmations: confs.parse_u32()?, }) } } + /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's + /// ABI serialization scheme. fn encode(self) -> Vec { - let Self { + let RawTransfersToNamada { transfers, nonce, confirmations, } = self; + let (amounts, sources, targets) = match transfers { + Either::Left(transfers) => { + let amounts: Vec = transfers + .iter() + .map(|RawTransferToNamada { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (sources, targets): (Vec, Vec) = transfers + .into_iter() + .map(|RawTransferToNamada { source, target, .. }| { + (Token::Address(source.0.into()), target.into_token()) + }) + .unzip(); + (amounts, sources, targets) + } + Either::Right(transfers) => { + let amounts: Vec = transfers + .iter() + .map(|RawTransferToNamada { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (sources, targets): (Vec, Vec) = transfers + .into_iter() + .map(|RawTransferToNamada { source, target, .. }| { + (Token::Address(source.0.into()), target.into_token()) + }) + .unzip(); + (amounts, sources, targets) + } + }; - let amounts: Vec = transfers.iter() - .map(|RawTransferToNamada{amount, ..}| Token::Uint(u64::from(*amount).into())) - .collect(); - let (sources, targets): (Vec, Vec) = transfers.into_iter() - .map(|RawTransferToNamada{source, target, ..}| - (Token::Address(source.0.into()), target.into_token()) - ) - .unzip(); encode(&[ - Token::Uint(nonce), + Token::Uint(nonce.into()), Token::Array(sources), Token::Array(targets), Token::Array(amounts), @@ -341,6 +443,8 @@ impl RawTransfersToNamada { } impl ValidatorSetUpdate { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`ValidatorSetUpdate`] fn decode(data: &[u8]) -> Result { let [nonce, bridge_validator_hash, goverance_validator_hash]: [Token; 3] = decode( @@ -351,14 +455,14 @@ impl ValidatorSetUpdate { ], data, ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "ValidatorSetUpdate signature should contain three types" - .into(), - ) - })?; + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "ValidatorSetUpdate signature should contain three types" + .into(), + ) + })?; Ok(Self { nonce: nonce.parse_uint256()?, @@ -368,6 +472,8 @@ impl ValidatorSetUpdate { }) } + /// Serialize an instance [`ValidatorSetUpdate`] using Ethereum's + /// ABI serialization scheme. fn encode(self) -> Vec { let ValidatorSetUpdate { nonce, @@ -384,6 +490,8 @@ impl ValidatorSetUpdate { } impl RawChangedContract { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`RawChangedContract`] fn decode(data: &[u8]) -> Result { let [name, address]: [Token; 2] = decode(&[ParamType::String, ParamType::Address], data) @@ -402,16 +510,17 @@ impl RawChangedContract { }) } + /// Serialize an instance [`RawChangedContract`] using Ethereum's + /// ABI serialization scheme. fn encode(self) -> Vec { - let RawChangedContract { - name, - address, - } = self; + let RawChangedContract { name, address } = self; encode(&[Token::String(name), Token::Address(address.0.into())]) } } impl UpdateBridgeWhitelist { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`UpdateBridgeWhitelist`] fn decode(data: &[u8]) -> Result { let [nonce, tokens, caps]: [Token; 3] = decode( &[ @@ -421,14 +530,14 @@ impl UpdateBridgeWhitelist { ], data, ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "UpdatedBridgeWhitelist signature should contain three types" - .into(), - ) - })?; + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "UpdatedBridgeWhitelist signature should contain three types" + .into(), + ) + })?; let tokens = tokens.parse_eth_address_array()?; let caps = caps.parse_amount_array()?; @@ -450,16 +559,19 @@ impl UpdateBridgeWhitelist { } } + /// Serialize an instance [`UpdateBridgeWhitelist`] using Ethereum's + /// ABI serialization scheme. fn encode(self) -> Vec { - let UpdateBridgeWhitelist { - nonce, - whitelist, - } = self; - - let (tokens, caps): (Vec, Vec) = whitelist.into_iter() - .map(| TokenWhitelist{token, cap} | - (Token::Address(token.0.into()), Token::Uint(u64::from(cap).into())) - ) + let UpdateBridgeWhitelist { nonce, whitelist } = self; + + let (tokens, caps): (Vec, Vec) = whitelist + .into_iter() + .map(|TokenWhitelist { token, cap }| { + ( + Token::Address(token.0.into()), + Token::Uint(u64::from(cap).into()), + ) + }) .unzip(); encode(&[Token::Uint(nonce), Token::Array(tokens), Token::Array(caps)]) } @@ -563,7 +675,7 @@ impl Parse for Token { fn parse_keccak(self) -> Result { if let Token::FixedBytes(bytes) = self { - let bytes = bytes.try_into().map_err(Error::Decode( + let bytes = bytes.try_into().map_err(|_| Error::Decode( "Expect 32 bytes for a Keccak hash".into(), ))?; Ok(KeccakHash(bytes)) @@ -665,9 +777,10 @@ mod test_events { assert_eq!( decode( &[ParamType::Address], - encode(&[Token::Address(erc.0.into())]).as_slice()) - .expect("Test failed"), + encode(&[Token::Address(erc.0.into())]).as_slice() + ) + .expect("Test failed"), erc ) } -} \ No newline at end of file +} diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 4cde012f39..c9e47acce1 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -1,16 +1,11 @@ +pub mod oracle; pub mod events; -pub mod test_tools; +mod test_tools; use std::ffi::OsString; -use std::sync::Arc; -use events::{EthAddress, EthereumEvent}; use thiserror::Error; -use tokio::sync::Mutex; -use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::oneshot::{channel, Receiver, Sender}; -use tokio::task; -use web30::client::Web3; +use tokio::sync::oneshot::{Sender, Receiver}; #[derive(Error, Debug)] pub enum Error { @@ -19,13 +14,13 @@ pub enum Error { #[error("{0}")] Runtime(String), #[error( - "The receiver of the Ethereum relayer messages unexpectedly dropped" + "The receiver of the Ethereum relayer messages unexpectedly dropped" )] RelayerReceiverDropped, - #[error("Web3 server unexpectedly stopped")] - Web3Server, + #[error("The Ethereum Oracle process unexpectedly stopped")] + Oracle, #[error( - "Could not read Ethereum network to connect to from env var: {0:?}" + "Could not read Ethereum network to connect to from env var: {0:?}" )] EthereumNetwork(OsString), #[error("Could not decode Ethereum event: {0}")] @@ -34,67 +29,50 @@ pub enum Error { pub type Result = std::result::Result; -/// Minimum number of confirmations needed to trust an Ethereum branch -const MIN_CONFIRMATIONS: u64 = 50; -/// Dummy addresses for smart contracts -const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); -const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); - -/// Run the Ethereum fullnode as well as a relayer -/// that sends RPC streams to the ledger. If either -/// stops or an abort signal is sent, these processes -/// are halted. +/// Run the Ethereum fullnode. If it stops or an abort +/// signal is sent, this processes is halted. pub async fn run( - url: &str, - mut sender: UnboundedSender, + mut ethereum_node: EthereumNode, abort_recv: Receiver>, ) -> Result<()> { - // start up the ethereum node. - let mut ethereum_node = EthereumNode::new(url).await?; - let (shutdown_send, shutdown_recv) = tokio::sync::oneshot::channel(); - - let (request, recv) = tokio::sync::mpsc::unbounded_channel(); - tokio::select! { // run the ethereum fullnode - status = ethereum_node.wait() => status, + status = ethereum_node.wait() => status, // wait for an abort signal resp_sender = abort_recv => { match resp_sender { Ok(resp_sender) => { tracing::info!("Shutting down Ethereum fullnode..."); - shutdown_send.send(()).unwrap(); ethereum_node.kill().await; resp_sender.send(()).unwrap(); }, Err(err) => { tracing::error!("The Ethereum abort sender has unexpectedly dropped: {}", err); tracing::info!("Shutting down Ethereum fullnode..."); - shutdown_send.send(()).unwrap(); ethereum_node.kill().await; } } Ok(()) } - resp = toki } } #[cfg(feature = "eth-fullnode")] /// Tools for running a geth fullnode process pub mod eth_fullnode { - use std::sync::Arc; - use tokio::process::{Child, Command}; - use tokio::sync::Mutex; + use tokio::sync::oneshot::{channel, Sender, Receiver, error::TryRecvError}; use web30::client::Web3; use super::{Error, Result}; - /// A handle to a running geth process + /// A handle to a running geth process and a channel + /// that indicates it should shut down if the oracle + /// stops. pub struct EthereumNode { process: Child, + abort_recv: Receiver<()>, } /// Read from environment variable which Ethereum @@ -120,12 +98,13 @@ pub mod eth_fullnode { } impl EthereumNode { - /// Starts the geth process and returns a handle to it. + /// Starts the geth process and returns a handle to it as well + /// as an oracle that can relay data from geth to the ledger. /// /// First looks up which network to connect to from an env var. /// It then starts the process and waits for it to finish /// syncing. - pub async fn new(url: &str) -> Result { + pub async fn new(url: &str) -> Result<(EthereumNode, Sender<()>)> { // the geth fullnode process let network = get_eth_network()?; let args = match &network { @@ -148,7 +127,10 @@ pub mod eth_fullnode { // it takes a brief amount of time to open up the websocket on // geth's end - let client = Arc::new(Mutex::new(Web3::new(url, std::time::Duration::from_secs(5)))); + let client = Web3::new( + url, + std::time::Duration::from_secs(5), + ); loop { match client.eth_syncing().await { @@ -166,23 +148,35 @@ pub mod eth_fullnode { } } - Ok(Self { + let (abort_sender, receiver) = channel(); + let node = Self { process: ethereum_node, - }) + abort_recv: receiver, + }; + Ok((node, abort_sender)) } - /// Wait for the process to finish. If it does, - /// return the status. + /// Wait for the process to finish or an abort message was + /// received from the Oracle process. If either, return the + /// status. pub async fn wait(&mut self) -> Result<()> { - match self.process.wait().await { - Ok(status) => { - if status.success() { - Ok(()) - } else { - Err(Error::Runtime(status.to_string())) + loop { + match self.process.try_wait() { + Ok(Some(status)) => { + return if status.success() { + Ok(()) + } else { + Err(Error::Runtime(status.to_string())) + }; } + Ok(None) => {}, + Err(err) => return Err(Error::Runtime(err.to_string())), + } + match self.abort_recv.try_recv() { + Ok(()) => return Ok(()), + Err(TryRecvError::Empty) => {}, + Err(TryRecvError::Closed) => return Err(Error::Oracle), } - Err(err) => Err(Error::Runtime(err.to_string())), } } @@ -197,233 +191,3 @@ pub mod eth_fullnode { pub use eth_fullnode::EthereumNode; #[cfg(not(feature = "eth-fullnode"))] pub use test_tools::mock_eth_fullnode::EthereumNode; - -/// Tools for polling the Geth fullnode asynchronously -#[cfg(feature = "eth-fullnode")] -pub mod ethereum_relayer { - use events::{signatures, PendingEvent}; - use num256::Uint256; - use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, UnboundedReceiver}; - use web30::client::Web3; - - use super::*; - - /// Types of requests that can be sent to the Web3 server - #[derive(Clone)] - enum Web3Cmd { - /// Requests the latest block seen by the full node - LatestBlock, - /// Find events with [`MIN_CONFIRMATIONS`] of confirmations - /// and adds them to the pending queue - FetchEvents{ - block_height: Uint256, - signature: &'static str, - }, - /// Request to shutdown the server - Shutdown, - } - - /// A request to the Web3 server. Includes the request body as well - /// as a one shot channel to return the response - struct Web3Request { - /// Request body - request: Web3Cmd, - /// Channel for sending response - reply: Sender - } - - /// A reply from the Web3 server. A return value of None - /// signifies a failure to connect to the underlying full node. - enum Web3Reply { - /// The latest block height - LatestBlock(Option), - /// A set of events from a block - Events(Option>), - /// Acknowledgment of shutdown - Shutdown, - } - - /// Runs a loop that receives requests from an inbound channel. - /// These requests include a channel for sending replies. - /// The server handles the request and sends a reply over the supplied - /// channel. - /// - /// If any channel fails to send, the server shuts down. - pub async fn web3_server( - url: &str, - mint_contract: EthAddress, - governance_contract: EthAddress, - mut channel: UnboundedReceiver, - ) { - let client = Arc::new(Mutex::new(Web3::new(url, std::time::Duration::from_secs(3)))); - // If a none is received, the sending channel has hung up, - // so the server stops. - while let Some( - Web3Request{ - request, - reply - } - ) = channel.recv().await { - // handle request by type - if match request { - Web3Cmd::LatestBlock => reply.send( - Web3Reply::LatestBlock(client - .lock() - .await - .eth_block_number() - .await - .ok()) - ), - Web3Cmd::FetchEvents{block_height, signature} => { - let addr = match signatures::SigType::from(signature) { - signatures::SigType::Bridge => mint_contract.0.clone().into(), - signatures::SigType::Governance => governance_contract.0.clone().into(), - }; - let events = client - .lock() - .await - .check_for_events( - block_height.clone(), - Some(block_height.clone()), - vec![addr], - vec![signature], - ) - .await - .ok() - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - signature, - block_height.clone(), - log.data.0.as_slice(), - ).ok() - }) - .collect::>()} - ); - reply.send( - Web3Reply::Events(events) - ) - } - Web3Cmd::Shutdown => { - reply.send(Web3Reply::Shutdown); - // shutdown the server without waiting to see if - // reply sent successfully. - return ; - } - }.is_err() { - // could not send reply because the receiver hung up, - // so the server stops - return; - } - } - } - - /// Check which events in the queue have reached their - /// required number of confirmations and send them - /// to the ledger. - /// - /// If the ledger's receiver has dropped, this will - /// propagate an error here. - fn process_queue( - latest_block: &Uint256, - pending: &mut Vec, - sender: &mut UnboundedSender - ) -> Result<()> { - let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); - std::mem::swap(&mut pending_tmp, pending); - for item in pending_tmp.into_iter() { - if item.is_confirmed(latest_block) { - sender - .send(item.event) - .map(Error::RelayerReceiverDropped)?; - } else { - pending.push(item); - } - } - Ok(()) - } - - /// Sends a request to the Web3 server and awaits a response. If the server has - /// stopped, returns an error. - fn process_request(request: &UnboundedSender, cmd: Web3Cmd) -> Result { - let (reply, mut recv) = channel(); - request.send( - Web3Request { - request: cmd, - reply, - } - ) - .map_err(|_| Error::Web3Server)?; - loop { - match recv.try_recv() { - Ok(reply) => return Ok(reply), - Err(tokio::sync::oneshot::error::TryRecvError::Closed) => return Err(Error::Web3Server), - _ => {} - } - } - } - - /// Starts a Web3 server that allows this method to talk to the Ethereum - /// full node. - /// - /// Continuously polls for events that are [`MIN_CONFIRMATIONS`] blocks old - /// and when they have met their confirmation target, forwards them to the - /// leder. - /// - /// On receiving a shutdown command, exit with an Ok(()). If the server shutsdown - /// are the ledger hangs up its end of the channel, shuts down Anoma. - pub async fn run_relayer( - url: &str, - mint_contract: EthAddress, - governance_contract: EthAddress, - mut sender: UnboundedSender, - mut shutdown: Receiver<()>, - ) -> Result<()> { - let (request, recv) = unbounded_channel(); - // start the server - - let mut latest_block: Uint256 = Default::default(); - let mut pending: Vec = Vec::new(); - // the main loop to watch for new events - loop { - // get the latest block height - let height = loop { - if let Web3Reply::LatestBlock(Some(height)) = process_request(&request, Web3Cmd::LatestBlock)? { - break height - } - if shutdown.try_recv().is_ok() { - let _ = process_request(&request, Web3Cmd::Shutdown); - return Ok(()) - } - }; - latest_block = height; - - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); - // get events corresponding to each signature - for sig in signatures::SIGNATURES { - let mut events = loop { - let cmd = Web3Cmd::FetchEvents { - block_height: block_to_check.clone(), - signature: sig - }; - if let Web3Reply::Events(Some(events)) = process_request(&request, cmd.clone())? { - break events; - } - if shutdown.try_recv().is_ok() { - let _ = process_request(&request, Web3Cmd::Shutdown); - return Ok(()) - } - }; - - // add events to queue and forward to ledger if confirmed - pending.append(&mut events); - process_queue(&latest_block, &mut pending, &mut sender)?; - } - } - } - -} - -#[cfg(feature = "eth-fullnode")] -pub use ethereum_relayer::{web3_server, run_relayer}; diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs new file mode 100644 index 0000000000..714afa4d81 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -0,0 +1,169 @@ +use std::ops::Deref; + +use num256::Uint256; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::oneshot::Sender; +use web30::client::Web3; + +use super::events::{signatures, EthAddress, EthereumEvent, PendingEvent}; + +/// Minimum number of confirmations needed to trust an Ethereum branch +pub(crate) const MIN_CONFIRMATIONS: u64 = 50; + +/// Dummy addresses for smart contracts +const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); +const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); + +/// A client that can talk to geth and parse +/// and relay events relevant to Anoma to the +/// ledger process +pub(crate) struct Oracle { + /// The client that talks to the Ethereum fullnode + client: Web3, + /// A channel for sending processed and confirmed + /// events to the ledger process + sender: UnboundedSender, + /// A channel to signal that the ledger should shut down + /// because the Oracle has stopped + abort: Option>, +} + +impl Deref for Oracle { + type Target = Web3; + + fn deref(&self) -> &Self::Target { + &self.client + } +} + +impl Drop for Oracle { + fn drop(&mut self) { + // send an abort signal to shut down the + // rest of the ledger gracefully + let abort = self.abort.take().unwrap(); + let _ = abort.send(()); + } +} + +impl Oracle { + /// Initialize a new [`Oracle`] + pub fn new(url: &str, sender: UnboundedSender, abort: Sender<()>) -> Self { + Self { + client: Web3::new(url, std::time::Duration::from_secs(30)), + sender, + abort: Some(abort), + } + } + + /// Send a series of [`EthereumEvent`]s to the Anoma + /// ledger. Returns a boolean indicating that all sent + /// successfully. If false is returned, the receiver + /// has hung up. + fn send(&self, events: Vec) -> bool { + events.into_iter() + .map(|event| self.sender.send(event)) + .all(|res| res.is_ok()) + } + + /// Check if the receiver in the ledger has hung up. + /// Used to help determine when to stop the oracle + fn connected(&self) -> bool { + !self.sender.is_closed() + } +} + +pub async fn run_oracle(url: &str, sender: UnboundedSender, abort_sender: Sender<()>) { + let oracle = Oracle::new(url, sender, abort_sender); + // Initialize our local state. This includes + // the latest block height seen and a queue of events + // awaiting a certain number of confirmations + let mut latest_block: Uint256 = Default::default(); + let mut pending: Vec = Vec::new(); + loop { + // update the latest block height + latest_block = loop { + if let Ok(height) = oracle.eth_block_number().await { + break height; + } + if !oracle.connected() { + tracing::info!( + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); + return + } + }; + + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. + for sig in signatures::SIGNATURES { + let addr = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => { + MINT_CONTRACT.0.clone().into() + } + signatures::SigType::Governance => { + GOVERNANCE_CONTRACT.0.clone().into() + } + }; + // fetch the events for matching the given signature + let mut events = loop { + if let Ok(pending) = oracle.check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) + .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ).ok() + }) + .collect::>() + }) { + break pending; + } + if !oracle.connected() { + tracing::info!( + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); + return + } + }; + pending.append(&mut events); + if !oracle.send(process_queue(&latest_block, &mut pending)) { + tracing::info!( + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); + return + } + } + } +} + +/// Check which events in the queue have reached their +/// required number of confirmations and remove them +/// from the queue of pending events +fn process_queue( + latest_block: &Uint256, + pending: &mut Vec, +) -> Vec { + let mut pending_tmp: Vec = + Vec::with_capacity(pending.len()); + std::mem::swap(&mut pending_tmp, pending); + let mut confirmed = vec![]; + for item in pending_tmp.into_iter() { + if item.is_confirmed(latest_block) { + confirmed.push(item.event); + } else { + pending.push(item); + } + } + confirmed +} diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index f1a3940486..e15efc3bdd 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -26,19 +26,20 @@ pub mod mock_eth_fullnode { #[cfg(not(feature = "eth-fullnode"))] pub mod mock_web3_client { use std::fmt::Debug; + use num256::Uint256; - use tokio::sync::mpsc::{channel, Sender, Receiver}; + use tokio::sync::mpsc::{channel, Receiver, Sender}; use web30::types::Log; - use super::{Error, Result}; use super::events::signatures::*; + use super::{Error, Result}; /// Commands we can send to the mock client pub enum TestCmd { Normal, Unresponsive, NewHeight(Uint256), - NewEvent(MockEventType, Vec) + NewEvent(MockEventType, Vec), } /// The type of events supported @@ -60,7 +61,7 @@ pub mod mock_web3_client { cmd_channel: Receiver, active: bool, latest_block_height: Uint256, - events: Vec<(MockEventType, Vec)> + events: Vec<(MockEventType, Vec)>, } impl Web3 { @@ -76,7 +77,7 @@ pub mod mock_web3_client { active: true, latest_block_height: Default::default(), events: vec![], - } + }, ) } @@ -86,7 +87,9 @@ pub mod mock_web3_client { match cmd { TestCmd::Normal => self.active = true, TestCmd::Unresponsive => self.active = false, - TestCmd::NewHeight(height) => self.latest_block_height = height, + TestCmd::NewHeight(height) => { + self.latest_block_height = height + } TestCmd::NewEvent(ty, data) => self.events.push((ty, data)), } } @@ -122,15 +125,17 @@ pub mod mock_web3_client { VALIDATOR_SET_UPDATE_SIG => MockEventType::ValSetUpdate, NEW_CONTRACT_SIG => MockEventType::NewContract, UPGRADED_CONTRACT_SIG => MockEventType::UpgradedContract, - UPDATE_BRIDGE_WHITELIST_SIG => MockEventType::BridgeWhitelist, - _ => return Ok(vec![]) + UPDATE_BRIDGE_WHITELIST_SIG => { + MockEventType::BridgeWhitelist + } + _ => return Ok(vec![]), }; let mut logs = vec![]; let mut events = vec![]; std::mem::swap(&mut self.events, &mut events); for (event_ty, data) in events.into_iter() { if &event_ty == &ty { - logs.push(Log{ + logs.push(Log { data: data.into(), ..Default::default() }); @@ -145,4 +150,3 @@ pub mod mock_web3_client { } } } - diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 22df6951c4..f48ec9cd8b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -16,6 +16,7 @@ use std::str::FromStr; use anoma::ledger::governance::storage as gov_storage; use anoma::types::storage::Key; use byte_unit::Byte; +use ethereum_node::{EthereumNode, events::EthereumEvent}; use futures::future::TryFutureExt; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -23,6 +24,7 @@ use sysinfo::{RefreshKind, System, SystemExt}; use tendermint_proto::abci::CheckTxType; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::CheckTxType; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use tower::ServiceBuilder; #[cfg(not(feature = "ABCI"))] use tower_abci::{response, split, Server}; @@ -38,6 +40,7 @@ use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; use crate::{config, wasm_loader}; + /// Env. var to set a number of Tokio RT worker threads const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; @@ -184,6 +187,36 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .thread_name(|i| format!("ledger-rayon-worker-{}", i)) .build_global() .unwrap(); + let (ethereum_node, oracle, eth_receiver) = if matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { + // boot up the ethereum node process and wait for it to finish syncing + let (eth_sender, eth_receiver) = unbounded_channel(); + let (ethereum_node, abort_sender) = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(EthereumNode::new(&config.ethereum_url())) + .expect("Unable to start the Ethereum fullnode"); + // Start the oracle process to relay data from the ethereum fullnode to the ledger. + // Unfortunately requires a single threaded runtime, so we do this here. + let url = config.ethereum_url(); + let oracle = std::thread::spawn(move || { + tokio::runtime::Builder::new_current_thread() + .thread_name("ethereum-relayer-tokio-worker") + .enable_all() + .build() + .unwrap() + .block_on( + ethereum_node::oracle::run_oracle( + &url, + eth_sender, + abort_sender, + ) + ) + }); + (Some(ethereum_node), Some(oracle), Some(eth_receiver)) + } else { + (None, None, None) + }; // Start tokio runtime with the `run_aux` function tokio::runtime::Builder::new_multi_thread() @@ -193,7 +226,9 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .enable_all() .build() .unwrap() - .block_on(run_aux(config, wasm_dir)); + .block_on(run_aux(config, wasm_dir, ethereum_node, eth_receiver)); + // join up the oracle thread. + let _ = oracle.map(|handle| handle.join()); } /// Resets the tendermint_node state and removes database files @@ -205,7 +240,14 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// 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) { +/// +/// TODO: Update this docstring +async fn run_aux( + config: config::Ledger, + wasm_dir: PathBuf, + ethereum_node: Option, + eth_receiver: Option>, +) { // Prefetch needed wasm artifacts wasm_loader::pre_fetch_wasm(&wasm_dir).await; @@ -290,7 +332,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { rocksdb::Cache::new_lru_cache(block_cache_size_bytes as usize).unwrap(); let tendermint_dir = config.tendermint_dir(); - let ethereum_url = config.ethereum_url(); 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(); @@ -304,10 +345,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Channel for signalling shut down from the shell or from Tendermint let (abort_send, abort_recv) = tokio::sync::mpsc::unbounded_channel::<&'static str>(); - // Channels for the Ethereum relayer to send new Ethereum block headers - // and smart contract logs to the ledger - let (eth_relayer_sender, mut eth_relayer_receiver) = - tokio::sync::mpsc::unbounded_channel(); // Channels for validators to send protocol txs to be broadcast to the // broadcaster service let (broadcaster_sender, broadcaster_receiver) = @@ -331,8 +368,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }; let res = ethereum_node::run( - ðereum_url, - eth_relayer_sender, + ethereum_node.unwrap(), eth_abort_recv, ) .map_err(Error::Ethereum) @@ -342,8 +378,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { drop(aborter); res }); - // wait for the Ethereum fullnode to finish syncing. - eth_relayer_receiver.recv().await.unwrap(); // Shutdown ethereum_node via a message to ensure that the child process // is properly cleaned-up. @@ -422,7 +456,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { config, wasm_dir, broadcaster_sender, - eth_relayer_receiver, + eth_receiver, &db_cache, vp_wasm_compilation_cache, tx_wasm_compilation_cache, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 367a34982e..1b839a6825 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -350,8 +350,9 @@ where Ok(result) => { if result.is_accepted() { tracing::info!( - "all VPs accepted apply_tx storage modification \ - {:#?}", + "all VPs accepted transaction {} storage \ + modification {:#?}", + tx_event["hash"], result ); self.write_log.commit_tx(); @@ -380,8 +381,9 @@ where } } else { tracing::info!( - "some VPs rejected apply_tx storage modification \ - {:#?}", + "some VPs rejected transaction {} storage \ + modification {:#?}", + tx_event["hash"], result.vps_result.rejected_vps ); self.write_log.drop_tx(); @@ -391,7 +393,11 @@ where tx_event["info"] = result.to_string(); } Err(msg) => { - tracing::info!("Transaction failed with: {}", msg); + tracing::info!( + "Transaction {} failed with: {}", + tx_event["hash"], + msg + ); self.write_log.drop_tx(); tx_event["gas_used"] = self .gas_meter diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 5802a5d026..4cba82d4c6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -255,7 +255,7 @@ where config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - ethereum_recv: UnboundedReceiver, + eth_receiver: Option>, db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -306,7 +306,7 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, - ethereum_recv, + ethereum_recv: eth_receiver.unwrap(), }) .expect( "Validator data should have been stored in the \ @@ -325,7 +325,7 @@ where }, }, broadcast_sender, - ethereum_recv, + ethereum_recv: eth_receiver.unwrap(), } } } @@ -564,7 +564,6 @@ where /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { - let mut response = response::Commit::default(); // commit changes from the write-log to storage self.write_log .commit_block(&mut self.storage) @@ -583,8 +582,7 @@ where root, self.storage.last_height, ); - response.data = root.0; - response + response::Commit::default() } /// Validate a transaction request. On success, the transaction will @@ -597,10 +595,9 @@ where ) -> response::CheckTx { let mut response = response::CheckTx::default(); match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => response.log = String::from("Mempool validation passed"), - Err(msg) => { + Ok(_) => {} + Err(_) => { response.code = 1; - response.log = msg.to_string(); } } response diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 4cde93e5de..bf822d047d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -13,7 +13,7 @@ use anoma::types::transaction::hash_tx; use futures::future::FutureExt; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestBeginBlock; -use tokio::sync::mpsc::{UnboundedSender, UnboundedReceiver}; +use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tower::Service; #[cfg(not(feature = "ABCI"))] use tower_abci::{BoxError, Request as Req, Response as Resp}; @@ -21,12 +21,12 @@ use tower_abci::{BoxError, Request as Req, Response as Resp}; use tower_abci_old::{BoxError, Request as Req, Response as Resp}; use super::super::Shell; +use super::super::ethereum_node::events::EthereumEvent; 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; -use crate::node::ledger::ethereum_node::events::EthereumEvent; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -50,7 +50,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - ethereum_recv: UnboundedReceiver, + eth_receiver: Option>, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -64,7 +64,7 @@ impl AbcippShim { config, wasm_dir, broadcast_sender, - ethereum_recv, + eth_receiver, Some(db_cache), vp_wasm_compilation_cache, tx_wasm_compilation_cache, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 705f5f6d73..5c5df367b7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -73,6 +73,7 @@ chrono = "0.4.19" clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" ed25519-consensus = "1.2.0" +ethabi = "17.0.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} hex = "0.4.3" @@ -83,7 +84,7 @@ ibc-abci = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", branch ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abcipp-rebase-master", 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} ics23 = "0.6.7" -itertools = "0.10.0" +itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} parity-wasm = {version = "0.42.2", optional = true} proptest = {version = "1.0.0", optional = true} From 25d8a6d785bc8fa9e6ab48e416f4116cb787f4d0 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 24 Jun 2022 10:34:40 +0200 Subject: [PATCH 0029/1995] [chore]: Formatting and cleanup --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 25 +++--- .../lib/node/ledger/ethereum_node/oracle.rs | 81 ++++++++++--------- apps/src/lib/node/ledger/mod.rs | 49 +++++------ apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 4 files changed, 82 insertions(+), 75 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index c9e47acce1..f3c5b4cbc5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -1,11 +1,11 @@ -pub mod oracle; pub mod events; +pub mod oracle; mod test_tools; use std::ffi::OsString; use thiserror::Error; -use tokio::sync::oneshot::{Sender, Receiver}; +use tokio::sync::oneshot::{Receiver, Sender}; #[derive(Error, Debug)] pub enum Error { @@ -14,13 +14,13 @@ pub enum Error { #[error("{0}")] Runtime(String), #[error( - "The receiver of the Ethereum relayer messages unexpectedly dropped" + "The receiver of the Ethereum relayer messages unexpectedly dropped" )] RelayerReceiverDropped, #[error("The Ethereum Oracle process unexpectedly stopped")] Oracle, #[error( - "Could not read Ethereum network to connect to from env var: {0:?}" + "Could not read Ethereum network to connect to from env var: {0:?}" )] EthereumNetwork(OsString), #[error("Could not decode Ethereum event: {0}")] @@ -29,7 +29,6 @@ pub enum Error { pub type Result = std::result::Result; - /// Run the Ethereum fullnode. If it stops or an abort /// signal is sent, this processes is halted. pub async fn run( @@ -62,7 +61,8 @@ pub async fn run( /// Tools for running a geth fullnode process pub mod eth_fullnode { use tokio::process::{Child, Command}; - use tokio::sync::oneshot::{channel, Sender, Receiver, error::TryRecvError}; + use tokio::sync::oneshot::error::TryRecvError; + use tokio::sync::oneshot::{channel, Receiver, Sender}; use web30::client::Web3; use super::{Error, Result}; @@ -127,10 +127,7 @@ pub mod eth_fullnode { // it takes a brief amount of time to open up the websocket on // geth's end - let client = Web3::new( - url, - std::time::Duration::from_secs(5), - ); + let client = Web3::new(url, std::time::Duration::from_secs(5)); loop { match client.eth_syncing().await { @@ -164,17 +161,17 @@ pub mod eth_fullnode { match self.process.try_wait() { Ok(Some(status)) => { return if status.success() { - Ok(()) + Ok(()) } else { - Err(Error::Runtime(status.to_string())) + Err(Error::Runtime(status.to_string())) }; } - Ok(None) => {}, + Ok(None) => {} Err(err) => return Err(Error::Runtime(err.to_string())), } match self.abort_recv.try_recv() { Ok(()) => return Ok(()), - Err(TryRecvError::Empty) => {}, + Err(TryRecvError::Empty) => {} Err(TryRecvError::Closed) => return Err(Error::Oracle), } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 714afa4d81..0395d763c3 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -47,7 +47,11 @@ impl Drop for Oracle { impl Oracle { /// Initialize a new [`Oracle`] - pub fn new(url: &str, sender: UnboundedSender, abort: Sender<()>) -> Self { + pub fn new( + url: &str, + sender: UnboundedSender, + abort: Sender<()>, + ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), sender, @@ -60,7 +64,8 @@ impl Oracle { /// successfully. If false is returned, the receiver /// has hung up. fn send(&self, events: Vec) -> bool { - events.into_iter() + events + .into_iter() .map(|event| self.sender.send(event)) .all(|res| res.is_ok()) } @@ -72,12 +77,16 @@ impl Oracle { } } -pub async fn run_oracle(url: &str, sender: UnboundedSender, abort_sender: Sender<()>) { +pub async fn run_oracle( + url: &str, + sender: UnboundedSender, + abort_sender: Sender<()>, +) { let oracle = Oracle::new(url, sender, abort_sender); // Initialize our local state. This includes // the latest block height seen and a queue of events // awaiting a certain number of confirmations - let mut latest_block: Uint256 = Default::default(); + let mut latest_block; let mut pending: Vec = Vec::new(); loop { // update the latest block height @@ -87,10 +96,10 @@ pub async fn run_oracle(url: &str, sender: UnboundedSender, abort } if !oracle.connected() { tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" ); - return + return; } }; @@ -98,50 +107,51 @@ pub async fn run_oracle(url: &str, sender: UnboundedSender, abort // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. for sig in signatures::SIGNATURES { let addr = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => { - MINT_CONTRACT.0.clone().into() - } + signatures::SigType::Bridge => MINT_CONTRACT.0.clone().into(), signatures::SigType::Governance => { GOVERNANCE_CONTRACT.0.clone().into() } }; // fetch the events for matching the given signature let mut events = loop { - if let Ok(pending) = oracle.check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), - vec![addr], - vec![sig], - ) - .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ).ok() - }) - .collect::>() - }) { + if let Ok(pending) = oracle + .check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) + .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) + .ok() + }) + .collect::>() + }) + { break pending; } if !oracle.connected() { tracing::info!( "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" + the receiver has hung up. Shutting down" ); - return + return; } }; pending.append(&mut events); if !oracle.send(process_queue(&latest_block, &mut pending)) { tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" ); - return + return; } } } @@ -154,13 +164,12 @@ fn process_queue( latest_block: &Uint256, pending: &mut Vec, ) -> Vec { - let mut pending_tmp: Vec = - Vec::with_capacity(pending.len()); + let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); std::mem::swap(&mut pending_tmp, pending); let mut confirmed = vec![]; for item in pending_tmp.into_iter() { if item.is_confirmed(latest_block) { - confirmed.push(item.event); + confirmed.push(item.event); } else { pending.push(item); } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index f48ec9cd8b..ccbe42e384 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -16,7 +16,8 @@ use std::str::FromStr; use anoma::ledger::governance::storage as gov_storage; use anoma::types::storage::Key; use byte_unit::Byte; -use ethereum_node::{EthereumNode, events::EthereumEvent}; +use ethereum_node::events::EthereumEvent; +use ethereum_node::EthereumNode; use futures::future::TryFutureExt; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -40,7 +41,6 @@ use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; use crate::{config, wasm_loader}; - /// Env. var to set a number of Tokio RT worker threads const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; @@ -187,17 +187,22 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .thread_name(|i| format!("ledger-rayon-worker-{}", i)) .build_global() .unwrap(); - let (ethereum_node, oracle, eth_receiver) = if matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { + let (ethereum_node, oracle, eth_receiver) = if matches!( + config.tendermint.tendermint_mode, + TendermintMode::Validator + ) { // boot up the ethereum node process and wait for it to finish syncing let (eth_sender, eth_receiver) = unbounded_channel(); - let (ethereum_node, abort_sender) = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(EthereumNode::new(&config.ethereum_url())) - .expect("Unable to start the Ethereum fullnode"); - // Start the oracle process to relay data from the ethereum fullnode to the ledger. - // Unfortunately requires a single threaded runtime, so we do this here. + let (ethereum_node, abort_sender) = + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(EthereumNode::new(&config.ethereum_url())) + .expect("Unable to start the Ethereum fullnode"); + // Start the oracle process to relay data from the ethereum fullnode to + // the ledger. Unfortunately requires a single threaded runtime, + // so we do this here. let url = config.ethereum_url(); let oracle = std::thread::spawn(move || { tokio::runtime::Builder::new_current_thread() @@ -205,13 +210,11 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .enable_all() .build() .unwrap() - .block_on( - ethereum_node::oracle::run_oracle( - &url, - eth_sender, - abort_sender, - ) - ) + .block_on(ethereum_node::oracle::run_oracle( + &url, + eth_sender, + abort_sender, + )) }); (Some(ethereum_node), Some(oracle), Some(eth_receiver)) } else { @@ -367,12 +370,10 @@ async fn run_aux( who: "Ethereum", }; - let res = ethereum_node::run( - ethereum_node.unwrap(), - eth_abort_recv, - ) - .map_err(Error::Ethereum) - .await; + let res = + ethereum_node::run(ethereum_node.unwrap(), eth_abort_recv) + .map_err(Error::Ethereum) + .await; tracing::info!("Ethereum fullnode is no longer running."); drop(aborter); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index bf822d047d..6e6670b203 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -20,8 +20,8 @@ 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::super::ethereum_node::events::EthereumEvent; +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; From 77ee4b89b3a0908ba62a0b029f092530d4e7e4ed Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 24 Jun 2022 10:40:35 +0200 Subject: [PATCH 0030/1995] [chore]: Cleanup of some generic code --- .../lib/node/ledger/ethereum_node/events.rs | 214 +++++++++--------- 1 file changed, 102 insertions(+), 112 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index f34bed9ecc..96d2694bac 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -7,9 +7,9 @@ use anoma::types::token::Amount; use ethabi::param_type::ParamType; use ethabi::token::Token; use ethabi::{decode, encode, Uint}; +use itertools::Either; use num256::Uint256; use thiserror::Error; -use itertools::Either; #[derive(Error, Debug)] pub enum Error { @@ -84,18 +84,22 @@ impl PendingEvent { signatures::TRANSFER_TO_NAMADA_SIG => { RawTransfersToNamada::decode(data, TargetAddressType::Native) .map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToNamada(txs.transfers.unwrap_left()), - }) + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToNamada( + txs.transfers.unwrap_left(), + ), + }) } signatures::TRANSFER_TO_ERC_SIG => { RawTransfersToNamada::decode(data, TargetAddressType::Erc20) .map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToErc(txs.transfers.unwrap_right()), - }) + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToErc( + txs.transfers.unwrap_right(), + ), + }) } signatures::VALIDATOR_SET_UPDATE_SIG => { ValidatorSetUpdate::decode(data).map( @@ -123,11 +127,11 @@ impl PendingEvent { signatures::UPGRADED_CONTRACT_SIG => RawChangedContract::decode( data, ) - .map(|RawChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::UpgradedContract { name, address }, - }), + .map(|RawChangedContract { name, address }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpgradedContract { name, address }, + }), signatures::UPDATE_BRIDGE_WHITELIST_SIG => { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { @@ -162,7 +166,8 @@ pub struct KeccakHash(pub [u8; 32]); /// An Ethereum event to be processed by the Anoma ledger #[derive(Debug)] pub enum EthereumEvent { - /// Event transferring batches of ether from Ethereum to wrapped ETH on Anoma + /// Event transferring batches of ether from Ethereum to wrapped ETH on + /// Anoma TransfersToNamada(Vec), /// Event transferring a batch of ERC20 tokens from Ethereum to a wrapped /// version on Anoma @@ -260,7 +265,6 @@ struct RawChangedContract { address: EthAddress, } - /// Event for whitelisting new tokens and their /// rate limits struct UpdateBridgeWhitelist { @@ -279,26 +283,18 @@ enum TargetAddressType { } /// Trait for determining target address type -trait TargetAddress: Debug { - fn address_type() -> TargetAddressType; - fn into_token(&self) -> Token; +pub trait TargetAddress: Debug { + fn to_token(&self) -> Token; } impl TargetAddress for Address { - fn address_type() -> TargetAddressType { - TargetAddressType::Native - } - - fn into_token(&self) -> Token { + fn to_token(&self) -> Token { Token::String(self.encode()) } } impl TargetAddress for EthAddress { - fn address_type() -> TargetAddressType { - TargetAddressType::Erc20 - } - fn into_token(&self) -> Token { + fn to_token(&self) -> Token { Token::Address(self.0.into()) } } @@ -339,58 +335,61 @@ impl RawTransfersToNamada { })?; let sources = sources.parse_eth_address_array()?; - let targets: Either, Vec> = match address_type { - TargetAddressType::Native => Either::Left(targets.parse_address_array()?), - TargetAddressType::Erc20 => Either::Right(targets.parse_eth_address_array()?), + let targets: Either, Vec> = match address_type + { + TargetAddressType::Native => { + Either::Left(targets.parse_address_array()?) + } + TargetAddressType::Erc20 => { + Either::Right(targets.parse_eth_address_array()?) + } }; let amounts = amounts.parse_amount_array()?; if sources.len() != amounts.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - transfer amounts" + transfer amounts" .into(), )) - } else if targets.as_ref().either(| l| l.len(), | r| r.len()) != sources.len() { + } else if targets.as_ref().either(|l| l.len(), |r| r.len()) + != sources.len() + { Err(Error::Decode( "Number of source addresses is different from number of \ - target addresses" + target addresses" .into(), )) } else { - let transfers = match targets { - Either::Left(targets) => Either::Left( - sources - .into_iter() - .zip(targets.into_iter()) - .zip(amounts.into_iter()) - .map(|((source, target), amount)| RawTransferToNamada { - source, - target, - amount, - }) - .collect() - ), - Either::Right(targets) => Either::Right( - sources - .into_iter() - .zip(targets.into_iter()) - .zip(amounts.into_iter()) - .map(|((source, target), amount)| RawTransferToNamada { - source, - target, - amount, - }) - .collect() - ) - }; Ok(Self { - transfers, + transfers: match targets { + Either::Left(targets) => Either::Left(Self::craft_transfers(sources, amounts, targets)), + Either::Right(targets) => Either::Right(Self::craft_transfers(sources, amounts, targets)), + }, nonce: nonce.parse_uint256()?, confirmations: confs.parse_u32()?, }) } } + /// Method that zips together the sources, amounts, and targets + /// into a vector of transfers + fn craft_transfers( + sources: Vec, + amounts: Vec, + targets: Vec + ) -> Vec> { + sources + .into_iter() + .zip(targets.into_iter()) + .zip(amounts.into_iter()) + .map(|((source, target), amount)| RawTransferToNamada { + source, + target, + amount, + }) + .collect() + } + /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's /// ABI serialization scheme. fn encode(self) -> Vec { @@ -399,37 +398,9 @@ impl RawTransfersToNamada { nonce, confirmations, } = self; - let (amounts, sources, targets) = match transfers { - Either::Left(transfers) => { - let amounts: Vec = transfers - .iter() - .map(|RawTransferToNamada { amount, .. }| { - Token::Uint(u64::from(*amount).into()) - }) - .collect(); - let (sources, targets): (Vec, Vec) = transfers - .into_iter() - .map(|RawTransferToNamada { source, target, .. }| { - (Token::Address(source.0.into()), target.into_token()) - }) - .unzip(); - (amounts, sources, targets) - } - Either::Right(transfers) => { - let amounts: Vec = transfers - .iter() - .map(|RawTransferToNamada { amount, .. }| { - Token::Uint(u64::from(*amount).into()) - }) - .collect(); - let (sources, targets): (Vec, Vec) = transfers - .into_iter() - .map(|RawTransferToNamada { source, target, .. }| { - (Token::Address(source.0.into()), target.into_token()) - }) - .unzip(); - (amounts, sources, targets) - } + let [amounts, sources, targets] = match transfers { + Either::Left(transfers) => Self::tokenize_transfers(transfers), + Either::Right(transfers) => Self::tokenize_transfers(transfers), }; encode(&[ @@ -440,6 +411,25 @@ impl RawTransfersToNamada { Token::Uint(confirmations.into()), ]) } + + /// Serializeds a vector of transfers using the ABI scheme in a way + /// that matches how the ethereum smart contracts encodes + /// a batch of transfers in an event. + fn tokenize_transfers(transfers: Vec>) -> [Vec; 3] { + let amounts: Vec = transfers + .iter() + .map(|RawTransferToNamada { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (sources, targets): (Vec, Vec) = transfers + .into_iter() + .map(|RawTransferToNamada { source, target, .. }| { + (Token::Address(source.0.into()), target.to_token()) + }) + .unzip(); + [amounts, sources, targets] + } } impl ValidatorSetUpdate { @@ -455,14 +445,14 @@ impl ValidatorSetUpdate { ], data, ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "ValidatorSetUpdate signature should contain three types" - .into(), - ) - })?; + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "ValidatorSetUpdate signature should contain three types" + .into(), + ) + })?; Ok(Self { nonce: nonce.parse_uint256()?, @@ -530,14 +520,14 @@ impl UpdateBridgeWhitelist { ], data, ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "UpdatedBridgeWhitelist signature should contain three types" - .into(), - ) - })?; + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "UpdatedBridgeWhitelist signature should contain three types" + .into(), + ) + })?; let tokens = tokens.parse_eth_address_array()?; let caps = caps.parse_amount_array()?; @@ -675,9 +665,9 @@ impl Parse for Token { fn parse_keccak(self) -> Result { if let Token::FixedBytes(bytes) = self { - let bytes = bytes.try_into().map_err(|_| Error::Decode( - "Expect 32 bytes for a Keccak hash".into(), - ))?; + let bytes = bytes.try_into().map_err(|_| { + Error::Decode("Expect 32 bytes for a Keccak hash".into()) + })?; Ok(KeccakHash(bytes)) } else { Err(Error::Decode(format!( @@ -779,7 +769,7 @@ mod test_events { &[ParamType::Address], encode(&[Token::Address(erc.0.into())]).as_slice() ) - .expect("Test failed"), + .expect("Test failed"), erc ) } From 20408bc3e6042e4a1548ed0e28dbf636f2546a8b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 24 Jun 2022 10:47:45 +0100 Subject: [PATCH 0031/1995] Removing duplicates from hash.rs --- shared/src/types/hash.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 66753f5653..bac018a7dc 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -70,14 +70,6 @@ impl AsRef<[u8]> for Hash { } } -impl Deref for Hash { - type Target = [u8; 32]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - impl TryFrom<&[u8]> for Hash { type Error = self::Error; @@ -116,9 +108,3 @@ impl Hash { Self(*digest.as_ref()) } } - -impl From for TmHash { - fn from(hash: Hash) -> Self { - TmHash::Sha256(hash.0) - } -} From 9d22d5e9867bcdbc2d80b3edec300b3ca6b3256a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 24 Jun 2022 10:47:52 +0100 Subject: [PATCH 0032/1995] Update Cargo.lock --- Cargo.lock | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a2ee981e9..753ed83c86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1088,6 +1088,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cache-padded" version = "1.2.0" @@ -3650,14 +3661,17 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "6.20.3" +version = "0.6.1+6.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c309a9d2470844aceb9a4a098cf5286154d20596868b75a6b36357d2bb9ca25d" +checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" dependencies = [ "bindgen", + "bzip2-sys", "cc", "glob", "libc", + "libz-sys", + "zstd-sys", ] [[package]] @@ -5558,9 +5572,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c749134fda8bfc90d0de643d59bfc841dcb3ac8a1062e12b6754bd60235c48b3" +checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" dependencies = [ "libc", "librocksdb-sys", @@ -8365,3 +8379,13 @@ dependencies = [ "syn", "synstructure", ] + +[[package]] +name = "zstd-sys" +version = "1.6.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +dependencies = [ + "cc", + "libc", +] From b7b7f2dda820336ae3ab945dcd8d4b23331b646f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 24 Jun 2022 10:58:30 +0100 Subject: [PATCH 0033/1995] Update Cargo.lock --- Cargo.lock | 367 ++++++++++++++++++++++++++--------------------------- 1 file changed, 183 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 753ed83c86..24d0d23964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] @@ -120,7 +120,7 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.3.11", "wasmer", "wasmer-cache", @@ -214,7 +214,7 @@ dependencies = [ "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-log", "tracing-subscriber 0.3.11", "websocket", @@ -282,7 +282,7 @@ dependencies = [ "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "test-log", "toml", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.3.11", ] @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "ark-bls12-381" @@ -526,14 +526,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", @@ -568,15 +568,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" @@ -596,16 +587,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.10", "futures-channel", "futures-core", "futures-io", @@ -614,7 +605,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -665,9 +655,9 @@ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -1020,9 +1010,9 @@ 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 = "byte-tools" @@ -1293,7 +1283,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.26", + "tracing-core 0.1.28", "tracing-error", ] @@ -1443,12 +1433,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", ] [[package]] @@ -1459,20 +1449,20 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.10", "memoffset", + "once_cell", "scopeguard", ] @@ -1489,12 +1479,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", + "once_cell", ] [[package]] @@ -2424,14 +2414,14 @@ 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", "js-sys", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2485,9 +2475,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -2577,8 +2567,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.2", - "tracing 0.1.34", + "tokio-util 0.7.3", + "tracing 0.1.35", ] [[package]] @@ -2605,11 +2595,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", ] @@ -2708,9 +2694,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes 1.1.0", "fnv", @@ -2779,7 +2765,7 @@ dependencies = [ "socket2 0.4.4", "tokio", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", "want", ] @@ -2848,7 +2834,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "derive_more", @@ -2868,8 +2854,8 @@ dependencies = [ "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", - "tracing 0.1.34", + "time 0.3.11", + "tracing 0.1.35", ] [[package]] @@ -2895,14 +2881,14 @@ dependencies = [ "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.34", + "time 0.3.11", + "tracing 0.1.35", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#83dbb07e8e1a51c03681966eaf31653250699196" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master#2b0e40227e62fafd029fc6d9bceb6e62d3e1c583" dependencies = [ "bytes 1.1.0", "prost 0.9.0", @@ -3014,12 +3000,12 @@ 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", + "hashbrown 0.12.1", "serde 1.0.137", ] @@ -3066,9 +3052,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" @@ -3132,9 +3118,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" dependencies = [ "wasm-bindgen", ] @@ -3880,9 +3866,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" dependencies = [ "libc", ] @@ -3910,12 +3896,12 @@ dependencies = [ [[package]] name = "message-io" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6612f460798dabbdbb3d4643d9700a32291cb9a5637f07ad25428030fc06db" +checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", "integer-encoding", "lazy_static 1.4.0", "log 0.4.17", @@ -4030,9 +4016,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", @@ -4208,14 +4194,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", + "memoffset", ] [[package]] @@ -4316,7 +4303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint", - "num-complex 0.4.1", + "num-complex 0.4.2", "num-integer", "num-iter", "num-rational", @@ -4346,9 +4333,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", ] @@ -4387,9 +4374,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", @@ -4504,9 +4491,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.73" +version = "0.9.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" dependencies = [ "autocfg 1.1.0", "cc", @@ -4522,7 +4509,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", ] @@ -4935,9 +4922,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] @@ -5130,9 +5117,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -5240,7 +5227,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]] @@ -5349,7 +5336,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.10", "num_cpus", ] @@ -5392,7 +5379,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "redox_syscall 0.2.13", "thiserror", ] @@ -5466,9 +5453,9 @@ 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", @@ -5493,6 +5480,7 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5598,9 +5586,9 @@ 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", @@ -5643,7 +5631,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.9", + "semver 1.0.10", ] [[package]] @@ -5673,9 +5661,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" [[package]] name = "rusty-fork" @@ -5852,9 +5840,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" [[package]] name = "semver-parser" @@ -6270,22 +6258,23 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.20.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.20.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ - "heck 0.3.3", + "heck 0.4.0", "proc-macro2", "quote", + "rustversion", "syn", ] @@ -6331,9 +6320,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -6400,7 +6389,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6421,7 +6410,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", "zeroize", ] @@ -6449,7 +6438,7 @@ dependencies = [ "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", + "time 0.3.11", "zeroize", ] @@ -6477,14 +6466,14 @@ dependencies = [ "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", + "time 0.3.11", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "flex-error", "serde 1.0.137", @@ -6510,14 +6499,14 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "derive_more", "flex-error", "serde 1.0.137", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6530,13 +6519,13 @@ dependencies = [ "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", + "time 0.3.11", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6547,7 +6536,7 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6564,7 +6553,7 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6581,20 +6570,20 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.11", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "async-trait", "async-tungstenite", "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -6609,9 +6598,9 @@ dependencies = [ "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", "thiserror", - "time 0.3.9", + "time 0.3.11", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", "url 2.2.2", "uuid", "walkdir", @@ -6627,7 +6616,7 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", "hyper 0.14.19", "hyper-proxy", @@ -6642,9 +6631,9 @@ dependencies = [ "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", + "time 0.3.11", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", "url 2.2.2", "uuid", "walkdir", @@ -6653,7 +6642,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master#854eaff344534c5838f8dc4deff456ff0dad5c4a" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6662,7 +6651,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abcipp-rebase-master)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6677,7 +6666,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", + "time 0.3.11", ] [[package]] @@ -6799,9 +6788,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ "itoa", "libc", @@ -6824,7 +6813,7 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.9", + "time 0.3.11", "url 2.2.2", ] @@ -6845,14 +6834,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.2" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -6907,9 +6896,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", @@ -6958,9 +6947,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 0.2.9", @@ -7031,16 +7020,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] @@ -7079,7 +7068,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -7097,9 +7086,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", @@ -7110,16 +7099,16 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.2", + "tokio-util 0.7.3", "tower-layer", "tower-service", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#25a0c673ea6748730f86f7f20c933d0f07b50621" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abcipp-rebase-master#6cd24d2a91011635929bea5c1ae05d8b4f4a81a9" dependencies = [ "bytes 1.1.0", "futures 0.3.21", @@ -7169,9 +7158,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" @@ -7186,15 +7175,15 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 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.26", + "tracing-core 0.1.28", ] [[package]] @@ -7228,11 +7217,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static 1.4.0", + "once_cell", "valuable", ] @@ -7242,7 +7231,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.34", + "tracing 0.1.35", "tracing-subscriber 0.2.25", ] @@ -7253,7 +7242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ "pin-project 1.0.10", - "tracing 0.1.34", + "tracing 0.1.35", ] [[package]] @@ -7273,7 +7262,7 @@ checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static 1.4.0", "log 0.4.17", - "tracing-core 0.1.26", + "tracing-core 0.1.28", ] [[package]] @@ -7284,7 +7273,7 @@ checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", "thread_local 1.1.4", - "tracing-core 0.1.26", + "tracing-core 0.1.28", ] [[package]] @@ -7300,8 +7289,8 @@ dependencies = [ "sharded-slab", "smallvec 1.8.0", "thread_local 1.1.4", - "tracing 0.1.34", - "tracing-core 0.1.26", + "tracing 0.1.35", + "tracing-core 0.1.28", "tracing-log", ] @@ -7468,9 +7457,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" @@ -7583,7 +7572,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]] @@ -7708,9 +7697,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7718,9 +7707,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static 1.4.0", @@ -7733,9 +7722,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7745,9 +7734,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7755,9 +7744,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -7768,9 +7757,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-encoder" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +dependencies = [ + "leb128", +] [[package]] name = "wasm-timer" @@ -7859,7 +7857,7 @@ dependencies = [ "rayon", "smallvec 1.8.0", "target-lexicon", - "tracing 0.1.34", + "tracing 0.1.35", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7934,7 +7932,7 @@ dependencies = [ "rkyv", "serde 1.0.137", "tempfile", - "tracing 0.1.34", + "tracing 0.1.35", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -8025,20 +8023,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "41.0.0" +version = "42.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f882898b8b817cc4edc16aa3692fdc087b356edc8cc0c2164f5b5181e31c3870" +checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b3b9b3e39e66c7fd3f8be785e74444d216260f491e93369e317ed6482ff80f" +checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" dependencies = [ "wast", ] @@ -8057,7 +8056,7 @@ dependencies = [ "globset", "lazy_static 1.4.0", "log 0.4.17", - "nix 0.20.0", + "nix 0.20.2", "notify", "walkdir", "winapi 0.3.9", @@ -8065,9 +8064,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" dependencies = [ "js-sys", "wasm-bindgen", From 2f4966956cc564ea716b421873edc0ee1e703e46 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 24 Jun 2022 14:02:16 +0100 Subject: [PATCH 0034/1995] Fix ledger tests --- tests/src/e2e/ledger_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 90f779511e..1b27da09f4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -486,7 +486,6 @@ fn invalid_transactions() -> Result<()> { client.exp_string(r#""code": "1"#)?; client.assert_success(); - let mut ledger = bg_ledger.foreground(); ledger.exp_string("some VPs rejected transaction")?; // Wait to commit a block From b98c3e0b8cc4c519d3688582da33e0e9334b5171 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 27 Jun 2022 14:14:56 +0100 Subject: [PATCH 0035/1995] Add MultiSigned --- shared/src/proto/mod.rs | 3 ++- shared/src/proto/types.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..adacda806e 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -4,7 +4,8 @@ pub mod generated; mod types; pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, + Dkg, Error, Intent, IntentGossipMessage, IntentId, MultiSigned, Signed, + SignedTxData, Tx, }; #[cfg(test)] diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..ff9fc32dc2 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -60,6 +60,22 @@ pub struct Signed { pub sig: common::Signature, } +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct MultiSigned { + /// Arbitrary data to be signed + pub data: T, + /// The signature of the data + pub sigs: Vec, +} + impl PartialEq for Signed where T: BorshSerialize + BorshDeserialize + PartialEq, From 6d42e6b520f6c5636937e897d4d8aa994c8de7ca Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 27 Jun 2022 18:35:17 +0100 Subject: [PATCH 0036/1995] Add ethereum_events.rs --- shared/src/types/ethereum_events.rs | 52 +++++++++++++++++++++++++++++ shared/src/types/mod.rs | 1 + 2 files changed, 53 insertions(+) create mode 100644 shared/src/types/ethereum_events.rs diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs new file mode 100644 index 0000000000..a861b58932 --- /dev/null +++ b/shared/src/types/ethereum_events.rs @@ -0,0 +1,52 @@ +//! Types to do with interfacing with the Ethereum blockchain +use std::fmt::Debug; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + +use crate::proto::MultiSigned; +use crate::types::address::Address; +use crate::types::token::Amount; + +/// An Ethereum event to be processed by the Anoma ledger +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum EthereumEvent { + /// Event transferring batches of ether from Ethereum to wrapped ETH on + /// Anoma + TransfersToNamada(Vec), +} + +/// Representation of address on Ethereum +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthAddress(pub [u8; 20]); + +/// An event transferring some kind of value from Ethereum to Anoma +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct TransferToNamada { + /// Quantity of ether in the transfer + pub amount: Amount, + /// Address on Ethereum of the asset + pub asset: EthereumAsset, + /// The Namada address receiving wrapped assets on Anoma + pub receiver: Address, +} + +/// Represents Ethereum assets on the Ethereum blockchain +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum EthereumAsset { + /// Native ETH + Eth, + /// An ERC20 token and the address of its contract + ERC20(EthAddress), +} + +/// This is created by the block proposer based on the Ethereum events included +/// in the vote extensions of the previous Tendermint round +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedEthEvent { + /// Address and voting power of the signing validators + pub signers: Vec<(Address, u64)>, + /// Events as signed by validators + pub event: MultiSigned, +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4b8d7288ca..748475a423 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod address; pub mod chain; pub mod dylib; +pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; From b6712dc71b276cd8154bb6678de546fef355a527 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 27 Jun 2022 18:35:27 +0100 Subject: [PATCH 0037/1995] Add ProtocolTxType::EthereumEvents --- shared/src/types/transaction/protocol.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index 54198699e8..b24ad18d49 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -33,6 +33,7 @@ mod protocol_txs { use super::*; use crate::proto::Tx; + use crate::types::ethereum_events::MultiSignedEthEvent; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; @@ -76,9 +77,8 @@ mod protocol_txs { DKG(DkgMessage), /// Tx requesting a new DKG session keypair NewDkgKeypair(Tx), - /// Aggregation of Ethereum state changes - /// voted on by validators in last block - EthereumStateUpdate(Tx), + /// Ethereum events contained in vote extensions + EthereumEvents(Vec), } impl ProtocolTxType { From fdcaa1e9c5881726a0c5f90963a7d5b1a450f332 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 09:37:05 +0100 Subject: [PATCH 0038/1995] Derive PartialEq + Eq for EthereumEvent --- shared/src/types/ethereum_events.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index a861b58932..ff4ca64ace 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -8,7 +8,9 @@ use crate::types::address::Address; use crate::types::token::Amount; /// An Ethereum event to be processed by the Anoma ledger -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub enum EthereumEvent { /// Event transferring batches of ether from Ethereum to wrapped ETH on /// Anoma @@ -17,12 +19,14 @@ pub enum EthereumEvent { /// Representation of address on Ethereum #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct EthAddress(pub [u8; 20]); /// An event transferring some kind of value from Ethereum to Anoma -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct TransferToNamada { /// Quantity of ether in the transfer pub amount: Amount, @@ -33,7 +37,9 @@ pub struct TransferToNamada { } /// Represents Ethereum assets on the Ethereum blockchain -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub enum EthereumAsset { /// Native ETH Eth, From f26a88c4eb4020cb7c1f0759486e2b11a6bf7048 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 14:30:44 +0100 Subject: [PATCH 0039/1995] Remove EthereumAsset::Eth variant --- shared/src/types/ethereum_events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ff4ca64ace..04bff44def 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -41,8 +41,6 @@ pub struct TransferToNamada { Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, )] pub enum EthereumAsset { - /// Native ETH - Eth, /// An ERC20 token and the address of its contract ERC20(EthAddress), } From fdb8a8773b3b859394c37a73dcd49d5b87766dc9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 15:59:59 +0100 Subject: [PATCH 0040/1995] Add EthereumEvent::hash fn --- shared/src/types/ethereum_events.rs | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 04bff44def..85c8d2bc43 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use sha2::{Digest, Sha256}; use crate::proto::MultiSigned; use crate::types::address::Address; @@ -17,6 +18,16 @@ pub enum EthereumEvent { TransfersToNamada(Vec), } +impl EthereumEvent { + fn hash(&self) -> Result<[u8; 32], std::io::Error> { + let bytes = self.try_to_vec()?; + let mut hasher = Sha256::new(); + hasher.update(&bytes); + let hash: [u8; 32] = hasher.finalize().into(); + Ok(hash) + } +} + /// Representation of address on Ethereum #[derive( Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchema, @@ -54,3 +65,24 @@ pub struct MultiSignedEthEvent { /// Events as signed by validators pub event: MultiSigned, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ethereum_event_hash() { + let event = EthereumEvent::TransfersToNamada(vec![]); + + let hash = event.hash().unwrap(); + + assert_eq!( + hash, + [ + 136, 85, 80, 138, 173, 225, 110, 197, 115, 210, 30, 106, 72, + 93, 253, 10, 118, 36, 8, 92, 26, 20, 181, 236, 221, 100, 133, + 222, 12, 104, 57, 164 + ] + ); + } +} From 66c4c99a378bf4957aea732c17537eb3bbdf7dfa Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 16:46:21 +0100 Subject: [PATCH 0041/1995] Add docstring for EthereumEvent::hash --- shared/src/types/ethereum_events.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 85c8d2bc43..2e4a625c26 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -19,6 +19,7 @@ pub enum EthereumEvent { } impl EthereumEvent { + /// SHA256 of the Borsh serialization of the [`EthereumEvent`] fn hash(&self) -> Result<[u8; 32], std::io::Error> { let bytes = self.try_to_vec()?; let mut hasher = Sha256::new(); From 128cc493c9db1eccc049d0fb8a45eb100bb8ec95 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 16:54:32 +0100 Subject: [PATCH 0042/1995] Add EthereumEventNonce --- shared/src/types/ethereum_events.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 2e4a625c26..89f3c54336 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -8,6 +8,12 @@ use crate::proto::MultiSigned; use crate::types::address::Address; use crate::types::token::Amount; +/// Each event has a nonce set by the Ethereum smart contract that emitted it. +#[derive( + Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthereumEventNonce(u64); + /// An Ethereum event to be processed by the Anoma ledger #[derive( Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, @@ -15,7 +21,7 @@ use crate::types::token::Amount; pub enum EthereumEvent { /// Event transferring batches of ether from Ethereum to wrapped ETH on /// Anoma - TransfersToNamada(Vec), + TransfersToNamada(Vec, EthereumEventNonce), } impl EthereumEvent { @@ -73,16 +79,17 @@ mod tests { #[test] fn test_ethereum_event_hash() { - let event = EthereumEvent::TransfersToNamada(vec![]); + let nonce = EthereumEventNonce(123); + let event = EthereumEvent::TransfersToNamada(vec![], nonce); let hash = event.hash().unwrap(); assert_eq!( hash, [ - 136, 85, 80, 138, 173, 225, 110, 197, 115, 210, 30, 106, 72, - 93, 253, 10, 118, 36, 8, 92, 26, 20, 181, 236, 221, 100, 133, - 222, 12, 104, 57, 164 + 94, 227, 170, 45, 164, 208, 161, 180, 203, 148, 96, 173, 90, + 30, 102, 44, 30, 187, 124, 90, 117, 204, 19, 188, 7, 104, 19, + 46, 13, 62, 203, 243 ] ); } From 76aa3ac67f1c7baec69c0feeeb11f0ccb0587f3e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 16:59:47 +0100 Subject: [PATCH 0043/1995] Use our standard Hash type --- shared/src/types/ethereum_events.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 89f3c54336..bab9250a8f 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,10 +2,10 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use sha2::{Digest, Sha256}; use crate::proto::MultiSigned; use crate::types::address::Address; +use crate::types::hash::Hash; use crate::types::token::Amount; /// Each event has a nonce set by the Ethereum smart contract that emitted it. @@ -25,13 +25,10 @@ pub enum EthereumEvent { } impl EthereumEvent { - /// SHA256 of the Borsh serialization of the [`EthereumEvent`] - fn hash(&self) -> Result<[u8; 32], std::io::Error> { + /// SHA256 of the Borsh serialization of the [`EthereumEvent`]. + fn hash(&self) -> Result { let bytes = self.try_to_vec()?; - let mut hasher = Sha256::new(); - hasher.update(&bytes); - let hash: [u8; 32] = hasher.finalize().into(); - Ok(hash) + Ok(Hash::sha256(&bytes)) } } @@ -86,11 +83,11 @@ mod tests { assert_eq!( hash, - [ + Hash([ 94, 227, 170, 45, 164, 208, 161, 180, 203, 148, 96, 173, 90, 30, 102, 44, 30, 187, 124, 90, 117, 204, 19, 188, 7, 104, 19, 46, 13, 62, 203, 243 - ] + ]) ); } } From e334ba86fdbca300eeb1e61ad7b3367377151a8d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 17:11:08 +0100 Subject: [PATCH 0044/1995] Add fraction crate dependency to anoma crate --- Cargo.lock | 11 +++++++++++ shared/Cargo.toml | 1 + 2 files changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 24d0d23964..ab032bbfae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,7 @@ dependencies = [ "ed25519-consensus", "ferveo", "ferveo-common", + "fraction", "group-threshold-cryptography", "hex", "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", @@ -2207,6 +2208,16 @@ dependencies = [ "percent-encoding 2.1.0", ] +[[package]] +name = "fraction" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c42ca58f4e486c530d93c7f74ab469293b20d7f86b478ca9d702a2f95fed61" +dependencies = [ + "lazy_static 1.4.0", + "num", +] + [[package]] name = "fs_extra" version = "1.2.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 4cfeaf14b8..ae93afad1a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ derivative = "2.2.0" ed25519-consensus = "1.2.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} +fraction = "0.11.0" 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. From a716f6c42025a727312cb4b202c2abc13ab4b9ca Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 17:20:18 +0100 Subject: [PATCH 0045/1995] Add FractionalVotingPower type Not compiling due to inability to derive BorshSchema --- shared/src/types/ethereum_events.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index bab9250a8f..b9b7a0b39b 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -60,12 +60,19 @@ pub enum EthereumAsset { ERC20(EthAddress), } +/// A fraction of the total voting power. This should always be a reduced +/// fraction that is between zero and one inclusive. +#[derive( + Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchema, +)] +struct FractionalVotingPower(fraction::Fraction); + /// This is created by the block proposer based on the Ethereum events included /// in the vote extensions of the previous Tendermint round #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedEthEvent { /// Address and voting power of the signing validators - pub signers: Vec<(Address, u64)>, + pub signers: Vec<(Address, FractionalVotingPower)>, /// Events as signed by validators pub event: MultiSigned, } From e04c0bd2f298667db325bf04cafc30c611185d64 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 29 Jun 2022 17:27:52 +0100 Subject: [PATCH 0046/1995] Make FractionalVotingPower type just be (u64, u64) So that it's Borsh serdelizable --- shared/src/types/ethereum_events.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index b9b7a0b39b..6d41a5c3e4 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -65,7 +65,13 @@ pub enum EthereumAsset { #[derive( Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchema, )] -struct FractionalVotingPower(fraction::Fraction); +pub struct FractionalVotingPower(u64, u64); + +impl From for fraction::Fraction { + fn from(fvp: FractionalVotingPower) -> Self { + fraction::Fraction::new(fvp.0, fvp.1) + } +} /// This is created by the block proposer based on the Ethereum events included /// in the vote extensions of the previous Tendermint round From bc9dd2ef729d62a1900bbd73e685b84ebd19bf20 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 30 Jun 2022 16:52:11 +0200 Subject: [PATCH 0047/1995] [chore]: Added tests for the oracle and fixed bugs that it uncovered --- Cargo.lock | 1 + Makefile | 2 +- apps/Cargo.toml | 1 + apps/src/lib/config/mod.rs | 2 +- .../lib/node/ledger/ethereum_node/events.rs | 415 ++++++++---------- apps/src/lib/node/ledger/ethereum_node/mod.rs | 42 +- .../lib/node/ledger/ethereum_node/oracle.rs | 326 +++++++++++++- .../node/ledger/ethereum_node/test_tools.rs | 130 ++++-- apps/src/lib/node/ledger/mod.rs | 111 +++-- .../lib/node/ledger/shell/finalize_block.rs | 14 +- apps/src/lib/node/ledger/shell/mod.rs | 26 +- .../lib/node/ledger/shell/prepare_proposal.rs | 6 +- .../lib/node/ledger/shell/process_proposal.rs | 20 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- shared/src/types/ethereum_events.rs | 126 ++++++ shared/src/types/mod.rs | 1 + wasm/checksums.json | 36 +- wasm/tx_template/Cargo.lock | 252 ++++++++++- wasm/vp_template/Cargo.lock | 252 ++++++++++- wasm/wasm_source/Cargo.lock | 252 ++++++++++- 20 files changed, 1601 insertions(+), 416 deletions(-) create mode 100644 shared/src/types/ethereum_events.rs diff --git a/Cargo.lock b/Cargo.lock index 2e65e6f668..c0528bb092 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,7 @@ dependencies = [ "byteorder", "cargo-watch", "clap 3.0.0-beta.2", + "clarity", "color-eyre", "config", "curl", diff --git a/Makefile b/Makefile index 52d9466bca..4ab77d59cd 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ 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" && \ + --features "std testing ABCI-plus-plus eth-fullnode" && \ $(cargo) +$(nightly) clippy --all-targets \ --manifest-path ./proof_of_stake/Cargo.toml \ --features "testing" && \ diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 819f266d0b..cab9e3ff94 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -78,6 +78,7 @@ byte-unit = "4.0.13" byteorder = "1.4.2" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} +clarity = "0.5.1" color-eyre = "0.5.10" config = "0.11.0" curl = "0.4.43" diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 838821e203..873e3cce3f 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -45,7 +45,7 @@ pub const TENDERMINT_DIR: &str = "tendermint"; /// Chain-specific Anoma DB. Nested in chain dirs. pub const DB_DIR: &str = "db"; /// Websocket address for Ethereum fullnode RPC -pub const ETHEREUM_URL: &str = "ws://127.0.0.1:8546"; +pub const ETHEREUM_URL: &str = "http://127.0.0.1:8545"; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 96d2694bac..1afa36c189 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -3,11 +3,16 @@ use std::fmt::Debug; use std::str::FromStr; use anoma::types::address::Address; +use anoma::types::ethereum_events::{ + EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, + TransferToEthereum, TransferToNamada, Uint, +}; use anoma::types::token::Amount; use ethabi::param_type::ParamType; use ethabi::token::Token; -use ethabi::{decode, encode, Uint}; -use itertools::Either; +use ethabi::decode; +#[cfg(test)] +use ethabi::encode; use num256::Uint256; use thiserror::Error; @@ -22,7 +27,7 @@ pub type Result = std::result::Result; pub mod signatures { pub const TRANSFER_TO_NAMADA_SIG: &str = "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; - pub const TRANSFER_TO_ERC_SIG: &str = + pub const TRANSFER_TO_ETHEREUM_SIG: &str = "TransferToErc(uint256,address[],address[],uint256[],uint32)"; pub const VALIDATOR_SET_UPDATE_SIG: &str = "ValidatorSetUpdate(uint256,bytes32,bytes32)"; @@ -32,7 +37,7 @@ pub mod signatures { "UpdateBridgeWhiteList(uint256,address[],uint256[])"; pub const SIGNATURES: [&str; 6] = [ TRANSFER_TO_NAMADA_SIG, - TRANSFER_TO_ERC_SIG, + TRANSFER_TO_ETHEREUM_SIG, VALIDATOR_SET_UPDATE_SIG, NEW_CONTRACT_SIG, UPGRADED_CONTRACT_SIG, @@ -49,7 +54,7 @@ pub mod signatures { impl From<&str> for SigType { fn from(sig: &str) -> Self { match sig { - TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ERC_SIG => SigType::Bridge, + TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => SigType::Bridge, _ => SigType::Governance, } } @@ -58,7 +63,7 @@ pub mod signatures { /// An event waiting for a certain number of confirmations /// before being sent to the ledger -pub struct PendingEvent { +pub(super) struct PendingEvent { /// number of confirmations to consider this event finalized confirmations: Uint256, /// the block height from which this event originated @@ -67,6 +72,35 @@ pub struct PendingEvent { pub event: EthereumEvent, } +/// Event emitted with the validator set changes +pub struct ValidatorSetUpdate { + /// A monotonically increasing nonce + nonce: Uint, + /// Hash of the validators in the bridge contract + bridge_validator_hash: KeccakHash, + /// Hash of the validators in the governance contract + governance_validator_hash: KeccakHash, +} + +/// Event indicating a new smart contract has been +/// deployed or upgraded on Ethereum +pub(super) struct ChangedContract { + /// Name of the contract + pub(super) name: String, + /// Address of the contract on Ethereum + pub (super) address: EthAddress, +} + +/// Event for whitelisting new tokens and their +/// rate limits +struct UpdateBridgeWhitelist { + /// A monotonically increasing nonce + nonce: Uint, + /// Tokens to be allowed to be transferred across the bridge + whitelist: Vec, +} + + impl PendingEvent { /// Decodes bytes into an [`EthereumEvent`] based on the signature. /// This is is turned into a [`PendingEvent`] along with the block @@ -82,22 +116,22 @@ impl PendingEvent { ) -> Result { match signature { signatures::TRANSFER_TO_NAMADA_SIG => { - RawTransfersToNamada::decode(data, TargetAddressType::Native) + RawTransfersToNamada::decode(data) .map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, event: EthereumEvent::TransfersToNamada( - txs.transfers.unwrap_left(), + txs.transfers, ), }) } - signatures::TRANSFER_TO_ERC_SIG => { - RawTransfersToNamada::decode(data, TargetAddressType::Erc20) + signatures::TRANSFER_TO_ETHEREUM_SIG => { + RawTransfersToEthereum::decode(data) .map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::TransfersToErc( - txs.transfers.unwrap_right(), + event: EthereumEvent::TransfersToEthereum( + txs.transfers, ), }) } @@ -118,16 +152,16 @@ impl PendingEvent { }, ) } - signatures::NEW_CONTRACT_SIG => RawChangedContract::decode(data) - .map(|RawChangedContract { name, address }| PendingEvent { + signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) + .map(|ChangedContract { name, address }| PendingEvent { confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::NewContract { name, address }, }), - signatures::UPGRADED_CONTRACT_SIG => RawChangedContract::decode( + signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode( data, ) - .map(|RawChangedContract { name, address }| PendingEvent { + .map(|ChangedContract { name, address }| PendingEvent { confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpgradedContract { name, address }, @@ -155,171 +189,40 @@ impl PendingEvent { } } -/// Representation of address on Ethereum -#[derive(Clone, Debug, PartialEq)] -pub struct EthAddress(pub [u8; 20]); - -/// A Keccak hash -#[derive(Clone, Debug, PartialEq)] -pub struct KeccakHash(pub [u8; 32]); - -/// An Ethereum event to be processed by the Anoma ledger -#[derive(Debug)] -pub enum EthereumEvent { - /// Event transferring batches of ether from Ethereum to wrapped ETH on - /// Anoma - TransfersToNamada(Vec), - /// Event transferring a batch of ERC20 tokens from Ethereum to a wrapped - /// version on Anoma - TransfersToErc(Vec), - /// Event indication that the validator set has been updated - /// in the governance contract - ValidatorSetUpdate { - /// Monotonically increasing nonce - nonce: Uint, - /// Hash of the validators in the bridge contract - bridge_validator_hash: KeccakHash, - /// Hash of the validators in the governance contract - governance_validator_hash: KeccakHash, - }, - /// Event indication that a new smart contract has been - /// deployed - NewContract { - /// Name of the contract - name: String, - /// Address of the contract on Ethereum - address: EthAddress, - }, - /// Event indicating that a smart contract has been updated - UpgradedContract { - /// Name of the contract - name: String, - /// Address of the contract on Ethereum - address: EthAddress, - }, - /// Event indication a new Ethereum based token has been whitelisted for - /// transfer across the bridge - UpdateBridgeWhitelist { - /// Monotonically increasing nonce - nonce: Uint, - /// Tokens to be allowed to be transferred across the bridge - whitelist: Vec, - }, -} - -/// An event transferring some kind of value from Ethereum to Anoma -#[derive(Debug)] -pub struct RawTransferToNamada { - /// Quantity of ether in the transfer - pub amount: Amount, - /// Address paying the ether - pub source: EthAddress, - /// The address receiving wrapped assets on Anoma - pub target: T, -} - -/// Type alias for transferring ether to wrapped ETH -pub type TransferToNamada = RawTransferToNamada
; -/// Type alias for transferring ERC20 to wrapped version on Anoma -pub type TransferToErc = RawTransferToNamada; - -/// struct for whitelisting a token from Ethereum. -/// Includes the address of issuing contract and -/// a cap on the max amount of this token allowed to be -/// held by the bridge. -#[derive(Debug)] -pub struct TokenWhitelist { - /// Address of Ethereum smart contract issuing token - pub token: EthAddress, - /// Maximum amount of token allowed on the bridge - pub cap: Amount, -} - -/// A batch of [`RawTransferToNamada`] from an Ethereum event -struct RawTransfersToNamada { +/// A batch of [`TransferToNamada`] from an Ethereum event +pub(super) struct RawTransfersToNamada { /// A list of transfers - pub transfers: Either, Vec>, + pub transfers: Vec, /// A monotonically increasing nonce + #[allow(dead_code)] pub nonce: Uint, /// The number of confirmations needed to consider this batch /// finalized pub confirmations: u32, } -/// Event emitted with the validator set changes -struct ValidatorSetUpdate { - /// A monotonically increasing nonce - nonce: Uint, - /// Hash of the validators in the bridge contract - bridge_validator_hash: KeccakHash, - /// Hash of the validators in the governance contract - governance_validator_hash: KeccakHash, -} - -/// Event indicating a new smart contract has been -/// deployed or upgraded on Ethereum -struct RawChangedContract { - /// Name of the contract - name: String, - /// Address of the contract on Ethereum - address: EthAddress, -} - -/// Event for whitelisting new tokens and their -/// rate limits -struct UpdateBridgeWhitelist { +/// A batch of [`TransferToNamada`] from an Ethereum event +pub(super) struct RawTransfersToEthereum { + /// A list of transfers + pub transfers: Vec, /// A monotonically increasing nonce - nonce: Uint, - /// Tokens to be allowed to be transferred across the bridge - whitelist: Vec, -} - -/// Type of address to transfer to on Anoma -enum TargetAddressType { - /// Output of the bridge will be wrapped ETH - Native, - /// Output of the bridge will be a wrapped ERC20 token - Erc20, -} - -/// Trait for determining target address type -pub trait TargetAddress: Debug { - fn to_token(&self) -> Token; -} - -impl TargetAddress for Address { - fn to_token(&self) -> Token { - Token::String(self.encode()) - } -} - -impl TargetAddress for EthAddress { - fn to_token(&self) -> Token { - Token::Address(self.0.into()) - } + #[allow(dead_code)] + pub nonce: Uint, + /// The number of confirmations needed to consider this batch + /// finalized + pub confirmations: u32, } impl RawTransfersToNamada { /// Parse ABI serialized data from an Ethereum event into /// an instance of [`RawTransfersToNamada`] - fn decode(data: &[u8], address_type: TargetAddressType) -> Result { - let name = match address_type { - TargetAddressType::Native => "TransferToNamada", - TargetAddressType::Erc20 => "TransferToErc", - }; + fn decode(data: &[u8]) -> Result { - let [nonce, sources, targets, amounts, confs]: [Token; 5] = decode( + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( &[ ParamType::Uint(256), ParamType::Array(Box::new(ParamType::Address)), - match address_type { - TargetAddressType::Native => { - ParamType::Array(Box::new(ParamType::String)) - } - TargetAddressType::Erc20 => { - ParamType::Array(Box::new(ParamType::Address)) - } - }, + ParamType::Array(Box::new(ParamType::String)), ParamType::Array(Box::new(ParamType::Uint(256))), ParamType::Uint(32), ], @@ -328,32 +231,21 @@ impl RawTransfersToNamada { .map_err(|err| Error::Decode(format!("{:?}", err)))? .try_into() .map_err(|_| { - Error::Decode(format!( - "{} signature should contain five types", - name - )) + Error::Decode( + "TransferToNamada signature should contain five types".to_string(), + ) })?; - let sources = sources.parse_eth_address_array()?; - let targets: Either, Vec> = match address_type - { - TargetAddressType::Native => { - Either::Left(targets.parse_address_array()?) - } - TargetAddressType::Erc20 => { - Either::Right(targets.parse_eth_address_array()?) - } - }; + let assets = assets.parse_eth_address_array()?; + let receivers = receivers.parse_address_array()?; let amounts = amounts.parse_amount_array()?; - if sources.len() != amounts.len() { + if assets.len() != amounts.len() { Err(Error::Decode( "Number of source addresses is different from number of \ transfer amounts" .into(), )) - } else if targets.as_ref().either(|l| l.len(), |r| r.len()) - != sources.len() - { + } else if receivers.len() != assets.len() { Err(Error::Decode( "Number of source addresses is different from number of \ target addresses" @@ -361,74 +253,138 @@ impl RawTransfersToNamada { )) } else { Ok(Self { - transfers: match targets { - Either::Left(targets) => Either::Left(Self::craft_transfers(sources, amounts, targets)), - Either::Right(targets) => Either::Right(Self::craft_transfers(sources, amounts, targets)), - }, + transfers: assets + .into_iter() + .zip(receivers.into_iter()) + .zip(amounts.into_iter()) + .map(|((asset, receiver), amount)| TransferToNamada { + amount, + asset, + receiver, + }) + .collect(), nonce: nonce.parse_uint256()?, confirmations: confs.parse_u32()?, }) } } - /// Method that zips together the sources, amounts, and targets - /// into a vector of transfers - fn craft_transfers( - sources: Vec, - amounts: Vec, - targets: Vec - ) -> Vec> { - sources - .into_iter() - .zip(targets.into_iter()) - .zip(amounts.into_iter()) - .map(|((source, target), amount)| RawTransferToNamada { - source, - target, - amount, - }) - .collect() - } - /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's /// ABI serialization scheme. + #[cfg(test)] fn encode(self) -> Vec { let RawTransfersToNamada { transfers, nonce, confirmations, } = self; - let [amounts, sources, targets] = match transfers { - Either::Left(transfers) => Self::tokenize_transfers(transfers), - Either::Right(transfers) => Self::tokenize_transfers(transfers), - }; + let amounts: Vec = transfers + .iter() + .map(|TransferToNamada { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (assets, receivers): (Vec, Vec) = transfers + .into_iter() + .map(|TransferToNamada { asset, receiver, .. }| { + (Token::Address(asset.0.into()), Token::String(receiver.to_string())) + }) + .unzip(); encode(&[ Token::Uint(nonce.into()), - Token::Array(sources), - Token::Array(targets), + Token::Array(assets), + Token::Array(receivers), Token::Array(amounts), Token::Uint(confirmations.into()), ]) } +} + +impl RawTransfersToEthereum { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`RawTransfersToEthereum`] + fn decode(data: &[u8]) -> Result { + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "TransferToERC signature should contain five types".to_string() + ) + })?; - /// Serializeds a vector of transfers using the ABI scheme in a way - /// that matches how the ethereum smart contracts encodes - /// a batch of transfers in an event. - fn tokenize_transfers(transfers: Vec>) -> [Vec; 3] { + let assets = assets.parse_eth_address_array()?; + let receivers = receivers.parse_eth_address_array()?; + let amounts = amounts.parse_amount_array()?; + if assets.len() != amounts.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ + transfer amounts" + .into(), + )) + } else if receivers.len() != assets.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ + target addresses" + .into(), + )) + } else { + Ok(Self { + transfers: assets + .into_iter() + .zip(receivers.into_iter()) + .zip(amounts.into_iter()) + .map(|((asset, receiver), amount)| TransferToEthereum { + amount, + asset, + receiver, + }) + .collect(), + nonce: nonce.parse_uint256()?, + confirmations: confs.parse_u32()?, + }) + } + } + + /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + pub(super) fn encode(self) -> Vec { + let RawTransfersToEthereum { + transfers, + nonce, + confirmations, + } = self; let amounts: Vec = transfers .iter() - .map(|RawTransferToNamada { amount, .. }| { + .map(|TransferToEthereum { amount, .. }| { Token::Uint(u64::from(*amount).into()) }) .collect(); - let (sources, targets): (Vec, Vec) = transfers + let (assets, receivers): (Vec, Vec) = transfers .into_iter() - .map(|RawTransferToNamada { source, target, .. }| { - (Token::Address(source.0.into()), target.to_token()) + .map(|TransferToEthereum { asset, receiver, .. }| { + (Token::Address(asset.0.into()), Token::Address(receiver.0.into())) }) .unzip(); - [amounts, sources, targets] + + encode(&[ + Token::Uint(nonce.into()), + Token::Array(assets), + Token::Array(receivers), + Token::Array(amounts), + Token::Uint(confirmations.into()), + ]) } } @@ -464,6 +420,7 @@ impl ValidatorSetUpdate { /// Serialize an instance [`ValidatorSetUpdate`] using Ethereum's /// ABI serialization scheme. + #[cfg(test)] fn encode(self) -> Vec { let ValidatorSetUpdate { nonce, @@ -472,16 +429,16 @@ impl ValidatorSetUpdate { } = self; encode(&[ - Token::Uint(nonce), + Token::Uint(nonce.into()), Token::FixedBytes(bridge_validator_hash.0.into()), Token::FixedBytes(governance_validator_hash.0.into()), ]) } } -impl RawChangedContract { +impl ChangedContract { /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`RawChangedContract`] + /// an instance of [`ChangedContract`] fn decode(data: &[u8]) -> Result { let [name, address]: [Token; 2] = decode(&[ParamType::String, ParamType::Address], data) @@ -500,10 +457,11 @@ impl RawChangedContract { }) } - /// Serialize an instance [`RawChangedContract`] using Ethereum's + /// Serialize an instance [`ChangedContract`] using Ethereum's /// ABI serialization scheme. - fn encode(self) -> Vec { - let RawChangedContract { name, address } = self; + #[cfg(test)] + pub(super) fn encode(self) -> Vec { + let ChangedContract { name, address } = self; encode(&[Token::String(name), Token::Address(address.0.into())]) } } @@ -551,6 +509,7 @@ impl UpdateBridgeWhitelist { /// Serialize an instance [`UpdateBridgeWhitelist`] using Ethereum's /// ABI serialization scheme. + #[cfg(test)] fn encode(self) -> Vec { let UpdateBridgeWhitelist { nonce, whitelist } = self; @@ -563,7 +522,7 @@ impl UpdateBridgeWhitelist { ) }) .unzip(); - encode(&[Token::Uint(nonce), Token::Array(tokens), Token::Array(caps)]) + encode(&[Token::Uint(nonce.into()), Token::Array(tokens), Token::Array(caps)]) } } @@ -632,7 +591,7 @@ impl Parse for Token { fn parse_uint256(self) -> Result { if let Token::Uint(uint) = self { - Ok(uint) + Ok(uint.into()) } else { Err(Error::Decode(format!( "Expected type `Uint`, got {:?}", @@ -770,7 +729,7 @@ mod test_events { encode(&[Token::Address(erc.0.into())]).as_slice() ) .expect("Test failed"), - erc + vec![Token::Address(erc.0.into())] ) } } diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index f3c5b4cbc5..8912ca203b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -1,6 +1,11 @@ pub mod events; pub mod oracle; -mod test_tools; +#[cfg(feature = "eth-fullnode")] +pub use oracle::{Oracle, run_oracle}; +pub mod test_tools; +#[cfg(not(feature="eth-fullnode"))] +pub use test_tools::mock_oracle::{Oracle, run_oracle}; + use std::ffi::OsString; @@ -85,7 +90,7 @@ pub mod eth_fullnode { match std::env::var("ETHEREUM_NETWORK") { Ok(path) => { tracing::info!("Connecting to Ethereum network: {}", &path); - Ok(Some(path)) + Ok(Some(format!("--{}", path))) } Err(std::env::VarError::NotPresent) => { tracing::info!("Connecting to Ethereum mainnet"); @@ -108,15 +113,10 @@ pub mod eth_fullnode { // the geth fullnode process let network = get_eth_network()?; let args = match &network { - Some(network) => vec![ - "--syncmode", - "snap", - network.as_str(), - "--ws", - "--ws.api", - "eth", - ], - None => vec!["--syncmode", "snap", "--ws", "--ws.api", "eth"], + Some(network) => { + vec!["--syncmode", "snap", network.as_str(), "--http"] + } + None => vec!["--syncmode", "snap", "--http"], }; let ethereum_node = Command::new("geth") .args(&args) @@ -130,18 +130,14 @@ pub mod eth_fullnode { let client = Web3::new(url, std::time::Duration::from_secs(5)); loop { - match client.eth_syncing().await { - Ok(true) => {} - Ok(false) => { - tracing::info!("Finished syncing"); - break; - } - Err(err) => { - tracing::error!( - "Encountered an error while syncing: {}", - err - ); - } + if let Ok(false) = client.eth_syncing().await { + tracing::info!("Finished syncing"); + break; + } + if let Err(e) = client.eth_syncing().await { + // This is very noisy and usually not interesting. + // Still can be very useful + tracing::debug!("Error trying to connect to Geth: {:?}", e); } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 0395d763c3..0c88265690 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,11 +1,16 @@ use std::ops::Deref; +use anoma::types::ethereum_events::{EthAddress, EthereumEvent}; +use clarity::Address; use num256::Uint256; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; +#[cfg(not(test))] use web30::client::Web3; +#[cfg(test)] +use super::test_tools::mock_web3_client::Web3; -use super::events::{signatures, EthAddress, EthereumEvent, PendingEvent}; +use super::events::{signatures, PendingEvent}; /// Minimum number of confirmations needed to trust an Ethereum branch pub(crate) const MIN_CONFIRMATIONS: u64 = 50; @@ -17,7 +22,7 @@ const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); /// A client that can talk to geth and parse /// and relay events relevant to Anoma to the /// ledger process -pub(crate) struct Oracle { +pub struct Oracle { /// The client that talks to the Ethereum fullnode client: Web3, /// A channel for sending processed and confirmed @@ -67,7 +72,7 @@ impl Oracle { events .into_iter() .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) + .all(|res| res.is_ok()) && !self.sender.is_closed() } /// Check if the receiver in the ledger has hung up. @@ -77,12 +82,24 @@ impl Oracle { } } +/// Set up an Oracle and run the process where the Oracle +/// processes and forwards Ethereum events to the ledger pub async fn run_oracle( url: &str, sender: UnboundedSender, abort_sender: Sender<()>, ) { let oracle = Oracle::new(url, sender, abort_sender); + run_oracle_aux(oracle).await; +} + +/// Given an oracle, watch for new Ethereum events, processing +/// them into Anoma native types. +/// +/// It also checks that once the specified number of confirmations +/// is reached, an event is forwarded to the ledger process +async fn run_oracle_aux(oracle: Oracle) { + // Initialize our local state. This includes // the latest block height seen and a queue of events // awaiting a certain number of confirmations @@ -102,14 +119,24 @@ pub async fn run_oracle( return; } }; - + // No blocks in existence yet with enough confirmations + if Uint256::from(MIN_CONFIRMATIONS) > latest_block { + if !oracle.connected() { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return; + } + continue; + } let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. for sig in signatures::SIGNATURES { - let addr = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.clone().into(), + let addr: Address = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => MINT_CONTRACT.0.into(), signatures::SigType::Governance => { - GOVERNANCE_CONTRACT.0.clone().into() + GOVERNANCE_CONTRACT.0.into() } }; // fetch the events for matching the given signature @@ -176,3 +203,288 @@ fn process_queue( } confirmed } + + +#[cfg(test)] +mod test_oracle { + use tokio::sync::oneshot::{Receiver, channel}; + + use super::*; + use super::super::test_tools::mock_web3_client::{TestCmd, Web3}; + use crate::node::ledger::ethereum_node::events::{ChangedContract, RawTransfersToEthereum}; + use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::MockEventType; + use anoma::types::ethereum_events::TransferToEthereum; + + /// The data returned from setting up a test + struct TestPackage { + oracle: Oracle, + admin_channel: tokio::sync::mpsc::UnboundedSender, + eth_recv: tokio::sync::mpsc::UnboundedReceiver, + abort_recv: Receiver<()>, + } + + /// Set up an oracle with a mock web3 client that we can contr + fn setup() -> TestPackage { + let (admin_channel, client) = Web3::setup(); + let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (abort, abort_recv) = channel(); + TestPackage { + oracle: Oracle { + client, + sender: eth_sender, + abort: Some(abort), + }, + admin_channel, + eth_recv: eth_receiver, + abort_recv, + } + } + + /// Test that if the oracle shuts down, it + /// sends a message to the fullnode to stop + #[test] + fn test_abort_send() { + let TestPackage { + oracle, + mut abort_recv, + .. + } = setup(); + drop(oracle); + assert!(abort_recv.try_recv().is_ok()) + } + + /// Test that if the fullnode stops, the oracle + /// shuts down, even if the web3 client is unresponsive + #[test] + fn test_shutdown() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel.send(TestCmd::Unresponsive).expect("Test failed"); + drop(eth_recv); + oracle.join().expect("Test failed"); + } + + /// Test that if no logs are received from the web3 + /// client, no events are sent out + #[test] + fn test_no_logs_no_op() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(100u32))) + .expect("Test failed"); + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + drop(eth_recv); + oracle.join().expect("Test failed"); + } + + /// Test that if a new block height doesn't increase, + /// no events are sent out even if there are + /// some in the logs. + #[test] + fn test_cant_get_new_height() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel.send( + TestCmd::NewHeight(50u32.into()) + ).expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]) + }.encode(); + admin_channel.send( + TestCmd::NewEvent{ + event_type:MockEventType::NewContract, + data:new_event, + height: 51, + } + ).expect("Test failed"); + // since height is not updating, we should not receive events + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + drop(eth_recv); + oracle.join().expect("Test failed"); + } + + /// Test that the oracle waits until new logs + /// are received before sending them on. + #[test] + fn test_wait_on_new_logs() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel.send( + TestCmd::NewHeight(50u32.into()) + ).expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]) + }.encode(); + admin_channel.send( + TestCmd::NewEvent{ + event_type: MockEventType::NewContract, + data: new_event, + height: 100, + } + ).expect("Test failed"); + + // we should not receive events even though the height is large + // enough + admin_channel.send(TestCmd::Unresponsive).expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(101u32))) + .expect("Test failed"); + + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + // check that when web3 becomes responsive, oracle sends event + admin_channel.send(TestCmd::Normal).expect("Test failed"); + let event = eth_recv.blocking_recv() + .expect("Test failed"); + if let EthereumEvent::NewContract {name, address} = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address.0, [0; 20]); + } else { + panic!("Test failed"); + } + drop(eth_recv); + oracle.join().expect("Test failed"); + } + + /// Test that events are only sent when they + /// reach the required number of confirmations + #[test] + fn test_finality_gadget() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel.send( + TestCmd::NewHeight(50u32.into()) + ).expect("Test failed"); + + // confirmed after 50 blocks + let first_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]) + }.encode(); + + // confirmed after 75 blocks + let second_event = RawTransfersToEthereum { + transfers: vec![TransferToEthereum { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: EthAddress([1; 20]), + }], + nonce: 1.into(), + confirmations: 75, + }.encode(); + + // send in the events to the logs + admin_channel.send( + TestCmd::NewEvent { + event_type: MockEventType::TransferToEthereum, + data: second_event, + height: 125, + } + ).expect("Test failed"); + admin_channel.send( + TestCmd::NewEvent{ + event_type: MockEventType::NewContract, + data: first_event, + height: 100, + } + ).expect("Test failed"); + + // increase block height so first event is confirmed but second is not. + admin_channel + .send(TestCmd::NewHeight(Uint256::from(102u32))) + .expect("Test failed"); + // check the correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract {name, address} = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address, EthAddress([0; 20])); + } else { + panic!("Test failed, {:?}", event); + } + + // check no other events are received + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + + // increase block height so second event is confirmed + admin_channel + .send(TestCmd::NewHeight(Uint256::from(130u32))) + .expect("Test failed"); + // check correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::TransfersToEthereum(mut transfers) = event { + assert_eq!(transfers.len(), 1); + let transfer = transfers.remove(0); + assert_eq!( + transfer, + TransferToEthereum { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: EthAddress([1; 20]), + } + ); + } else { + panic!("Test failed"); + } + + drop(eth_recv); + oracle.join().expect("Test failed"); + } +} \ No newline at end of file diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index e15efc3bdd..875850fa4a 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -1,18 +1,22 @@ -use super::*; + #[cfg(not(feature = "eth-fullnode"))] /// tools for running a mock ethereum fullnode process pub mod mock_eth_fullnode { - use anoma::types::hash::Hash; - use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::oneshot::{channel, Receiver, Sender}; - use super::Result; + use super::super::Result; - pub struct EthereumNode; + pub struct EthereumNode{ + receiver: Receiver<()>, + } + + pub struct AbortSender; impl EthereumNode { - pub async fn new(_: &str) -> Result { - Ok(Self {}) + pub async fn new(_: &str) -> Result<(EthereumNode, Sender<()>)> { + let (abort_sender, receiver) = channel(); + Ok((Self {receiver}, abort_sender)) } pub async fn wait(&mut self) -> Result<()> { @@ -24,104 +28,143 @@ pub mod mock_eth_fullnode { } #[cfg(not(feature = "eth-fullnode"))] +pub mod mock_oracle { + + use anoma::types::ethereum_events::EthereumEvent; + use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::oneshot::Sender; + + pub struct Oracle; + + pub async fn run_oracle( + _: &str, + _: UnboundedSender, + abort: Sender<()>, + ) { + loop { + if abort.is_closed() { + return + } + } + } +} + +#[cfg(test)] pub mod mock_web3_client { + use std::cell::RefCell; use std::fmt::Debug; use num256::Uint256; - use tokio::sync::mpsc::{channel, Receiver, Sender}; + use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use web30::types::Log; - use super::events::signatures::*; - use super::{Error, Result}; + use super::super::events::signatures::*; + use super::super::{Error, Result}; /// Commands we can send to the mock client + #[derive(Debug)] pub enum TestCmd { Normal, Unresponsive, NewHeight(Uint256), - NewEvent(MockEventType, Vec), + NewEvent{ + event_type: MockEventType, + data: Vec, + height: u32, + }, } /// The type of events supported - #[derive(PartialEq)] + #[derive(Debug, PartialEq)] pub enum MockEventType { TransferToNamada, - TransferToErc, + TransferToEthereum, ValSetUpdate, NewContract, UpgradedContract, BridgeWhitelist, } - /// A mock of a web3 api connected to an ethereum fullnode. + /// A pointer to a mock Web3 client. The + /// reason is for interior mutability. + pub struct Web3(RefCell); + + /// A mock of a web3 api client connected to an ethereum fullnode. /// It is not connected to a full node and is fully controllable /// via a channel to allow us to mock different behavior for /// testing purposes. - pub struct Web3 { - cmd_channel: Receiver, + pub struct Web3Client{ + cmd_channel: UnboundedReceiver, active: bool, latest_block_height: Uint256, - events: Vec<(MockEventType, Vec)>, + events: Vec<(MockEventType, Vec, u32)>, } impl Web3 { + /// This method is part of the Web3 api we use, + /// but is not meant to be used in tests + #[allow(dead_code)] + pub fn new(_: &str, _: std::time::Duration) -> Self { + panic!("Method is here for api completeness. It is not meant to be used in tests.") + } + /// Return a new client and a separate sender /// to send in admin commands - pub fn new() -> (Sender, Self) { + pub fn setup() -> (UnboundedSender, Self) { // we can only send one command at a time. - let (cmd_sender, cmd_channel) = channel(1); + let (cmd_sender, cmd_channel) = unbounded_channel(); ( cmd_sender, - Self { + Self(RefCell::new(Web3Client { cmd_channel, active: true, latest_block_height: Default::default(), events: vec![], - }, + })), ) } /// Check and apply new incoming commands - fn check_cmd_channel(&mut self) { - if let Ok(cmd) = self.cmd_channel.try_recv() { - match cmd { - TestCmd::Normal => self.active = true, - TestCmd::Unresponsive => self.active = false, - TestCmd::NewHeight(height) => { - self.latest_block_height = height - } - TestCmd::NewEvent(ty, data) => self.events.push((ty, data)), + fn check_cmd_channel(&self) { + let cmd = + if let Ok(cmd) = self.0.borrow_mut().cmd_channel.try_recv() { + cmd + } else { + return + }; + match cmd { + TestCmd::Normal => self.0.borrow_mut().active = true, + TestCmd::Unresponsive => self.0.borrow_mut().active = false, + TestCmd::NewHeight(height) => { + self.0.borrow_mut().latest_block_height = height } + TestCmd::NewEvent{event_type: ty, data, height} => self.0.borrow_mut().events.push((ty, data, height)), } } /// Gets the latest block number send in from the /// command channel if we have not set the client to /// act unresponsive. - pub async fn eth_block_number(&mut self) -> Result { + pub async fn eth_block_number(&self) -> Result { self.check_cmd_channel(); - if self.active { - Ok(self.latest_block_height.clone()) - } else { - Err(Error::Runtime("Uh oh, I'm not responding".into())) - } + Ok(self.0.borrow().latest_block_height.clone()) } /// Gets the events (for the appropriate signature) that /// have been added from the command channel unless the /// client has not been set to act unresponsive. pub async fn check_for_events( - &mut self, + &self, _: Uint256, _: Option, _: impl Debug, mut events: Vec<&str>, ) -> Result> { self.check_cmd_channel(); - if self.active { + if self.0.borrow().active { let ty = match events.remove(0) { TRANSFER_TO_NAMADA_SIG => MockEventType::TransferToNamada, - TRANSFER_TO_ERC_SIG => MockEventType::TransferToErc, + TRANSFER_TO_ETHEREUM_SIG => MockEventType::TransferToEthereum, VALIDATOR_SET_UPDATE_SIG => MockEventType::ValSetUpdate, NEW_CONTRACT_SIG => MockEventType::NewContract, UPGRADED_CONTRACT_SIG => MockEventType::UpgradedContract, @@ -132,15 +175,16 @@ pub mod mock_web3_client { }; let mut logs = vec![]; let mut events = vec![]; - std::mem::swap(&mut self.events, &mut events); - for (event_ty, data) in events.into_iter() { - if &event_ty == &ty { + let mut client = self.0.borrow_mut(); + std::mem::swap(&mut client.events, &mut events); + for (event_ty, data, height) in events.into_iter() { + if event_ty == ty && client.latest_block_height >= Uint256::from(height) { logs.push(Log { data: data.into(), ..Default::default() }); } else { - self.events.push((event_ty, data)); + client.events.push((event_ty, data, height)); } } Ok(logs) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index ccbe42e384..bc48ab56b3 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -16,7 +16,6 @@ use std::str::FromStr; use anoma::ledger::governance::storage as gov_storage; use anoma::types::storage::Key; use byte_unit::Byte; -use ethereum_node::events::EthereumEvent; use ethereum_node::EthereumNode; use futures::future::TryFutureExt; use once_cell::unsync::Lazy; @@ -25,7 +24,7 @@ use sysinfo::{RefreshKind, System, SystemExt}; use tendermint_proto::abci::CheckTxType; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::CheckTxType; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; +use tokio::sync::mpsc::unbounded_channel; use tower::ServiceBuilder; #[cfg(not(feature = "ABCI"))] use tower_abci::{response, split, Server}; @@ -187,39 +186,6 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .thread_name(|i| format!("ledger-rayon-worker-{}", i)) .build_global() .unwrap(); - let (ethereum_node, oracle, eth_receiver) = if matches!( - config.tendermint.tendermint_mode, - TendermintMode::Validator - ) { - // boot up the ethereum node process and wait for it to finish syncing - let (eth_sender, eth_receiver) = unbounded_channel(); - let (ethereum_node, abort_sender) = - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(EthereumNode::new(&config.ethereum_url())) - .expect("Unable to start the Ethereum fullnode"); - // Start the oracle process to relay data from the ethereum fullnode to - // the ledger. Unfortunately requires a single threaded runtime, - // so we do this here. - let url = config.ethereum_url(); - let oracle = std::thread::spawn(move || { - tokio::runtime::Builder::new_current_thread() - .thread_name("ethereum-relayer-tokio-worker") - .enable_all() - .build() - .unwrap() - .block_on(ethereum_node::oracle::run_oracle( - &url, - eth_sender, - abort_sender, - )) - }); - (Some(ethereum_node), Some(oracle), Some(eth_receiver)) - } else { - (None, None, None) - }; // Start tokio runtime with the `run_aux` function tokio::runtime::Builder::new_multi_thread() @@ -229,9 +195,7 @@ pub fn run(config: config::Ledger, wasm_dir: PathBuf) { .enable_all() .build() .unwrap() - .block_on(run_aux(config, wasm_dir, ethereum_node, eth_receiver)); - // join up the oracle thread. - let _ = oracle.map(|handle| handle.join()); + .block_on(run_aux(config, wasm_dir)); } /// Resets the tendermint_node state and removes database files @@ -245,12 +209,7 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// functioning. /// /// TODO: Update this docstring -async fn run_aux( - config: config::Ledger, - wasm_dir: PathBuf, - ethereum_node: Option, - eth_receiver: Option>, -) { +async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Prefetch needed wasm artifacts wasm_loader::pre_fetch_wasm(&wasm_dir).await; @@ -353,15 +312,29 @@ async fn run_aux( let (broadcaster_sender, broadcaster_receiver) = tokio::sync::mpsc::unbounded_channel(); - let (ethereum_node, broadcaster) = if matches!( + let ethereum_url = config.ethereum_url(); + let (ethereum_node, oracle, broadcaster) = if matches!( config.tendermint.tendermint_mode, TendermintMode::Validator ) { + let local = tokio::task::LocalSet::new(); + // boot up the ethereum node process and wait for it to finish syncing + let (eth_sender, eth_receiver) = unbounded_channel(); + let url = ethereum_url.clone(); + let (ethereum_node, abort_sender) = local + .run_until(async move { + EthereumNode::new(&url) + .await + .expect("Unable to start the Ethereum fullnode") + }) + .await; + // Start Ethereum fullnode // Channel for signalling shut down to Tendermint process let (eth_abort_send, eth_abort_recv) = tokio::sync::oneshot::channel::>(); let abort_send_for_eth = abort_send.clone(); + // run geth in the background let ethereum_node = tokio::spawn(async move { // On panic or exit, the `Drop` of `AbortSender` will send abort // message @@ -370,16 +343,24 @@ async fn run_aux( who: "Ethereum", }; - let res = - ethereum_node::run(ethereum_node.unwrap(), eth_abort_recv) - .map_err(Error::Ethereum) - .await; + let res = ethereum_node::run(ethereum_node, eth_abort_recv) + .map_err(Error::Ethereum) + .await; tracing::info!("Ethereum fullnode is no longer running."); drop(aborter); res }); + let oracle = local.spawn_local(async move { + ethereum_node::run_oracle( + ðereum_url, + eth_sender, + abort_sender, + ) + .await + }); + // Shutdown ethereum_node via a message to ensure that the child process // is properly cleaned-up. let (eth_abort_resp_send, eth_abort_resp_recv) = @@ -397,6 +378,7 @@ async fn run_aux( eth_abort_resp_send, eth_abort_resp_recv, )), + Some((local, oracle, eth_receiver)), Some(( tokio::spawn(async move { // Construct a service for broadcasting protocol txs from @@ -409,17 +391,23 @@ async fn run_aux( sender: abort_send_for_broadcaster, who: "Broadcaster", }; - let res = broadcaster.run(bc_abort_recv).await; + broadcaster.run(bc_abort_recv).await; tracing::info!("Broadcaster is no longer running."); drop(aborter); - res }), bc_abort_send, )), ) } else { - (None, None) + (None, None, None) + }; + + let (rt, oracle_proc, eth_receiver) = match oracle { + Some((rt, oracle_proc, oracle_channel)) => { + (Some(rt), Some(oracle_proc), Some(oracle_channel)) + } + None => (None, None, None), }; // Channel for signalling shut down to Tendermint process @@ -519,8 +507,10 @@ async fn run_aux( eth_abort_resp_send, eth_abort_resp_recv, )), + Some(local), + Some(oracle), Some((broadcaster, bc_abort_send)), - ) = (ethereum_node, broadcaster) + ) = (ethereum_node, rt, oracle_proc, broadcaster) { // Ask to shutdown tendermint node cleanly. Ignore error, which can // happen if the tendermint_node task has already finished. @@ -537,17 +527,24 @@ async fn run_aux( } // request the broadcaster shutdown let _ = bc_abort_send.send(()); - tokio::try_join!(tendermint_node, ethereum_node, abci, broadcaster) + + tokio::try_join!( + tendermint_node, + ethereum_node, + local.run_until(async move { oracle.await }), + abci, + broadcaster + ) } else { // if we are not a validator, the broadcaster service and Ethereum // fullnode are not active. Thus, we fill in their return values // with () tokio::try_join!(tendermint_node, abci) - .map(|results| (results.0, Ok(()), results.1, ())) + .map(|results| (results.0, Ok(()), (), results.1, ())) }; match res { - Ok((tendermint_res, eth_res, abci_res, _)) => { + Ok((tendermint_res, eth_res, _, abci_res, _)) => { // we ignore errors on user-initiated shutdown if aborted { if let Err(err) = tendermint_res { @@ -640,7 +637,7 @@ async fn wait_for_abort( let mut sigterm = signal(SignalKind::terminate()).unwrap(); let mut sighup = signal(SignalKind::hangup()).unwrap(); let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - let _ = tokio::select! { + tokio::select! { signal = tokio::signal::ctrl_c() => { match signal { Ok(()) => tracing::info!("Received interrupt signal, exiting..."), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1b839a6825..46f025a74c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -531,7 +531,7 @@ mod test_finalize_block { /// not appear in the queue of txs to be decrypted #[test] fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_wrappers = vec![]; @@ -605,7 +605,7 @@ mod test_finalize_block { /// check that the correct event is returned. #[test] fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = gen_keypair(); let mut processed_txs = vec![]; // create some wrapper txs @@ -662,7 +662,7 @@ mod test_finalize_block { /// proposal #[test] fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = gen_keypair(); let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -711,7 +711,7 @@ mod test_finalize_block { /// check that the correct event is returned. #[test] fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let raw_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some(String::from("transaction data").as_bytes().to_owned()), @@ -747,7 +747,7 @@ mod test_finalize_block { /// but the tx result contains the appropriate error code. #[test] fn test_undecryptable_returns_error_code() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); @@ -804,7 +804,7 @@ mod test_finalize_block { /// but the tx result contains the appropriate error code. #[test] fn test_undecryptable_returns_error_code() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); @@ -861,7 +861,7 @@ mod test_finalize_block { /// decrypted txs are de-queued. #[test] fn test_mixed_txs_queued_in_correct_order() { - let (mut shell, _) = setup(); + let (mut shell, _, _) = setup(); let keypair = gen_keypair(); let mut processed_txs = vec![]; let mut valid_txs = vec![]; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4cba82d4c6..d5837c6507 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -32,6 +32,7 @@ use anoma::ledger::storage::{ use anoma::ledger::{ibc, parameters, pos}; use anoma::proto::{self, Tx}; use anoma::types::chain::ChainId; +use anoma::types::ethereum_events::EthereumEvent; use anoma::types::key::*; use anoma::types::storage::{BlockHeight, Key}; use anoma::types::time::{DateTimeUtc, TimeZone, Utc}; @@ -71,7 +72,6 @@ use tower_abci_old::{request, response}; use super::rpc; use crate::config::{genesis, TendermintMode}; -use crate::node::ledger::ethereum_node::events::EthereumEvent; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -768,11 +768,14 @@ mod test_utils { } impl TestShell { - /// Returns a new shell paired with a broadcast receiver, which will - /// receives any protocol txs sent by the shell. - pub fn new() -> (Self, UnboundedReceiver>) { + /// Returns a new shell with + /// - A broadcast receiver, which will receive + /// any protocol txs sent by the shell. + /// - A sender that can send Ethereum events into the ledger, + /// mocking the Ethereum fullnode process + pub fn new() -> (Self, UnboundedReceiver>, UnboundedSender) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); - let (_eth_sender, eth_receiver) = + let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB @@ -787,13 +790,14 @@ mod test_utils { ), top_level_directory().join("wasm"), sender, - eth_receiver, + Some(eth_receiver), None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), }, receiver, + eth_sender ) } @@ -870,8 +874,8 @@ mod test_utils { /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup() -> (TestShell, UnboundedReceiver>) { - let (mut test, receiver) = TestShell::new(); + pub(super) fn setup() -> (TestShell, UnboundedReceiver>, UnboundedSender) { + let (mut test, receiver, eth_receiver) = TestShell::new(); test.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -880,7 +884,7 @@ mod test_utils { chain_id: ChainId::default().to_string(), ..Default::default() }); - (test, receiver) + (test, receiver, eth_receiver) } /// This is just to be used in testing. It is not @@ -918,7 +922,7 @@ mod test_utils { ), top_level_directory().join("wasm"), sender.clone(), - receiver, + Some(receiver), None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, @@ -977,7 +981,7 @@ mod test_utils { ), top_level_directory().join("wasm"), sender, - receiver, + Some(receiver), None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 53494765dd..a1a559db8c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -129,7 +129,7 @@ mod prepare_block { /// proposed block. #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), @@ -150,7 +150,7 @@ mod prepare_block { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -192,7 +192,7 @@ mod prepare_block { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 01a13a684b..b79279c248 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -298,7 +298,7 @@ mod test_process_proposal { /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -349,7 +349,7 @@ mod test_process_proposal { /// Test that a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -437,7 +437,7 @@ mod test_process_proposal { /// non-zero, [`process_proposal`] rejects that tx #[test] fn test_wrapper_unknown_address() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = crate::wallet::defaults::keys().remove(0).1; let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -486,7 +486,7 @@ mod test_process_proposal { /// [`process_proposal`] rejects that tx #[test] fn test_wrapper_insufficient_balance_address() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); shell.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -547,7 +547,7 @@ mod test_process_proposal { /// validated, [`process_proposal`] rejects it #[test] fn test_decrypted_txs_out_of_order() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { @@ -613,7 +613,7 @@ mod test_process_proposal { /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( @@ -664,7 +664,7 @@ mod test_process_proposal { /// undecryptable but still accepted #[test] fn test_invalid_hash_commitment() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); shell.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -739,7 +739,7 @@ mod test_process_proposal { /// marked undecryptable and the errors handled correctly #[test] fn test_undecryptable() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); shell.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -811,7 +811,7 @@ mod test_process_proposal { /// [`process_proposal`] than expected, they are rejected #[test] fn test_too_many_decrypted_txs() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -844,7 +844,7 @@ mod test_process_proposal { /// Process Proposal should reject a RawTx, but not panic #[test] fn test_raw_tx_rejected() { - let (mut shell, _) = TestShell::new(); + let (mut shell, _, _) = TestShell::new(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 6e6670b203..be2a76132a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use std::pin::Pin; use std::task::{Context, Poll}; +use anoma::types::ethereum_events::EthereumEvent; #[cfg(feature = "ABCI")] use anoma::types::hash::Hash; #[cfg(feature = "ABCI")] @@ -20,7 +21,6 @@ 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::ethereum_node::events::EthereumEvent; use super::super::Shell; use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; #[cfg(not(feature = "ABCI"))] diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs new file mode 100644 index 0000000000..4c05663be2 --- /dev/null +++ b/shared/src/types/ethereum_events.rs @@ -0,0 +1,126 @@ +//! Types representing data intended for Anoma via Ethereum events + +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use ethabi::Uint as ethUint; + +use crate::types::address::Address; +use crate::types::token::Amount; + +/// Anoma native type to replace the ethabi::Uint type +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct Uint(pub [u64; 4]); + +impl From for Uint { + fn from(value: ethUint) -> Self { + Self(value.0) + } +} + +impl From for ethUint { + fn from(value: Uint) -> Self { + Self(value.0) + } +} + +impl From for Uint { + fn from(value: u64) -> Self { + ethUint::from(value).into() + } +} + +/// Representation of address on Ethereum +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct EthAddress(pub [u8; 20]); + +/// A Keccak hash +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct KeccakHash(pub [u8; 32]); + +/// An Ethereum event to be processed by the Anoma ledger +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +pub enum EthereumEvent { + /// Event transferring batches of ether or Ethereum based ERC20 tokens + /// from Ethereum to wrapped assets on Anoma + TransfersToNamada(Vec), + /// A confirmation event that a batch of transfers have been made + /// from Anoma to Ethereum + TransfersToEthereum(Vec), + /// Event indication that the validator set has been updated + /// in the governance contract + ValidatorSetUpdate { + /// Monotonically increasing nonce + #[allow(dead_code)] + nonce: Uint, + /// Hash of the validators in the bridge contract + #[allow(dead_code)] + bridge_validator_hash: KeccakHash, + /// Hash of the validators in the governance contract + #[allow(dead_code)] + governance_validator_hash: KeccakHash, + }, + /// Event indication that a new smart contract has been + /// deployed + NewContract { + /// Name of the contract + #[allow(dead_code)] + name: String, + /// Address of the contract on Ethereum + #[allow(dead_code)] + address: EthAddress, + }, + /// Event indicating that a smart contract has been updated + UpgradedContract { + /// Name of the contract + #[allow(dead_code)] + name: String, + /// Address of the contract on Ethereum + #[allow(dead_code)] + address: EthAddress, + }, + /// Event indication a new Ethereum based token has been whitelisted for + /// transfer across the bridge + UpdateBridgeWhitelist { + /// Monotonically increasing nonce + #[allow(dead_code)] + nonce: Uint, + /// Tokens to be allowed to be transferred across the bridge + #[allow(dead_code)] + whitelist: Vec, + }, +} + +/// An event transferring some kind of value from Ethereum to Anoma +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct TransferToNamada { + /// Quantity of the ERC20 token in the transfer + pub amount: Amount, + /// Address of the smart contract issuing the token + pub asset: EthAddress, + /// The address receiving wrapped assets on Anoma + pub receiver: Address, +} + +/// An event transferring some kind of value from Ethereum to Anoma +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct TransferToEthereum { + /// Quantity of wrapped Asset in the transfer + pub amount: Amount, + /// Address of the smart contract issuing the token + pub asset: EthAddress, + /// The address receiving assets on Ethereum + pub receiver: EthAddress, +} + + +/// struct for whitelisting a token from Ethereum. +/// Includes the address of issuing contract and +/// a cap on the max amount of this token allowed to be +/// held by the bridge. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[allow(dead_code)] +pub struct TokenWhitelist { + /// Address of Ethereum smart contract issuing token + pub token: EthAddress, + /// Maximum amount of token allowed on the bridge + pub cap: Amount, +} \ No newline at end of file diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4b8d7288ca..47c0a9fcae 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -2,6 +2,7 @@ pub mod address; pub mod chain; +pub mod ethereum_events; pub mod dylib; pub mod governance; pub mod hash; diff --git a/wasm/checksums.json b/wasm/checksums.json index 4d9be9bc22..40e7ac4fb0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.211d2592f91a99ecae1033999b47566899db17bfcce5c86a6820336e3f4c5283.wasm", - "tx_enqueue_eth_transfer.wasm": "tx_enqueue_eth_transfer.6bec59aa27e635c9f0184553a4e6ef61de6c0116ec09519aa79c6bf01b6cfa25.wasm", - "tx_from_intent.wasm": "tx_from_intent.66d64054bb90a88b3b24545126bc1f5e90fe99f6b2eef189a1820ac5a86a94ce.wasm", - "tx_ibc.wasm": "tx_ibc.734f8d676b42876203aedee8dffaaadfa949b8b6f2da0c21a999578cba65ad01.wasm", - "tx_init_account.wasm": "tx_init_account.f26c6b64a5c0311ca353ea7394c3dc26e7a96023e7f493b33e05b00f36bc60b8.wasm", - "tx_init_nft.wasm": "tx_init_nft.6f3dccf50dab1dd761a775bbe629f2fe75c02b50b1c2a4d367ce4481de6d2128.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bfd12a8943f49c2b77098918029c472f2fb4f787aed8be2478d92cd785c4cdb6.wasm", - "tx_init_validator.wasm": "tx_init_validator.b2e94f4eb239d69df3e9011b22421d7f156d136cf42d0a31f093cb1fd8ce2970.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.06911b885acc1a734fca4c68d260600bca1e52b0f089c8dbde3c6f604d490e4a.wasm", - "tx_transfer.wasm": "tx_transfer.7c7637013dff61643875088025365b267338e77347c3b9d3116259ccce017a24.wasm", - "tx_unbond.wasm": "tx_unbond.0c15932b9694fcd04324543f6d16c36301ade321045f74a591f83ce9ca89dfec.wasm", - "tx_update_vp.wasm": "tx_update_vp.39a744a88e41ec2f46e38e302e01102517b174d96fedd1e899fbe4166fc9f1ee.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0830d1746a9f7bb7f87cf5e4b62fc00372b8d3176c51606669be983989f71ca6.wasm", - "tx_withdraw.wasm": "tx_withdraw.5fb94bbf64b86805e7e08b4c8804b2887b61cdde6b3776464f0285961ecdcad9.wasm", - "vp_nft.wasm": "vp_nft.27d7e464393996c60e826b7945d2af4a60a819d940f6c471ce7828786fe4164c.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.f585956bbce7e2912a1d28fd903dbd4f756afe21f490502a1ee5ac33db6afa56.wasm", - "vp_token.wasm": "vp_token.da2df3ed5baf74c485c8229e2e873012507e6e4a8cc61c89a3ada918f9e8e585.wasm", - "vp_user.wasm": "vp_user.9fb62eed9e9dc1239614481142c3812b938eb550a78f937b9015ac456e83e2bd.wasm" + "tx_bond.wasm": "tx_bond.be811dde2e02ce0139e8b3015f8dfca741bc941a087586f3d9768b4ecadd4aac.wasm", + "tx_ethereum_headers.wasm": "tx_ethereum_headers.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_from_intent.wasm": "tx_from_intent.f5ce4107b218b580edc077a7a4324e4486f5ebed44232248add34785f4c2fd0b.wasm", + "tx_ibc.wasm": "tx_ibc.1d9c9c9d14dcc796a506b6699ed7a70522e069287167d9ff856cf8a7ac9c6220.wasm", + "tx_init_account.wasm": "tx_init_account.c6061a7aebe5bbcac423a956e6c65b538964dba1d081b2f9d85e4d64ad3f99ad.wasm", + "tx_init_nft.wasm": "tx_init_nft.95e0214876deff5472f268a6aa3be45476e3c3b01eba6ea98f027b77d56c5206.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7ab644de36cda1abf4d50fc609b2892df747769ad6d3659a1840989e17fa042e.wasm", + "tx_init_validator.wasm": "tx_init_validator.c216fb01f597de955bba3606204d6173475980a5cfde1e6065bd7c9eb6c43c4f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.d98cdab6d4c77cb969cc23a7ded432ee03424e6212ce2de35ab476771505997c.wasm", + "tx_transfer.wasm": "tx_transfer.b177bc9b29007557de455655c8e7aa46145d66540b5ef12c6dec133942e60676.wasm", + "tx_unbond.wasm": "tx_unbond.6861a1346ed3a9ac3bc92f95fd10d47b510aa82f0e613ca139c42d656796eede.wasm", + "tx_update_vp.wasm": "tx_update_vp.9ae597c11c7afe0856f9825e82eb0f6a4409e13929052c1a8fbb4e3fcfa882c7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7f3be2bee815e0e58edb412a17ce9f185a86683b3ec6a2da118fe6e729caf438.wasm", + "tx_withdraw.wasm": "tx_withdraw.5d2f6ee9ae810efff7762eeb52a9ec68dadbcf21e0297575fcc9da5089a306da.wasm", + "vp_nft.wasm": "vp_nft.648a42b9f7a85c60fab42cb49b82ae29556c064f36cae744304984756316e729.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e3113d13da1cee25f991c374ff0e415ea6e1d903f620b4e211e2b6923df2827f.wasm", + "vp_token.wasm": "vp_token.2bbea520df7866b55fd5bbe144f33e911d6e34d63a8980bce0cafd6f69c76453.wasm", + "vp_user.wasm": "vp_user.64d0ad5f5e9f200cfcca2d3c97c2cc69ef49b365a4d12adcf727c39bf3fca0d2.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 62f3342df6..3f55501e90 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "hex", "ibc", @@ -338,6 +339,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -393,7 +406,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -424,6 +437,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -631,6 +650,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" @@ -853,6 +878,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.7" @@ -891,6 +960,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -923,6 +1004,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -1218,7 +1305,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1239,6 +1326,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1553,6 +1678,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1655,6 +1806,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1664,6 +1828,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1821,6 +1995,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2003,6 +2183,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2026,6 +2216,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2232,6 +2428,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2298,6 +2504,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2348,6 +2560,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2546,6 +2764,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2809,6 +3036,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3272,6 +3511,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 00b8272ab0..45c25ca7d0 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "hex", "ibc", @@ -338,6 +339,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -393,7 +406,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -424,6 +437,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -631,6 +650,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" @@ -853,6 +878,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.7" @@ -891,6 +960,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -923,6 +1004,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -1218,7 +1305,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1239,6 +1326,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1553,6 +1678,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1655,6 +1806,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1664,6 +1828,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1821,6 +1995,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2003,6 +2183,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2026,6 +2216,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2232,6 +2428,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2298,6 +2504,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2348,6 +2560,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2546,6 +2764,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2798,6 +3025,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3272,6 +3511,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 14662821df..4707474507 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "hex", "ibc", @@ -364,6 +365,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -419,7 +432,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -450,6 +463,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -657,6 +676,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" @@ -879,6 +904,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.7" @@ -917,6 +986,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -949,6 +1030,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -1244,7 +1331,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1265,6 +1352,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1579,6 +1704,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1681,6 +1832,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1690,6 +1854,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1847,6 +2021,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2029,6 +2209,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2052,6 +2242,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2258,6 +2454,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2324,6 +2530,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2374,6 +2586,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2572,6 +2790,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2824,6 +3051,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3287,6 +3526,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" From dbd1738d9b1a50ec85b7b70ce83ab32443c054af Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 30 Jun 2022 19:03:29 +0100 Subject: [PATCH 0048/1995] Rename EthereumEventNonce -> Nonce --- shared/src/types/ethereum_events.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 6d41a5c3e4..9c541e4ec7 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -12,7 +12,7 @@ use crate::types::token::Amount; #[derive( Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, )] -pub struct EthereumEventNonce(u64); +pub struct Nonce(u64); /// An Ethereum event to be processed by the Anoma ledger #[derive( @@ -21,7 +21,7 @@ pub struct EthereumEventNonce(u64); pub enum EthereumEvent { /// Event transferring batches of ether from Ethereum to wrapped ETH on /// Anoma - TransfersToNamada(Vec, EthereumEventNonce), + TransfersToNamada(Vec, Nonce), } impl EthereumEvent { @@ -89,7 +89,7 @@ mod tests { #[test] fn test_ethereum_event_hash() { - let nonce = EthereumEventNonce(123); + let nonce = Nonce(123); let event = EthereumEvent::TransfersToNamada(vec![], nonce); let hash = event.hash().unwrap(); From a2e248ffd710f09462b880c53d6ae94aa4106393 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 30 Jun 2022 19:12:39 +0100 Subject: [PATCH 0049/1995] Split out RawEvent and EthereumEvent types --- shared/src/types/ethereum_events.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 9c541e4ec7..f9ca142385 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -18,10 +18,22 @@ pub struct Nonce(u64); #[derive( Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, )] -pub enum EthereumEvent { - /// Event transferring batches of ether from Ethereum to wrapped ETH on - /// Anoma - TransfersToNamada(Vec, Nonce), +pub enum RawEvent { + /// Event transferring batches of Ethereum assets from Ethereum to wrapped + /// assets on Anoma + TransfersToNamada(Vec), +} + +/// An Ethereum event emitted by an Ethereum bridge smart contract. +#[derive( + Debug, PartialEq, Eq, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthereumEvent { + /// The Ethereum event. + pub event: RawEvent, + /// All events must be emitted with a nonce so that otherwise identical + /// events will be unique. + pub nonce: Nonce, } impl EthereumEvent { @@ -90,7 +102,8 @@ mod tests { #[test] fn test_ethereum_event_hash() { let nonce = Nonce(123); - let event = EthereumEvent::TransfersToNamada(vec![], nonce); + let event = RawEvent::TransfersToNamada(vec![]); + let event = EthereumEvent { nonce, event }; let hash = event.hash().unwrap(); From a5b0153506224009bababa0201fa250dd081cb0e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 30 Jun 2022 19:42:54 +0100 Subject: [PATCH 0050/1995] Implement Borsh* for FractionalVotingPower --- shared/src/types/ethereum_events.rs | 76 ++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index f9ca142385..3bc5b25028 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use fraction::Fraction; use crate::proto::MultiSigned; use crate::types::address::Address; @@ -74,14 +75,75 @@ pub enum EthereumAsset { /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. -#[derive( - Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchema, -)] -pub struct FractionalVotingPower(u64, u64); +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FractionalVotingPower(Fraction); + +impl TryFrom<&FractionalVotingPower> for (u64, u64) { + type Error = std::io::Error; + + fn try_from(power: &FractionalVotingPower) -> Result { + let numerator = power + .0 + .numer() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Could not serialize FractionalVotingPower, missing \ + numerator", + ) + })? + .to_owned(); + let denominator = power + .0 + .denom() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "Could not serialize FractionalVotingPower, missing \ + numerator", + ) + })? + .to_owned(); + Ok((numerator, denominator)) + } +} + +impl BorshSerialize for FractionalVotingPower { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let (numer, denom): (u64, u64) = + TryFrom::<&FractionalVotingPower>::try_from(&self)?; + (numer, denom).serialize(writer) + } +} + +impl BorshDeserialize for FractionalVotingPower { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; + Ok(FractionalVotingPower(Fraction::new(numer, denom))) + } +} + +impl BorshSchema for FractionalVotingPower { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + u64::declaration(), + u64::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } -impl From for fraction::Fraction { - fn from(fvp: FractionalVotingPower) -> Self { - fraction::Fraction::new(fvp.0, fvp.1) + fn declaration() -> borsh::schema::Declaration { + "FractionalVotingPower".into() } } From 35435368902d6eb17935c1dc78bf766927f3c262 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 1 Jul 2022 08:54:17 +0100 Subject: [PATCH 0051/1995] Use num-rational instead of fraction crate --- Cargo.lock | 12 +------ shared/Cargo.toml | 2 +- shared/src/types/ethereum_events.rs | 50 +++++++++++------------------ 3 files changed, 20 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab032bbfae..2af0e8aa3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,6 @@ dependencies = [ "ed25519-consensus", "ferveo", "ferveo-common", - "fraction", "group-threshold-cryptography", "hex", "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abcipp-rebase-master)", @@ -100,6 +99,7 @@ dependencies = [ "ics23", "itertools 0.10.3", "loupe", + "num-rational", "parity-wasm", "pretty_assertions", "proptest", @@ -2208,16 +2208,6 @@ dependencies = [ "percent-encoding 2.1.0", ] -[[package]] -name = "fraction" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c42ca58f4e486c530d93c7f74ab469293b20d7f86b478ca9d702a2f95fed61" -dependencies = [ - "lazy_static 1.4.0", - "num", -] - [[package]] name = "fs_extra" version = "1.2.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ae93afad1a..3506db42ac 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,7 +76,7 @@ derivative = "2.2.0" ed25519-consensus = "1.2.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} -fraction = "0.11.0" +num-rational = "0.4.1" 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. diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 3bc5b25028..2da90e1eae 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use fraction::Fraction; +use num_rational::Ratio; use crate::proto::MultiSigned; use crate::types::address::Address; @@ -76,35 +76,11 @@ pub enum EthereumAsset { /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. #[derive(Clone, PartialEq, Eq, Debug)] -pub struct FractionalVotingPower(Fraction); - -impl TryFrom<&FractionalVotingPower> for (u64, u64) { - type Error = std::io::Error; - - fn try_from(power: &FractionalVotingPower) -> Result { - let numerator = power - .0 - .numer() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Could not serialize FractionalVotingPower, missing \ - numerator", - ) - })? - .to_owned(); - let denominator = power - .0 - .denom() - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Could not serialize FractionalVotingPower, missing \ - numerator", - ) - })? - .to_owned(); - Ok((numerator, denominator)) +pub struct FractionalVotingPower(Ratio); + +impl From<&FractionalVotingPower> for (u64, u64) { + fn from(ratio: &FractionalVotingPower) -> Self { + (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) } } @@ -114,7 +90,17 @@ impl BorshSerialize for FractionalVotingPower { writer: &mut W, ) -> std::io::Result<()> { let (numer, denom): (u64, u64) = - TryFrom::<&FractionalVotingPower>::try_from(&self)?; + TryFrom::<&FractionalVotingPower>::try_from(&self).map_err( + |err| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "Could not serialize {:?} to Borsh: {:?}", + self, err + ), + ) + }, + )?; (numer, denom).serialize(writer) } } @@ -122,7 +108,7 @@ impl BorshSerialize for FractionalVotingPower { impl BorshDeserialize for FractionalVotingPower { fn deserialize(buf: &mut &[u8]) -> std::io::Result { let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; - Ok(FractionalVotingPower(Fraction::new(numer, denom))) + Ok(FractionalVotingPower(Ratio::::new(numer, denom))) } } From 0580a86102ea35ed5517b73a0932ccecb02ff2a0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 1 Jul 2022 09:17:12 +0100 Subject: [PATCH 0052/1995] Add some tests --- shared/src/types/ethereum_events.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 2da90e1eae..6201c69fa1 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -75,7 +75,7 @@ pub enum EthereumAsset { /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] pub struct FractionalVotingPower(Ratio); impl From<&FractionalVotingPower> for (u64, u64) { @@ -164,4 +164,30 @@ mod tests { ]) ); } + + #[test] + fn test_fractional_voting_power() { + // this test is exercising the underlying library we use for fractions + // we want to make sure operators work as expected with our + // FractionalVotingPower type itself + assert!( + FractionalVotingPower((2, 3).into()) + > FractionalVotingPower((1, 4).into()) + ); + assert!( + FractionalVotingPower((1, 3).into()) + > FractionalVotingPower((1, 4).into()) + ); + assert!( + FractionalVotingPower((1, 3).into()) + == FractionalVotingPower((2, 6).into()) + ); + } + + #[test] + #[should_panic] + fn test_fractional_voting_power_panics() { + FractionalVotingPower((0, 0).into()); + FractionalVotingPower((1, 0).into()); + } } From 01d324e01eac7afad19145ebfeeb73e385e0dbe4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 1 Jul 2022 09:33:35 +0100 Subject: [PATCH 0053/1995] Add constructor for FractionalVotingPower --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/types/ethereum_events.rs | 49 ++++++++++++++++++++--------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2af0e8aa3a..da3774e20f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3506db42ac..94738838c1 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -74,6 +74,7 @@ chrono = "0.4.19" clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" ed25519-consensus = "1.2.0" +eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} num-rational = "0.4.1" diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 6201c69fa1..5d241c6aea 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::{eyre, Result}; use num_rational::Ratio; use crate::proto::MultiSigned; @@ -78,6 +79,23 @@ pub enum EthereumAsset { #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] pub struct FractionalVotingPower(Ratio); +impl FractionalVotingPower { + /// Create a new FractionalVotingPower. It must be between zero and one + /// inclusive. + pub fn new(numer: u64, denom: u64) -> Result { + if denom == 0 { + return Err(eyre!("denominator can't be zero")); + } + let ratio: Ratio = (numer, denom).into(); + if ratio > 1.into() { + return Err(eyre!( + "fractional voting power cannot be greater than one" + )); + } + Ok(Self(ratio)) + } +} + impl From<&FractionalVotingPower> for (u64, u64) { fn from(ratio: &FractionalVotingPower) -> Self { (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) @@ -166,28 +184,31 @@ mod tests { } #[test] - fn test_fractional_voting_power() { - // this test is exercising the underlying library we use for fractions - // we want to make sure operators work as expected with our - // FractionalVotingPower type itself + fn test_fractional_voting_power_ord_eq() { + // though this test is ultimately just exercising the underlying library + // we use for fractions, we want to make sure operators work as + // expected with our FractionalVotingPower type itself assert!( - FractionalVotingPower((2, 3).into()) - > FractionalVotingPower((1, 4).into()) + FractionalVotingPower::new(2, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() ); assert!( - FractionalVotingPower((1, 3).into()) - > FractionalVotingPower((1, 4).into()) + FractionalVotingPower::new(1, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() ); assert!( - FractionalVotingPower((1, 3).into()) - == FractionalVotingPower((2, 6).into()) + FractionalVotingPower::new(1, 3).unwrap() + == FractionalVotingPower::new(2, 6).unwrap() ); } #[test] - #[should_panic] - fn test_fractional_voting_power_panics() { - FractionalVotingPower((0, 0).into()); - FractionalVotingPower((1, 0).into()); + fn test_fractional_voting_power_valid_fractions() { + assert!(FractionalVotingPower::new(0, 0).is_err()); + assert!(FractionalVotingPower::new(1, 0).is_err()); + assert!(FractionalVotingPower::new(0, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 2).is_ok()); + assert!(FractionalVotingPower::new(3, 2).is_err()); } } From ca2ba870a3752e829f7a17c55561c643348bcd07 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 1 Jul 2022 11:14:34 +0200 Subject: [PATCH 0054/1995] [chore]: Cleaned up code on the ABCI++ feature flag. Clippy and formatting all happy. --- Makefile | 6 +- .../lib/node/ledger/ethereum_node/events.rs | 260 ++++++++++++++---- apps/src/lib/node/ledger/ethereum_node/mod.rs | 8 +- .../lib/node/ledger/ethereum_node/oracle.rs | 112 ++++---- .../node/ledger/ethereum_node/test_tools.rs | 45 +-- apps/src/lib/node/ledger/mod.rs | 8 +- apps/src/lib/node/ledger/shell/mod.rs | 22 +- shared/src/types/ethereum_events.rs | 29 +- shared/src/types/mod.rs | 2 +- 9 files changed, 339 insertions(+), 153 deletions(-) diff --git a/Makefile b/Makefile index 4ab77d59cd..c97d9cac17 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ clippy-abci-plus-plus: $(cargo) +$(nightly) clippy --all-targets \ --manifest-path ./tests/Cargo.toml \ --no-default-features \ - --features "wasm-runtime ABCI-plus-plus anoma_apps/ABCI-plus-plus" && \ + --features "wasm-runtime ABCI-plus-plus anoma_apps/ABCI-plus-plus anoma_apps/eth-fullnode" && \ $(cargo) +$(nightly) clippy \ --all-targets \ --manifest-path ./vm_env/Cargo.toml \ @@ -145,7 +145,7 @@ test-unit-abci-plus-plus: $(cargo) test \ --manifest-path ./apps/Cargo.toml \ --no-default-features \ - --features "testing std ABCI-plus-plus" && \ + --features "testing std ABCI-plus-plus eth-fullnode" && \ $(cargo) test --manifest-path ./proof_of_stake/Cargo.toml \ --features "testing" && \ $(cargo) test \ @@ -155,7 +155,7 @@ test-unit-abci-plus-plus: $(cargo) test \ --manifest-path ./tests/Cargo.toml \ --no-default-features \ - --features "wasm-runtime ABCI-plus-plus anoma_apps/ABCI-plus-plus" \ + --features "wasm-runtime ABCI-plus-plus anoma_apps/ABCI-plus-plus anoma_apps/eth-fullnode" \ -- --skip e2e && \ $(cargo) test \ --manifest-path ./vm_env/Cargo.toml \ diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 1afa36c189..92a2072a65 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -4,15 +4,15 @@ use std::str::FromStr; use anoma::types::address::Address; use anoma::types::ethereum_events::{ - EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, - TransferToEthereum, TransferToNamada, Uint, + EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, TransferToEthereum, + TransferToNamada, Uint, }; use anoma::types::token::Amount; -use ethabi::param_type::ParamType; -use ethabi::token::Token; use ethabi::decode; #[cfg(test)] use ethabi::encode; +use ethabi::param_type::ParamType; +use ethabi::token::Token; use num256::Uint256; use thiserror::Error; @@ -54,7 +54,9 @@ pub mod signatures { impl From<&str> for SigType { fn from(sig: &str) -> Self { match sig { - TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => SigType::Bridge, + TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => { + SigType::Bridge + } _ => SigType::Governance, } } @@ -73,6 +75,7 @@ pub(super) struct PendingEvent { } /// Event emitted with the validator set changes +#[derive(Clone, Debug, PartialEq)] pub struct ValidatorSetUpdate { /// A monotonically increasing nonce nonce: Uint, @@ -84,15 +87,17 @@ pub struct ValidatorSetUpdate { /// Event indicating a new smart contract has been /// deployed or upgraded on Ethereum +#[derive(Clone, Debug, PartialEq)] pub(super) struct ChangedContract { /// Name of the contract pub(super) name: String, /// Address of the contract on Ethereum - pub (super) address: EthAddress, + pub(super) address: EthAddress, } /// Event for whitelisting new tokens and their /// rate limits +#[derive(Clone, Debug, PartialEq)] struct UpdateBridgeWhitelist { /// A monotonically increasing nonce nonce: Uint, @@ -100,7 +105,6 @@ struct UpdateBridgeWhitelist { whitelist: Vec, } - impl PendingEvent { /// Decodes bytes into an [`EthereumEvent`] based on the signature. /// This is is turned into a [`PendingEvent`] along with the block @@ -116,24 +120,18 @@ impl PendingEvent { ) -> Result { match signature { signatures::TRANSFER_TO_NAMADA_SIG => { - RawTransfersToNamada::decode(data) - .map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToNamada( - txs.transfers, - ), - }) + RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToNamada(txs.transfers), + }) } signatures::TRANSFER_TO_ETHEREUM_SIG => { - RawTransfersToEthereum::decode(data) - .map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToEthereum( - txs.transfers, - ), - }) + RawTransfersToEthereum::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToEthereum(txs.transfers), + }) } signatures::VALIDATOR_SET_UPDATE_SIG => { ValidatorSetUpdate::decode(data).map( @@ -152,20 +150,19 @@ impl PendingEvent { }, ) } - signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) - .map(|ChangedContract { name, address }| PendingEvent { + signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data).map( + |ChangedContract { name, address }| PendingEvent { confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::NewContract { name, address }, + }, + ), + signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode(data) + .map(|ChangedContract { name, address }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpgradedContract { name, address }, }), - signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode( - data, - ) - .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::UpgradedContract { name, address }, - }), signatures::UPDATE_BRIDGE_WHITELIST_SIG => { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { @@ -190,6 +187,7 @@ impl PendingEvent { } /// A batch of [`TransferToNamada`] from an Ethereum event +#[derive(Clone, Debug, PartialEq)] pub(super) struct RawTransfersToNamada { /// A list of transfers pub transfers: Vec, @@ -202,6 +200,7 @@ pub(super) struct RawTransfersToNamada { } /// A batch of [`TransferToNamada`] from an Ethereum event +#[derive(Clone, Debug, PartialEq)] pub(super) struct RawTransfersToEthereum { /// A list of transfers pub transfers: Vec, @@ -217,7 +216,6 @@ impl RawTransfersToNamada { /// Parse ABI serialized data from an Ethereum event into /// an instance of [`RawTransfersToNamada`] fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( &[ ParamType::Uint(256), @@ -232,7 +230,8 @@ impl RawTransfersToNamada { .try_into() .map_err(|_| { Error::Decode( - "TransferToNamada signature should contain five types".to_string(), + "TransferToNamada signature should contain five types" + .to_string(), ) })?; @@ -253,7 +252,7 @@ impl RawTransfersToNamada { )) } else { Ok(Self { - transfers: assets + transfers: assets .into_iter() .zip(receivers.into_iter()) .zip(amounts.into_iter()) @@ -286,9 +285,16 @@ impl RawTransfersToNamada { .collect(); let (assets, receivers): (Vec, Vec) = transfers .into_iter() - .map(|TransferToNamada { asset, receiver, .. }| { - (Token::Address(asset.0.into()), Token::String(receiver.to_string())) - }) + .map( + |TransferToNamada { + asset, receiver, .. + }| { + ( + Token::Address(asset.0.into()), + Token::String(receiver.to_string()), + ) + }, + ) .unzip(); encode(&[ @@ -319,7 +325,7 @@ impl RawTransfersToEthereum { .try_into() .map_err(|_| { Error::Decode( - "TransferToERC signature should contain five types".to_string() + "TransferToERC signature should contain five types".to_string(), ) })?; @@ -340,7 +346,7 @@ impl RawTransfersToEthereum { )) } else { Ok(Self { - transfers: assets + transfers: assets .into_iter() .zip(receivers.into_iter()) .zip(amounts.into_iter()) @@ -373,9 +379,16 @@ impl RawTransfersToEthereum { .collect(); let (assets, receivers): (Vec, Vec) = transfers .into_iter() - .map(|TransferToEthereum { asset, receiver, .. }| { - (Token::Address(asset.0.into()), Token::Address(receiver.0.into())) - }) + .map( + |TransferToEthereum { + asset, receiver, .. + }| { + ( + Token::Address(asset.0.into()), + Token::Address(receiver.0.into()), + ) + }, + ) .unzip(); encode(&[ @@ -522,7 +535,11 @@ impl UpdateBridgeWhitelist { ) }) .unzip(); - encode(&[Token::Uint(nonce.into()), Token::Array(tokens), Token::Array(caps)]) + encode(&[ + Token::Uint(nonce.into()), + Token::Array(tokens), + Token::Array(caps), + ]) } } @@ -723,13 +740,152 @@ mod test_events { let string = String::from("test"); let keccak = KeccakHash([2; 32]); - assert_eq!( - decode( - &[ParamType::Address], - encode(&[Token::Address(erc.0.into())]).as_slice() - ) - .expect("Test failed"), - vec![Token::Address(erc.0.into())] + let [token]: [Token; 1] = decode( + &[ParamType::Address], + encode(&[Token::Address(erc.0.into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_eth_address().expect("Test failed"), erc); + + let [token]: [Token; 1] = decode( + &[ParamType::String], + encode(&[Token::String(address.to_string())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_address().expect("Test failed"), address); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(64)], + encode(&[Token::Uint(u64::from(amount).into())]).as_slice(), ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_amount().expect("Test failed"), amount); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(32)], + encode(&[Token::Uint(confs.into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_u32().expect("Test failed"), confs); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(256)], + encode(&[Token::Uint(uint.clone().into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_uint256().expect("Test failed"), uint); + + let [token]: [Token; 1] = decode( + &[ParamType::Bool], + encode(&[Token::Bool(boolean)]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_bool().expect("Test failed"), boolean); + + let [token]: [Token; 1] = decode( + &[ParamType::String], + encode(&[Token::String(string.clone())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_string().expect("Test failed"), string); + + let [token]: [Token; 1] = decode( + &[ParamType::FixedBytes(32)], + encode(&[Token::FixedBytes(keccak.0.to_vec())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_keccak().expect("Test failed"), keccak); + } + + /// Test that serialization and deserialization of + /// complex composite types is a no-op + #[test] + fn test_complex_round_trips() { + let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") + .expect("Test failed"); + let nam_transfers = RawTransfersToNamada { + transfers: vec![ + TransferToNamada { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: address, + }; + 2 + ], + nonce: Uint::from(1), + confirmations: 0, + }; + let eth_transfers = RawTransfersToEthereum { + transfers: vec![ + TransferToEthereum { + amount: Default::default(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]) + }; + 2 + ], + nonce: Uint::from(1), + confirmations: 0, + }; + let update = ValidatorSetUpdate { + nonce: Uint::from(1), + bridge_validator_hash: KeccakHash([1; 32]), + governance_validator_hash: KeccakHash([2; 32]), + }; + let changed = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let whitelist = UpdateBridgeWhitelist { + nonce: Uint::from(1), + whitelist: vec![ + TokenWhitelist { + token: EthAddress([0; 20]), + cap: Amount::from(1000), + }; + 2 + ], + }; + assert_eq!( + RawTransfersToNamada::decode(&nam_transfers.clone().encode()) + .expect("Test failed"), + nam_transfers + ); + assert_eq!( + RawTransfersToEthereum::decode(ð_transfers.clone().encode()) + .expect("Test failed"), + eth_transfers + ); + assert_eq!( + ValidatorSetUpdate::decode(&update.clone().encode()) + .expect("Test failed"), + update + ); + assert_eq!( + ChangedContract::decode(&changed.clone().encode()) + .expect("Test failed"), + changed + ); + assert_eq!( + UpdateBridgeWhitelist::decode(&whitelist.clone().encode()) + .expect("Test failed"), + whitelist + ); } } diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 8912ca203b..d29f0b2226 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -1,14 +1,12 @@ pub mod events; pub mod oracle; #[cfg(feature = "eth-fullnode")] -pub use oracle::{Oracle, run_oracle}; +pub use oracle::{run_oracle, Oracle}; pub mod test_tools; -#[cfg(not(feature="eth-fullnode"))] -pub use test_tools::mock_oracle::{Oracle, run_oracle}; - - use std::ffi::OsString; +#[cfg(not(feature = "eth-fullnode"))] +pub use test_tools::mock_oracle::{run_oracle, Oracle}; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 0c88265690..b7a1ee849b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,10 +7,10 @@ use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; #[cfg(not(test))] use web30::client::Web3; -#[cfg(test)] -use super::test_tools::mock_web3_client::Web3; use super::events::{signatures, PendingEvent}; +#[cfg(test)] +use super::test_tools::mock_web3_client::Web3; /// Minimum number of confirmations needed to trust an Ethereum branch pub(crate) const MIN_CONFIRMATIONS: u64 = 50; @@ -72,7 +72,8 @@ impl Oracle { events .into_iter() .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) && !self.sender.is_closed() + .all(|res| res.is_ok()) + && !self.sender.is_closed() } /// Check if the receiver in the ledger has hung up. @@ -99,7 +100,6 @@ pub async fn run_oracle( /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process async fn run_oracle_aux(oracle: Oracle) { - // Initialize our local state. This includes // the latest block height seen and a queue of events // awaiting a certain number of confirmations @@ -135,9 +135,7 @@ async fn run_oracle_aux(oracle: Oracle) { for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => { - GOVERNANCE_CONTRACT.0.into() - } + signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; // fetch the events for matching the given signature let mut events = loop { @@ -204,16 +202,17 @@ fn process_queue( confirmed } - #[cfg(test)] mod test_oracle { - use tokio::sync::oneshot::{Receiver, channel}; + use anoma::types::ethereum_events::TransferToEthereum; + use tokio::sync::oneshot::{channel, Receiver}; - use super::*; use super::super::test_tools::mock_web3_client::{TestCmd, Web3}; - use crate::node::ledger::ethereum_node::events::{ChangedContract, RawTransfersToEthereum}; + use super::*; + use crate::node::ledger::ethereum_node::events::{ + ChangedContract, RawTransfersToEthereum, + }; use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::MockEventType; - use anoma::types::ethereum_events::TransferToEthereum; /// The data returned from setting up a test struct TestPackage { @@ -266,7 +265,9 @@ mod test_oracle { let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); - admin_channel.send(TestCmd::Unresponsive).expect("Test failed"); + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); drop(eth_recv); oracle.join().expect("Test failed"); } @@ -311,21 +312,22 @@ mod test_oracle { tokio_test::block_on(run_oracle_aux(oracle)); }); // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel.send( - TestCmd::NewHeight(50u32.into()) - ).expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); let new_event = ChangedContract { name: "Test".to_string(), - address: EthAddress([0; 20]) - }.encode(); - admin_channel.send( - TestCmd::NewEvent{ - event_type:MockEventType::NewContract, - data:new_event, + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: new_event, height: 51, - } - ).expect("Test failed"); + }) + .expect("Test failed"); // since height is not updating, we should not receive events let mut time = std::time::Duration::from_secs(1); while time > std::time::Duration::from_millis(10) { @@ -350,25 +352,28 @@ mod test_oracle { tokio_test::block_on(run_oracle_aux(oracle)); }); // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel.send( - TestCmd::NewHeight(50u32.into()) - ).expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); let new_event = ChangedContract { name: "Test".to_string(), - address: EthAddress([0; 20]) - }.encode(); - admin_channel.send( - TestCmd::NewEvent{ + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, data: new_event, height: 100, - } - ).expect("Test failed"); + }) + .expect("Test failed"); // we should not receive events even though the height is large // enough - admin_channel.send(TestCmd::Unresponsive).expect("Test failed"); + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); admin_channel .send(TestCmd::NewHeight(Uint256::from(101u32))) .expect("Test failed"); @@ -380,9 +385,8 @@ mod test_oracle { } // check that when web3 becomes responsive, oracle sends event admin_channel.send(TestCmd::Normal).expect("Test failed"); - let event = eth_recv.blocking_recv() - .expect("Test failed"); - if let EthereumEvent::NewContract {name, address} = event { + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract { name, address } = event { assert_eq!(name.as_str(), "Test"); assert_eq!(address.0, [0; 20]); } else { @@ -406,15 +410,16 @@ mod test_oracle { tokio_test::block_on(run_oracle_aux(oracle)); }); // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel.send( - TestCmd::NewHeight(50u32.into()) - ).expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); // confirmed after 50 blocks let first_event = ChangedContract { name: "Test".to_string(), - address: EthAddress([0; 20]) - }.encode(); + address: EthAddress([0; 20]), + } + .encode(); // confirmed after 75 blocks let second_event = RawTransfersToEthereum { @@ -425,23 +430,24 @@ mod test_oracle { }], nonce: 1.into(), confirmations: 75, - }.encode(); + } + .encode(); // send in the events to the logs - admin_channel.send( - TestCmd::NewEvent { + admin_channel + .send(TestCmd::NewEvent { event_type: MockEventType::TransferToEthereum, data: second_event, height: 125, - } - ).expect("Test failed"); - admin_channel.send( - TestCmd::NewEvent{ + }) + .expect("Test failed"); + admin_channel + .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, data: first_event, height: 100, - } - ).expect("Test failed"); + }) + .expect("Test failed"); // increase block height so first event is confirmed but second is not. admin_channel @@ -449,7 +455,7 @@ mod test_oracle { .expect("Test failed"); // check the correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract {name, address} = event { + if let EthereumEvent::NewContract { name, address } = event { assert_eq!(name.as_str(), "Test"); assert_eq!(address, EthAddress([0; 20])); } else { @@ -487,4 +493,4 @@ mod test_oracle { drop(eth_recv); oracle.join().expect("Test failed"); } -} \ No newline at end of file +} diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 875850fa4a..ad89f82d2c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -1,5 +1,3 @@ - - #[cfg(not(feature = "eth-fullnode"))] /// tools for running a mock ethereum fullnode process pub mod mock_eth_fullnode { @@ -7,7 +5,7 @@ pub mod mock_eth_fullnode { use super::super::Result; - pub struct EthereumNode{ + pub struct EthereumNode { receiver: Receiver<()>, } @@ -16,7 +14,7 @@ pub mod mock_eth_fullnode { impl EthereumNode { pub async fn new(_: &str) -> Result<(EthereumNode, Sender<()>)> { let (abort_sender, receiver) = channel(); - Ok((Self {receiver}, abort_sender)) + Ok((Self { receiver }, abort_sender)) } pub async fn wait(&mut self) -> Result<()> { @@ -43,7 +41,7 @@ pub mod mock_oracle { ) { loop { if abort.is_closed() { - return + return; } } } @@ -55,7 +53,9 @@ pub mod mock_web3_client { use std::fmt::Debug; use num256::Uint256; - use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; + use tokio::sync::mpsc::{ + unbounded_channel, UnboundedReceiver, UnboundedSender, + }; use web30::types::Log; use super::super::events::signatures::*; @@ -67,7 +67,7 @@ pub mod mock_web3_client { Normal, Unresponsive, NewHeight(Uint256), - NewEvent{ + NewEvent { event_type: MockEventType, data: Vec, height: u32, @@ -93,7 +93,7 @@ pub mod mock_web3_client { /// It is not connected to a full node and is fully controllable /// via a channel to allow us to mock different behavior for /// testing purposes. - pub struct Web3Client{ + pub struct Web3Client { cmd_channel: UnboundedReceiver, active: bool, latest_block_height: Uint256, @@ -105,7 +105,10 @@ pub mod mock_web3_client { /// but is not meant to be used in tests #[allow(dead_code)] pub fn new(_: &str, _: std::time::Duration) -> Self { - panic!("Method is here for api completeness. It is not meant to be used in tests.") + panic!( + "Method is here for api completeness. It is not meant to be \ + used in tests." + ) } /// Return a new client and a separate sender @@ -127,18 +130,22 @@ pub mod mock_web3_client { /// Check and apply new incoming commands fn check_cmd_channel(&self) { let cmd = - if let Ok(cmd) = self.0.borrow_mut().cmd_channel.try_recv() { - cmd - } else { - return - }; + if let Ok(cmd) = self.0.borrow_mut().cmd_channel.try_recv() { + cmd + } else { + return; + }; match cmd { TestCmd::Normal => self.0.borrow_mut().active = true, TestCmd::Unresponsive => self.0.borrow_mut().active = false, TestCmd::NewHeight(height) => { self.0.borrow_mut().latest_block_height = height } - TestCmd::NewEvent{event_type: ty, data, height} => self.0.borrow_mut().events.push((ty, data, height)), + TestCmd::NewEvent { + event_type: ty, + data, + height, + } => self.0.borrow_mut().events.push((ty, data, height)), } } @@ -164,7 +171,9 @@ pub mod mock_web3_client { if self.0.borrow().active { let ty = match events.remove(0) { TRANSFER_TO_NAMADA_SIG => MockEventType::TransferToNamada, - TRANSFER_TO_ETHEREUM_SIG => MockEventType::TransferToEthereum, + TRANSFER_TO_ETHEREUM_SIG => { + MockEventType::TransferToEthereum + } VALIDATOR_SET_UPDATE_SIG => MockEventType::ValSetUpdate, NEW_CONTRACT_SIG => MockEventType::NewContract, UPGRADED_CONTRACT_SIG => MockEventType::UpgradedContract, @@ -178,7 +187,9 @@ pub mod mock_web3_client { let mut client = self.0.borrow_mut(); std::mem::swap(&mut client.events, &mut events); for (event_ty, data, height) in events.into_iter() { - if event_ty == ty && client.latest_block_height >= Uint256::from(height) { + if event_ty == ty + && client.latest_block_height >= Uint256::from(height) + { logs.push(Log { data: data.into(), ..Default::default() diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index bc48ab56b3..eb3aea5796 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -353,12 +353,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }); let oracle = local.spawn_local(async move { - ethereum_node::run_oracle( - ðereum_url, - eth_sender, - abort_sender, - ) - .await + ethereum_node::run_oracle(ðereum_url, eth_sender, abort_sender) + .await }); // Shutdown ethereum_node via a message to ensure that the child process diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d5837c6507..4931fe3804 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -769,11 +769,15 @@ mod test_utils { impl TestShell { /// Returns a new shell with - /// - A broadcast receiver, which will receive - /// any protocol txs sent by the shell. - /// - A sender that can send Ethereum events into the ledger, - /// mocking the Ethereum fullnode process - pub fn new() -> (Self, UnboundedReceiver>, UnboundedSender) { + /// - A broadcast receiver, which will receive any protocol txs sent + /// by the shell. + /// - A sender that can send Ethereum events into the ledger, mocking + /// the Ethereum fullnode process + pub fn new() -> ( + Self, + UnboundedReceiver>, + UnboundedSender, + ) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); @@ -797,7 +801,7 @@ mod test_utils { ), }, receiver, - eth_sender + eth_sender, ) } @@ -874,7 +878,11 @@ mod test_utils { /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup() -> (TestShell, UnboundedReceiver>, UnboundedSender) { + pub(super) fn setup() -> ( + TestShell, + UnboundedReceiver>, + UnboundedSender, + ) { let (mut test, receiver, eth_receiver) = TestShell::new(); test.init_chain(RequestInitChain { time: Some(Timestamp { diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 4c05663be2..4431d169b7 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -1,13 +1,15 @@ //! Types representing data intended for Anoma via Ethereum events -use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use crate::types::address::Address; use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct Uint(pub [u64; 4]); impl From for Uint { @@ -29,11 +31,15 @@ impl From for Uint { } /// Representation of address on Ethereum -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct EthAddress(pub [u8; 20]); /// A Keccak hash -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct KeccakHash(pub [u8; 32]); /// An Ethereum event to be processed by the Anoma ledger @@ -90,7 +96,9 @@ pub enum EthereumEvent { } /// An event transferring some kind of value from Ethereum to Anoma -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct TransferToNamada { /// Quantity of the ERC20 token in the transfer pub amount: Amount, @@ -101,7 +109,9 @@ pub struct TransferToNamada { } /// An event transferring some kind of value from Ethereum to Anoma -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct TransferToEthereum { /// Quantity of wrapped Asset in the transfer pub amount: Amount, @@ -111,16 +121,17 @@ pub struct TransferToEthereum { pub receiver: EthAddress, } - /// struct for whitelisting a token from Ethereum. /// Includes the address of issuing contract and /// a cap on the max amount of this token allowed to be /// held by the bridge. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, +)] #[allow(dead_code)] pub struct TokenWhitelist { /// Address of Ethereum smart contract issuing token pub token: EthAddress, /// Maximum amount of token allowed on the bridge pub cap: Amount, -} \ No newline at end of file +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 47c0a9fcae..748475a423 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -2,8 +2,8 @@ pub mod address; pub mod chain; -pub mod ethereum_events; pub mod dylib; +pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; From 00a150104485ba428f3349759d19227c4c938b24 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 1 Jul 2022 13:18:48 +0200 Subject: [PATCH 0055/1995] [chore]: Feature cleanup for clippy reasons for ABCI feature flag --- Makefile | 8 +- apps/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.lock | 252 +++++++++++++++++++++++++- 3 files changed, 255 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c97d9cac17..cf479b935f 100644 --- a/Makefile +++ b/Makefile @@ -22,13 +22,13 @@ build: $(cargo) build build-abci-plus-plus: - $(cargo) build --no-default-features --features "ABCI-plus-plus" + $(cargo) build --no-default-features --features "ABCI-plus-plus eth-fullnode" build-test: $(cargo) build --tests build-test-abci-plus-plus: - $(cargo) build --tests --no-default-features --features "ABCI-plus-plus" + $(cargo) build --tests --no-default-features --features "ABCI-plus-plus eth-fullnode" build-release: ANOMA_DEV=false $(cargo) build --release --package anoma_apps @@ -63,7 +63,7 @@ clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --a 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 && \ + ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets --features eth-fullnode -- -D warnings && \ make -C $(wasms) clippy && \ make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true @@ -164,7 +164,7 @@ test-unit-abci-plus-plus: test-unit: $(cargo) test --no-default-features \ - --features "wasm-runtime ABCI ibc-mocks-abci" \ + --features "wasm-runtime ABCI ibc-mocks-abci eth-fullnode" \ -- --skip e2e test-wasm: diff --git a/apps/Cargo.toml b/apps/Cargo.toml index cab9e3ff94..b915a37313 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -39,7 +39,7 @@ name = "anomaw" path = "src/bin/anoma-wallet/main.rs" [features] -default = ["std", "ABCI", "eth-fullnode"] +default = ["std", "ABCI"] dev = ["anoma/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index d017b5d4a3..eb7b9c8dec 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -50,6 +50,7 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", "ferveo-common", "hex", "ibc", @@ -359,6 +360,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.1" @@ -414,7 +427,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -445,6 +458,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -653,6 +672,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" @@ -875,6 +900,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.7" @@ -913,6 +982,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -945,6 +1026,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -1249,7 +1336,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1270,6 +1357,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1585,6 +1710,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1687,6 +1838,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1696,6 +1860,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1853,6 +2027,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2035,6 +2215,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.23.1" @@ -2058,6 +2248,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2264,6 +2460,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2330,6 +2536,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2380,6 +2592,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.3" @@ -2578,6 +2796,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2830,6 +3057,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3299,6 +3538,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.4" From 6ef67b2a183f9a9fe0ab616ff2175b7038c6c272 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 1 Jul 2022 14:18:46 +0200 Subject: [PATCH 0056/1995] [chore]: Merged in part of eth-integration-branch that I wanted to refactor --- Cargo.lock | 2 + .../lib/node/ledger/ethereum_node/events.rs | 10 +- .../lib/node/ledger/ethereum_node/oracle.rs | 3 +- shared/Cargo.toml | 2 + shared/src/proto/mod.rs | 3 +- shared/src/proto/types.rs | 145 ++++++++++++++ shared/src/types/ethereum_events.rs | 188 +++++++++++++++++- shared/src/types/key/mod.rs | 5 + wasm/tx_template/Cargo.lock | 18 +- wasm/vp_template/Cargo.lock | 18 +- wasm/wasm_source/Cargo.lock | 18 +- wasm_for_tests/wasm_source/Cargo.lock | 18 +- 12 files changed, 416 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0528bb092..21705e213c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", @@ -203,6 +204,7 @@ dependencies = [ "ics23", "itertools 0.10.3", "loupe", + "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 92a2072a65..5fa2a64559 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -123,14 +123,20 @@ impl PendingEvent { RawTransfersToNamada::decode(data).map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::TransfersToNamada(txs.transfers), + event: EthereumEvent::TransfersToNamada { + nonce: txs.nonce, + transfers: txs.transfers, + }, }) } signatures::TRANSFER_TO_ETHEREUM_SIG => { RawTransfersToEthereum::decode(data).map(|txs| PendingEvent { confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::TransfersToEthereum(txs.transfers), + event: EthereumEvent::TransfersToEthereum { + nonce: txs.nonce, + transfers: txs.transfers, + }, }) } signatures::VALIDATOR_SET_UPDATE_SIG => { diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index b7a1ee849b..a156aaa6eb 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -475,7 +475,8 @@ mod test_oracle { .expect("Test failed"); // check correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::TransfersToEthereum(mut transfers) = event { + if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event + { assert_eq!(transfers.len(), 1); let transfer = transfers.remove(0); assert_eq!( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 753a918174..3ad4faec3b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -75,8 +75,10 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" +eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} +num-rational = "0.4.1" 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. diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..adacda806e 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -4,7 +4,8 @@ pub mod generated; mod types; pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, + Dkg, Error, Intent, IntentGossipMessage, IntentId, MultiSigned, Signed, + SignedTxData, Tx, }; #[cfg(test)] diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..9c77e267ec 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -1,8 +1,10 @@ use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use std::hash::{Hash, Hasher}; +use borsh::schema::{Declaration, Definition}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use prost::Message; use serde::{Deserialize, Serialize}; @@ -93,6 +95,28 @@ where } } +impl BorshSchema for Signed +where + T: BorshSerialize + BorshDeserialize + BorshSchema, +{ + fn add_definitions_recursively( + definitions: &mut HashMap, + ) { + let fields = borsh::schema::Fields::NamedFields(borsh::maybestd::vec![ + ("data".to_string(), T::declaration()), + ("sig".to_string(), ::declaration()) + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + T::add_definitions_recursively(definitions); + ::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + format!("Signed<{}>", T::declaration()) + } +} + impl Signed where T: BorshSerialize + BorshDeserialize, @@ -120,6 +144,127 @@ where } } +/// A generic mulit-signed data wrapper for Borsh encode-able data. +#[derive( + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct MultiSigned { + /// Arbitrary data to be signed + pub data: T, + /// The signature of the data + pub sigs: Vec, +} + +impl PartialEq for MultiSigned +where + T: BorshSerialize + BorshDeserialize + PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.data == other.data && self.sigs == other.sigs + } +} + +impl Eq for MultiSigned where + T: BorshSerialize + BorshDeserialize + Eq + PartialEq +{ +} + +impl Hash for MultiSigned +where + T: BorshSerialize + BorshDeserialize + Hash, +{ + fn hash(&self, state: &mut H) { + self.data.hash(state); + self.sigs.hash(state); + } +} + +impl PartialOrd for MultiSigned +where + T: BorshSerialize + BorshDeserialize + PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.data.partial_cmp(&other.data) + } +} + +impl From> for MultiSigned +where + T: BorshSerialize + BorshDeserialize, +{ + fn from(Signed:: { data, sig }: Signed) -> Self { + Self { + data, + sigs: vec![sig], + } + } +} + +impl BorshSchema for MultiSigned +where + T: BorshSerialize + BorshDeserialize + BorshSchema, +{ + fn add_definitions_recursively( + definitions: &mut HashMap, + ) { + let fields = borsh::schema::Fields::NamedFields(borsh::maybestd::vec![ + ("data".to_string(), T::declaration()), + ("sigs".to_string(), >::declaration()) + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + T::add_definitions_recursively(definitions); + >::add_definitions_recursively(definitions); + } + + fn declaration() -> borsh::schema::Declaration { + format!("MultiSigned<{}>", T::declaration()) + } +} + +impl MultiSigned +where + T: BorshSerialize + BorshDeserialize, +{ + /// Initialize a new multi-signed data. + pub fn new(keypair: &common::SecretKey, data: T) -> Self { + let to_sign = data + .try_to_vec() + .expect("Encoding data for signing shouldn't fail"); + let sigs = vec![common::SigScheme::sign(keypair, &to_sign)]; + Self { data, sigs } + } + + /// Verify that the data has been signed by the secret key + /// counterpart of the given public keys. **Public keys and + /// signatures must be in same order** + pub fn verify( + &self, + pks: &[common::PublicKey], + ) -> std::result::Result<(), VerifySigError> { + let bytes = self + .data + .try_to_vec() + .expect("Encoding data for verifying signature shouldn't fail"); + if pks.len() != self.sigs.len() { + return Err(VerifySigError::InsufficientKeys); + } + for (pk, sig) in pks.iter().zip(&self.sigs) { + common::SigScheme::verify_signature_raw(pk, &bytes, sig)?; + } + Ok(()) + } + + /// Add a new signature to the data. + pub fn add_sig(&mut self, keypair: &common::SecretKey) { + let to_sign = self + .data + .try_to_vec() + .expect("Encoding data for signing shouldn't fail"); + self.sigs.push(common::SigScheme::sign(keypair, &to_sign)); + } +} + #[derive( Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, )] diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 4431d169b7..67b2da04f3 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -4,6 +4,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use crate::types::address::Address; +use crate::types::hash::Hash; use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type @@ -47,10 +48,24 @@ pub struct KeccakHash(pub [u8; 32]); pub enum EthereumEvent { /// Event transferring batches of ether or Ethereum based ERC20 tokens /// from Ethereum to wrapped assets on Anoma - TransfersToNamada(Vec), + TransfersToNamada { + /// Monotonically increasing nonce + #[allow(dead_code)] + nonce: Uint, + /// The batch of transfers + #[allow(dead_code)] + transfers: Vec, + }, /// A confirmation event that a batch of transfers have been made /// from Anoma to Ethereum - TransfersToEthereum(Vec), + TransfersToEthereum { + /// Monotonically increasing nonce + #[allow(dead_code)] + nonce: Uint, + /// The batch of transfers + #[allow(dead_code)] + transfers: Vec, + }, /// Event indication that the validator set has been updated /// in the governance contract ValidatorSetUpdate { @@ -95,6 +110,15 @@ pub enum EthereumEvent { }, } +impl EthereumEvent { + /// SHA256 of the Borsh serialization of the [`EthereumEvent`]. + #[allow(dead_code)] + fn hash(&self) -> Result { + let bytes = self.try_to_vec()?; + Ok(Hash::sha256(&bytes)) + } +} + /// An event transferring some kind of value from Ethereum to Anoma #[derive( Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, @@ -135,3 +159,163 @@ pub struct TokenWhitelist { /// Maximum amount of token allowed on the bridge pub cap: Amount, } + +/// Contains types necessary for processing Ethereum events +/// in vote extensions +pub mod vote_extensions { + use std::convert::TryFrom; + + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + use eyre::{eyre, Result}; + use num_rational::Ratio; + + use super::EthereumEvent; + use crate::proto::MultiSigned; + use crate::types::address::Address; + + /// A fraction of the total voting power. This should always be a reduced + /// fraction that is between zero and one inclusive. + #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] + pub struct FractionalVotingPower(Ratio); + + impl FractionalVotingPower { + /// Create a new FractionalVotingPower. It must be between zero and one + /// inclusive. + pub fn new(numer: u64, denom: u64) -> Result { + if denom == 0 { + return Err(eyre!("denominator can't be zero")); + } + let ratio: Ratio = (numer, denom).into(); + if ratio > 1.into() { + return Err(eyre!( + "fractional voting power cannot be greater than one" + )); + } + Ok(Self(ratio)) + } + } + + impl From<&FractionalVotingPower> for (u64, u64) { + fn from(ratio: &FractionalVotingPower) -> Self { + (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) + } + } + + impl BorshSerialize for FractionalVotingPower { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let (numer, denom): (u64, u64) = + TryFrom::<&FractionalVotingPower>::try_from(self).map_err( + |err| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "Could not serialize {:?} to Borsh: {:?}", + self, err + ), + ) + }, + )?; + (numer, denom).serialize(writer) + } + } + + impl BorshDeserialize for FractionalVotingPower { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let (numer, denom): (u64, u64) = + BorshDeserialize::deserialize(buf)?; + Ok(FractionalVotingPower(Ratio::::new(numer, denom))) + } + } + + impl BorshSchema for FractionalVotingPower { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + u64::declaration(), + u64::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "FractionalVotingPower".into() + } + } + + /// This is created by the block proposer based on the Ethereum events + /// included in the vote extensions of the previous Tendermint round + #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] + pub struct MultiSignedEthEvent { + /// Address and voting power of the signing validators + pub signers: Vec<(Address, FractionalVotingPower)>, + /// Events as signed by validators + pub event: MultiSigned, + } + + #[cfg(test)] + mod tests { + use super::*; + use crate::types::ethereum_events::Uint; + use crate::types::hash::Hash; + + /// Test the hashing of an Ethereum event + #[test] + fn test_ethereum_event_hash() { + let nonce = Uint::from(123u64); + let event = EthereumEvent::TransfersToNamada { + nonce, + transfers: vec![], + }; + let hash = event.hash().unwrap(); + + assert_eq!( + hash, + Hash([ + 94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, + 236, 209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, + 217, 204, 80, 38, 224, 23 + ]) + ); + } + + /// This test is ultimately just exercising the underlying + /// library we use for fractions, we want to make sure + /// operators work as expected with our FractionalVotingPower + /// type itself + #[test] + fn test_fractional_voting_power_ord_eq() { + assert!( + FractionalVotingPower::new(2, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + == FractionalVotingPower::new(2, 6).unwrap() + ); + } + + /// Test error handling on the FractionalVotingPower type + #[test] + fn test_fractional_voting_power_valid_fractions() { + assert!(FractionalVotingPower::new(0, 0).is_err()); + assert!(FractionalVotingPower::new(1, 0).is_err()); + assert!(FractionalVotingPower::new(0, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 2).is_ok()); + assert!(FractionalVotingPower::new(3, 2).is_err()); + } + } +} diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..f3654b1599 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -73,6 +73,11 @@ pub enum VerifySigError { MissingData, #[error("Signature belongs to a different scheme from the public key.")] MismatchedScheme, + #[error( + "An incorrect number of signatures was given to a piece of \ + multi-signed data" + )] + InsufficientKeys, } #[allow(missing_docs)] diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 3f55501e90..949f63703e 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -58,6 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", + "num-rational", "parity-wasm", "proptest", "prost", @@ -924,9 +926,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", @@ -1617,6 +1619,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 45c25ca7d0..e757387f1c 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -58,6 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", + "num-rational", "parity-wasm", "proptest", "prost", @@ -924,9 +926,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", @@ -1617,6 +1619,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 4707474507..2be16a2d97 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -58,6 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", + "num-rational", "parity-wasm", "proptest", "prost", @@ -950,9 +952,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", @@ -1643,6 +1645,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index eb7b9c8dec..1d1c2f0097 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -58,6 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", + "num-rational", "parity-wasm", "proptest", "prost", @@ -946,9 +948,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", @@ -1649,6 +1651,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" From eea86c7cb4635d4e7f84e96472b21727f1f7496b Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 1 Jul 2022 18:45:00 +0200 Subject: [PATCH 0057/1995] [chore]: Derive Eq to future proof agains clippy --- shared/src/types/ethereum_events.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 67b2da04f3..d77702a4e5 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -9,7 +9,7 @@ use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct Uint(pub [u64; 4]); @@ -33,13 +33,13 @@ impl From for Uint { /// Representation of address on Ethereum #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct EthAddress(pub [u8; 20]); /// A Keccak hash #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct KeccakHash(pub [u8; 32]); @@ -121,7 +121,7 @@ impl EthereumEvent { /// An event transferring some kind of value from Ethereum to Anoma #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct TransferToNamada { /// Quantity of the ERC20 token in the transfer @@ -134,7 +134,7 @@ pub struct TransferToNamada { /// An event transferring some kind of value from Ethereum to Anoma #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct TransferToEthereum { /// Quantity of wrapped Asset in the transfer @@ -150,7 +150,7 @@ pub struct TransferToEthereum { /// a cap on the max amount of this token allowed to be /// held by the bridge. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] #[allow(dead_code)] pub struct TokenWhitelist { From f49bc50f902d3dd8750d23b15ad7d30c2b531a20 Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Mon, 4 Jul 2022 11:48:54 +0000 Subject: [PATCH 0058/1995] [ci]: update wasm checksums --- wasm/checksums.json | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 40e7ac4fb0..8577ef6347 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.be811dde2e02ce0139e8b3015f8dfca741bc941a087586f3d9768b4ecadd4aac.wasm", - "tx_ethereum_headers.wasm": "tx_ethereum_headers.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.f5ce4107b218b580edc077a7a4324e4486f5ebed44232248add34785f4c2fd0b.wasm", - "tx_ibc.wasm": "tx_ibc.1d9c9c9d14dcc796a506b6699ed7a70522e069287167d9ff856cf8a7ac9c6220.wasm", - "tx_init_account.wasm": "tx_init_account.c6061a7aebe5bbcac423a956e6c65b538964dba1d081b2f9d85e4d64ad3f99ad.wasm", - "tx_init_nft.wasm": "tx_init_nft.95e0214876deff5472f268a6aa3be45476e3c3b01eba6ea98f027b77d56c5206.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7ab644de36cda1abf4d50fc609b2892df747769ad6d3659a1840989e17fa042e.wasm", - "tx_init_validator.wasm": "tx_init_validator.c216fb01f597de955bba3606204d6173475980a5cfde1e6065bd7c9eb6c43c4f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d98cdab6d4c77cb969cc23a7ded432ee03424e6212ce2de35ab476771505997c.wasm", - "tx_transfer.wasm": "tx_transfer.b177bc9b29007557de455655c8e7aa46145d66540b5ef12c6dec133942e60676.wasm", - "tx_unbond.wasm": "tx_unbond.6861a1346ed3a9ac3bc92f95fd10d47b510aa82f0e613ca139c42d656796eede.wasm", - "tx_update_vp.wasm": "tx_update_vp.9ae597c11c7afe0856f9825e82eb0f6a4409e13929052c1a8fbb4e3fcfa882c7.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7f3be2bee815e0e58edb412a17ce9f185a86683b3ec6a2da118fe6e729caf438.wasm", - "tx_withdraw.wasm": "tx_withdraw.5d2f6ee9ae810efff7762eeb52a9ec68dadbcf21e0297575fcc9da5089a306da.wasm", - "vp_nft.wasm": "vp_nft.648a42b9f7a85c60fab42cb49b82ae29556c064f36cae744304984756316e729.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e3113d13da1cee25f991c374ff0e415ea6e1d903f620b4e211e2b6923df2827f.wasm", - "vp_token.wasm": "vp_token.2bbea520df7866b55fd5bbe144f33e911d6e34d63a8980bce0cafd6f69c76453.wasm", - "vp_user.wasm": "vp_user.64d0ad5f5e9f200cfcca2d3c97c2cc69ef49b365a4d12adcf727c39bf3fca0d2.wasm" + "tx_bond.wasm": "tx_bond.acb723b7246f2ce7a6405f09e50388fa8e310cf9af2a16c0a26851daf673cfef.wasm", + "tx_from_intent.wasm": "tx_from_intent.abe7349934da2dc49d62f8aa2cd2420e11ba6ccad681ff7844d86c4bda5cd28a.wasm", + "tx_ibc.wasm": "tx_ibc.786211556a889710576692fb30e4f792d1b77c425667d486e1199e5a1096248c.wasm", + "tx_init_account.wasm": "tx_init_account.84afab1e27de5da88dda8c63ffb7f81ee5c14045744ddf1199322f07bdc2c59d.wasm", + "tx_init_nft.wasm": "tx_init_nft.8ac32640f77bc07d9ad648bb05a46097e0c42ac1aad5b6c3eb62d018edb9d743.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2abdb550d278b1018d257938d3b8528c98ead5e2569b1a0c7c233d89ca5c118b.wasm", + "tx_init_validator.wasm": "tx_init_validator.22bb82f0694b7230ea7b83c1747c146872cb3eadba452e428f58551b13e90d42.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f9a2a40a8f006f11141d7e430a18ff9cf29596b560a51d03df1963e65cde7b2d.wasm", + "tx_transfer.wasm": "tx_transfer.9e5f1b6cdbcdbad4f5243aadc0f5be0a40c42a9c8ade3fa7a32b614ad8c662e6.wasm", + "tx_unbond.wasm": "tx_unbond.9761cb33be82d0bf4bac9c82c1becb742ff3b25260f884a0e2c74491a475a9f8.wasm", + "tx_update_vp.wasm": "tx_update_vp.d6d8b278c7db4ab80c7324eab6c000b374c90f4c2e2f7889a736bdfc36b39b08.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.5018876a523f9caf408df73b2def93968eda915e842109df37b1c5b866936df1.wasm", + "tx_withdraw.wasm": "tx_withdraw.51e44e6adf91f9b294659e2c05b617bf8c01279c59bc690bf8abf8a8b79d81eb.wasm", + "vp_nft.wasm": "vp_nft.b4b9ee0fc0c10ff0eb5428732a0740c2d93a91d1691ff557104620d3d0075ace.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a71c23cdc80c359caf8640ad2bdd26cff5c5da8eb14687df145fc896c599e05f.wasm", + "vp_token.wasm": "vp_token.a13af6aaa0c523f27288c1dd5167c9ff647149b6ee7637ccbe2436309e7bc0e5.wasm", + "vp_user.wasm": "vp_user.a04e63ce8bda0e70a045fab21d5e0366bd89e7aa9d977e520437a47e2f39b7a0.wasm" } \ No newline at end of file From 09900d8d1c6fc35a74247246b43c44cda34f86b3 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 4 Jul 2022 15:10:49 +0200 Subject: [PATCH 0059/1995] [fix]: Fixed some feature flags to get rid of compiler warnings --- .../lib/node/ledger/ethereum_node/events.rs | 1625 +++++++++-------- apps/src/lib/node/ledger/ethereum_node/mod.rs | 2 +- .../lib/node/ledger/ethereum_node/oracle.rs | 897 ++++----- .../node/ledger/ethereum_node/test_tools.rs | 5 +- tests/src/e2e/ledger_tests.rs | 2 + 5 files changed, 1272 insertions(+), 1259 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 5fa2a64559..247bcc7972 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,897 +1,904 @@ -use std::convert::TryInto; -use std::fmt::Debug; -use std::str::FromStr; - -use anoma::types::address::Address; -use anoma::types::ethereum_events::{ - EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, TransferToEthereum, - TransferToNamada, Uint, -}; -use anoma::types::token::Amount; -use ethabi::decode; -#[cfg(test)] -use ethabi::encode; -use ethabi::param_type::ParamType; -use ethabi::token::Token; -use num256::Uint256; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Could not decode Ethereum event: {0}")] - Decode(String), -} -pub type Result = std::result::Result; - -pub mod signatures { - pub const TRANSFER_TO_NAMADA_SIG: &str = - "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; - pub const TRANSFER_TO_ETHEREUM_SIG: &str = - "TransferToErc(uint256,address[],address[],uint256[],uint32)"; - pub const VALIDATOR_SET_UPDATE_SIG: &str = - "ValidatorSetUpdate(uint256,bytes32,bytes32)"; - pub const NEW_CONTRACT_SIG: &str = "NewContract(string,address)"; - pub const UPGRADED_CONTRACT_SIG: &str = "UpgradedContract(string,address)"; - pub const UPDATE_BRIDGE_WHITELIST_SIG: &str = - "UpdateBridgeWhiteList(uint256,address[],uint256[])"; - pub const SIGNATURES: [&str; 6] = [ - TRANSFER_TO_NAMADA_SIG, - TRANSFER_TO_ETHEREUM_SIG, - VALIDATOR_SET_UPDATE_SIG, - NEW_CONTRACT_SIG, - UPGRADED_CONTRACT_SIG, - UPDATE_BRIDGE_WHITELIST_SIG, - ]; - - /// Used to determine which smart contract address - /// a signature belongs to - pub enum SigType { - Bridge, - Governance, +#[cfg(feature = "eth-fullnode")] +pub mod eth_events { + use std::convert::TryInto; + use std::fmt::Debug; + use std::str::FromStr; + + use anoma::types::address::Address; + use anoma::types::ethereum_events::{ + EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, TransferToEthereum, + TransferToNamada, Uint, + }; + use anoma::types::token::Amount; + use ethabi::decode; + #[cfg(test)] + use ethabi::encode; + use ethabi::param_type::ParamType; + use ethabi::token::Token; + use num256::Uint256; + use thiserror::Error; + + #[derive(Error, Debug)] + pub enum Error { + #[error("Could not decode Ethereum event: {0}")] + Decode(String), } - impl From<&str> for SigType { - fn from(sig: &str) -> Self { - match sig { - TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => { - SigType::Bridge + pub type Result = std::result::Result; + + pub mod signatures { + pub const TRANSFER_TO_NAMADA_SIG: &str = + "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; + pub const TRANSFER_TO_ETHEREUM_SIG: &str = + "TransferToErc(uint256,address[],address[],uint256[],uint32)"; + pub const VALIDATOR_SET_UPDATE_SIG: &str = + "ValidatorSetUpdate(uint256,bytes32,bytes32)"; + pub const NEW_CONTRACT_SIG: &str = "NewContract(string,address)"; + pub const UPGRADED_CONTRACT_SIG: &str = "UpgradedContract(string,address)"; + pub const UPDATE_BRIDGE_WHITELIST_SIG: &str = + "UpdateBridgeWhiteList(uint256,address[],uint256[])"; + pub const SIGNATURES: [&str; 6] = [ + TRANSFER_TO_NAMADA_SIG, + TRANSFER_TO_ETHEREUM_SIG, + VALIDATOR_SET_UPDATE_SIG, + NEW_CONTRACT_SIG, + UPGRADED_CONTRACT_SIG, + UPDATE_BRIDGE_WHITELIST_SIG, + ]; + + /// Used to determine which smart contract address + /// a signature belongs to + pub enum SigType { + Bridge, + Governance, + } + + impl From<&str> for SigType { + fn from(sig: &str) -> Self { + match sig { + TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => { + SigType::Bridge + } + _ => SigType::Governance, } - _ => SigType::Governance, } } } -} -/// An event waiting for a certain number of confirmations -/// before being sent to the ledger -pub(super) struct PendingEvent { - /// number of confirmations to consider this event finalized - confirmations: Uint256, - /// the block height from which this event originated - block_height: Uint256, - /// the event itself - pub event: EthereumEvent, -} + /// An event waiting for a certain number of confirmations + /// before being sent to the ledger + pub(super) struct PendingEvent { + /// number of confirmations to consider this event finalized + confirmations: Uint256, + /// the block height from which this event originated + block_height: Uint256, + /// the event itself + pub event: EthereumEvent, + } -/// Event emitted with the validator set changes -#[derive(Clone, Debug, PartialEq)] -pub struct ValidatorSetUpdate { - /// A monotonically increasing nonce - nonce: Uint, - /// Hash of the validators in the bridge contract - bridge_validator_hash: KeccakHash, - /// Hash of the validators in the governance contract - governance_validator_hash: KeccakHash, -} + /// Event emitted with the validator set changes + #[derive(Clone, Debug, PartialEq)] + pub struct ValidatorSetUpdate { + /// A monotonically increasing nonce + nonce: Uint, + /// Hash of the validators in the bridge contract + bridge_validator_hash: KeccakHash, + /// Hash of the validators in the governance contract + governance_validator_hash: KeccakHash, + } -/// Event indicating a new smart contract has been -/// deployed or upgraded on Ethereum -#[derive(Clone, Debug, PartialEq)] -pub(super) struct ChangedContract { - /// Name of the contract - pub(super) name: String, - /// Address of the contract on Ethereum - pub(super) address: EthAddress, -} + /// Event indicating a new smart contract has been + /// deployed or upgraded on Ethereum + #[derive(Clone, Debug, PartialEq)] + pub(super) struct ChangedContract { + /// Name of the contract + pub(super) name: String, + /// Address of the contract on Ethereum + pub(super) address: EthAddress, + } -/// Event for whitelisting new tokens and their -/// rate limits -#[derive(Clone, Debug, PartialEq)] -struct UpdateBridgeWhitelist { - /// A monotonically increasing nonce - nonce: Uint, - /// Tokens to be allowed to be transferred across the bridge - whitelist: Vec, -} + /// Event for whitelisting new tokens and their + /// rate limits + #[derive(Clone, Debug, PartialEq)] + struct UpdateBridgeWhitelist { + /// A monotonically increasing nonce + nonce: Uint, + /// Tokens to be allowed to be transferred across the bridge + whitelist: Vec, + } -impl PendingEvent { - /// Decodes bytes into an [`EthereumEvent`] based on the signature. - /// This is is turned into a [`PendingEvent`] along with the block - /// height passed in here. - /// - /// If the event contains a confirmations field, - /// this is passed to the corresponding [`PendingEvent`] field, - /// otherwise a default is used. - pub fn decode( - signature: &str, - block_height: Uint256, - data: &[u8], - ) -> Result { - match signature { - signatures::TRANSFER_TO_NAMADA_SIG => { - RawTransfersToNamada::decode(data).map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToNamada { - nonce: txs.nonce, - transfers: txs.transfers, - }, - }) - } - signatures::TRANSFER_TO_ETHEREUM_SIG => { - RawTransfersToEthereum::decode(data).map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToEthereum { - nonce: txs.nonce, - transfers: txs.transfers, - }, - }) - } - signatures::VALIDATOR_SET_UPDATE_SIG => { - ValidatorSetUpdate::decode(data).map( - |ValidatorSetUpdate { - nonce, - bridge_validator_hash, - governance_validator_hash, - }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + impl PendingEvent { + /// Decodes bytes into an [`EthereumEvent`] based on the signature. + /// This is is turned into a [`PendingEvent`] along with the block + /// height passed in here. + /// + /// If the event contains a confirmations field, + /// this is passed to the corresponding [`PendingEvent`] field, + /// otherwise a default is used. + pub fn decode( + signature: &str, + block_height: Uint256, + data: &[u8], + ) -> Result { + match signature { + signatures::TRANSFER_TO_NAMADA_SIG => { + RawTransfersToNamada::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToNamada { + nonce: txs.nonce, + transfers: txs.transfers, + }, + }) + } + signatures::TRANSFER_TO_ETHEREUM_SIG => { + RawTransfersToEthereum::decode(data).map(|txs| PendingEvent { + confirmations: txs.confirmations.into(), block_height, - event: EthereumEvent::ValidatorSetUpdate { - nonce, - bridge_validator_hash, - governance_validator_hash, + event: EthereumEvent::TransfersToEthereum { + nonce: txs.nonce, + transfers: txs.transfers, + }, + }) + } + signatures::VALIDATOR_SET_UPDATE_SIG => { + ValidatorSetUpdate::decode(data).map( + |ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + }, }, + ) + } + signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data).map( + |ChangedContract { name, address }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::NewContract { name, address }, }, - ) - } - signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data).map( - |ChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::NewContract { name, address }, - }, - ), - signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode(data) - .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::UpgradedContract { name, address }, - }), - signatures::UPDATE_BRIDGE_WHITELIST_SIG => { - UpdateBridgeWhitelist::decode(data).map( - |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { + ), + signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode(data) + .map(|ChangedContract { name, address }| PendingEvent { confirmations: super::oracle::MIN_CONFIRMATIONS.into(), block_height, - event: EthereumEvent::UpdateBridgeWhitelist { - nonce, - whitelist, + event: EthereumEvent::UpgradedContract { name, address }, + }), + signatures::UPDATE_BRIDGE_WHITELIST_SIG => { + UpdateBridgeWhitelist::decode(data).map( + |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { + confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + block_height, + event: EthereumEvent::UpdateBridgeWhitelist { + nonce, + whitelist, + }, }, - }, - ) + ) + } + _ => unreachable!(), } - _ => unreachable!(), } - } - /// Check if the minimum number of confirmations has been - /// reached at the input block height. - pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations >= height.clone() - self.block_height.clone() + /// Check if the minimum number of confirmations has been + /// reached at the input block height. + pub fn is_confirmed(&self, height: &Uint256) -> bool { + self.confirmations >= height.clone() - self.block_height.clone() + } } -} -/// A batch of [`TransferToNamada`] from an Ethereum event -#[derive(Clone, Debug, PartialEq)] -pub(super) struct RawTransfersToNamada { - /// A list of transfers - pub transfers: Vec, - /// A monotonically increasing nonce - #[allow(dead_code)] - pub nonce: Uint, - /// The number of confirmations needed to consider this batch - /// finalized - pub confirmations: u32, -} + /// A batch of [`TransferToNamada`] from an Ethereum event + #[derive(Clone, Debug, PartialEq)] + pub(super) struct RawTransfersToNamada { + /// A list of transfers + pub transfers: Vec, + /// A monotonically increasing nonce + #[allow(dead_code)] + pub nonce: Uint, + /// The number of confirmations needed to consider this batch + /// finalized + pub confirmations: u32, + } -/// A batch of [`TransferToNamada`] from an Ethereum event -#[derive(Clone, Debug, PartialEq)] -pub(super) struct RawTransfersToEthereum { - /// A list of transfers - pub transfers: Vec, - /// A monotonically increasing nonce - #[allow(dead_code)] - pub nonce: Uint, - /// The number of confirmations needed to consider this batch - /// finalized - pub confirmations: u32, -} + /// A batch of [`TransferToNamada`] from an Ethereum event + #[derive(Clone, Debug, PartialEq)] + pub(super) struct RawTransfersToEthereum { + /// A list of transfers + pub transfers: Vec, + /// A monotonically increasing nonce + #[allow(dead_code)] + pub nonce: Uint, + /// The number of confirmations needed to consider this batch + /// finalized + pub confirmations: u32, + } -impl RawTransfersToNamada { - /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`RawTransfersToNamada`] - fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( - &[ - ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::String)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ParamType::Uint(32), - ], - data, - ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "TransferToNamada signature should contain five types" - .to_string(), + impl RawTransfersToNamada { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`RawTransfersToNamada`] + fn decode(data: &[u8]) -> Result { + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::String)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, ) - })?; - - let assets = assets.parse_eth_address_array()?; - let receivers = receivers.parse_address_array()?; - let amounts = amounts.parse_amount_array()?; - if assets.len() != amounts.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "TransferToNamada signature should contain five types" + .to_string(), + ) + })?; + + let assets = assets.parse_eth_address_array()?; + let receivers = receivers.parse_address_array()?; + let amounts = amounts.parse_amount_array()?; + if assets.len() != amounts.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ transfer amounts" - .into(), - )) - } else if receivers.len() != assets.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ + .into(), + )) + } else if receivers.len() != assets.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ target addresses" - .into(), - )) - } else { - Ok(Self { - transfers: assets - .into_iter() - .zip(receivers.into_iter()) - .zip(amounts.into_iter()) - .map(|((asset, receiver), amount)| TransferToNamada { - amount, - asset, - receiver, - }) - .collect(), - nonce: nonce.parse_uint256()?, - confirmations: confs.parse_u32()?, - }) + .into(), + )) + } else { + Ok(Self { + transfers: assets + .into_iter() + .zip(receivers.into_iter()) + .zip(amounts.into_iter()) + .map(|((asset, receiver), amount)| TransferToNamada { + amount, + asset, + receiver, + }) + .collect(), + nonce: nonce.parse_uint256()?, + confirmations: confs.parse_u32()?, + }) + } } - } - /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's - /// ABI serialization scheme. - #[cfg(test)] - fn encode(self) -> Vec { - let RawTransfersToNamada { - transfers, - nonce, - confirmations, - } = self; - let amounts: Vec = transfers - .iter() - .map(|TransferToNamada { amount, .. }| { - Token::Uint(u64::from(*amount).into()) - }) - .collect(); - let (assets, receivers): (Vec, Vec) = transfers - .into_iter() - .map( - |TransferToNamada { - asset, receiver, .. - }| { - ( - Token::Address(asset.0.into()), - Token::String(receiver.to_string()), - ) - }, - ) - .unzip(); - - encode(&[ - Token::Uint(nonce.into()), - Token::Array(assets), - Token::Array(receivers), - Token::Array(amounts), - Token::Uint(confirmations.into()), - ]) + /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + fn encode(self) -> Vec { + let RawTransfersToNamada { + transfers, + nonce, + confirmations, + } = self; + let amounts: Vec = transfers + .iter() + .map(|TransferToNamada { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (assets, receivers): (Vec, Vec) = transfers + .into_iter() + .map( + |TransferToNamada { + asset, receiver, .. + }| { + ( + Token::Address(asset.0.into()), + Token::String(receiver.to_string()), + ) + }, + ) + .unzip(); + + encode(&[ + Token::Uint(nonce.into()), + Token::Array(assets), + Token::Array(receivers), + Token::Array(amounts), + Token::Uint(confirmations.into()), + ]) + } } -} -impl RawTransfersToEthereum { - /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`RawTransfersToEthereum`] - fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( - &[ - ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ParamType::Uint(32), - ], - data, - ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "TransferToERC signature should contain five types".to_string(), + impl RawTransfersToEthereum { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`RawTransfersToEthereum`] + fn decode(data: &[u8]) -> Result { + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, ) - })?; - - let assets = assets.parse_eth_address_array()?; - let receivers = receivers.parse_eth_address_array()?; - let amounts = amounts.parse_amount_array()?; - if assets.len() != amounts.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "TransferToERC signature should contain five types".to_string(), + ) + })?; + + let assets = assets.parse_eth_address_array()?; + let receivers = receivers.parse_eth_address_array()?; + let amounts = amounts.parse_amount_array()?; + if assets.len() != amounts.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ transfer amounts" - .into(), - )) - } else if receivers.len() != assets.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ + .into(), + )) + } else if receivers.len() != assets.len() { + Err(Error::Decode( + "Number of source addresses is different from number of \ target addresses" - .into(), - )) - } else { - Ok(Self { - transfers: assets - .into_iter() - .zip(receivers.into_iter()) - .zip(amounts.into_iter()) - .map(|((asset, receiver), amount)| TransferToEthereum { - amount, - asset, - receiver, - }) - .collect(), - nonce: nonce.parse_uint256()?, - confirmations: confs.parse_u32()?, - }) + .into(), + )) + } else { + Ok(Self { + transfers: assets + .into_iter() + .zip(receivers.into_iter()) + .zip(amounts.into_iter()) + .map(|((asset, receiver), amount)| TransferToEthereum { + amount, + asset, + receiver, + }) + .collect(), + nonce: nonce.parse_uint256()?, + confirmations: confs.parse_u32()?, + }) + } } - } - /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's - /// ABI serialization scheme. - #[cfg(test)] - pub(super) fn encode(self) -> Vec { - let RawTransfersToEthereum { - transfers, - nonce, - confirmations, - } = self; - let amounts: Vec = transfers - .iter() - .map(|TransferToEthereum { amount, .. }| { - Token::Uint(u64::from(*amount).into()) - }) - .collect(); - let (assets, receivers): (Vec, Vec) = transfers - .into_iter() - .map( - |TransferToEthereum { - asset, receiver, .. - }| { - ( - Token::Address(asset.0.into()), - Token::Address(receiver.0.into()), - ) - }, - ) - .unzip(); - - encode(&[ - Token::Uint(nonce.into()), - Token::Array(assets), - Token::Array(receivers), - Token::Array(amounts), - Token::Uint(confirmations.into()), - ]) + /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + pub(super) fn encode(self) -> Vec { + let RawTransfersToEthereum { + transfers, + nonce, + confirmations, + } = self; + let amounts: Vec = transfers + .iter() + .map(|TransferToEthereum { amount, .. }| { + Token::Uint(u64::from(*amount).into()) + }) + .collect(); + let (assets, receivers): (Vec, Vec) = transfers + .into_iter() + .map( + |TransferToEthereum { + asset, receiver, .. + }| { + ( + Token::Address(asset.0.into()), + Token::Address(receiver.0.into()), + ) + }, + ) + .unzip(); + + encode(&[ + Token::Uint(nonce.into()), + Token::Array(assets), + Token::Array(receivers), + Token::Array(amounts), + Token::Uint(confirmations.into()), + ]) + } } -} -impl ValidatorSetUpdate { - /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`ValidatorSetUpdate`] - fn decode(data: &[u8]) -> Result { - let [nonce, bridge_validator_hash, goverance_validator_hash]: [Token; - 3] = decode( - &[ - ParamType::Uint(256), - ParamType::FixedBytes(32), - ParamType::FixedBytes(32), - ], - data, - ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "ValidatorSetUpdate signature should contain three types" - .into(), + impl ValidatorSetUpdate { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`ValidatorSetUpdate`] + fn decode(data: &[u8]) -> Result { + let [nonce, bridge_validator_hash, goverance_validator_hash]: [Token; + 3] = decode( + &[ + ParamType::Uint(256), + ParamType::FixedBytes(32), + ParamType::FixedBytes(32), + ], + data, ) - })?; - - Ok(Self { - nonce: nonce.parse_uint256()?, - bridge_validator_hash: bridge_validator_hash.parse_keccak()?, - governance_validator_hash: goverance_validator_hash - .parse_keccak()?, - }) - } - - /// Serialize an instance [`ValidatorSetUpdate`] using Ethereum's - /// ABI serialization scheme. - #[cfg(test)] - fn encode(self) -> Vec { - let ValidatorSetUpdate { - nonce, - bridge_validator_hash, - governance_validator_hash, - } = self; - - encode(&[ - Token::Uint(nonce.into()), - Token::FixedBytes(bridge_validator_hash.0.into()), - Token::FixedBytes(governance_validator_hash.0.into()), - ]) - } -} - -impl ChangedContract { - /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`ChangedContract`] - fn decode(data: &[u8]) -> Result { - let [name, address]: [Token; 2] = - decode(&[ParamType::String, ParamType::Address], data) .map_err(|err| Error::Decode(format!("{:?}", err)))? .try_into() .map_err(|_| { Error::Decode( - "ContractUpdate signature should contain two types" + "ValidatorSetUpdate signature should contain three types" .into(), ) })?; - Ok(Self { - name: name.parse_string()?, - address: address.parse_eth_address()?, - }) - } + Ok(Self { + nonce: nonce.parse_uint256()?, + bridge_validator_hash: bridge_validator_hash.parse_keccak()?, + governance_validator_hash: goverance_validator_hash + .parse_keccak()?, + }) + } - /// Serialize an instance [`ChangedContract`] using Ethereum's - /// ABI serialization scheme. - #[cfg(test)] - pub(super) fn encode(self) -> Vec { - let ChangedContract { name, address } = self; - encode(&[Token::String(name), Token::Address(address.0.into())]) + /// Serialize an instance [`ValidatorSetUpdate`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + fn encode(self) -> Vec { + let ValidatorSetUpdate { + nonce, + bridge_validator_hash, + governance_validator_hash, + } = self; + + encode(&[ + Token::Uint(nonce.into()), + Token::FixedBytes(bridge_validator_hash.0.into()), + Token::FixedBytes(governance_validator_hash.0.into()), + ]) + } } -} -impl UpdateBridgeWhitelist { - /// Parse ABI serialized data from an Ethereum event into - /// an instance of [`UpdateBridgeWhitelist`] - fn decode(data: &[u8]) -> Result { - let [nonce, tokens, caps]: [Token; 3] = decode( - &[ - ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ], - data, - ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "UpdatedBridgeWhitelist signature should contain three types" - .into(), - ) - })?; + impl ChangedContract { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`ChangedContract`] + fn decode(data: &[u8]) -> Result { + let [name, address]: [Token; 2] = + decode(&[ParamType::String, ParamType::Address], data) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "ContractUpdate signature should contain two types" + .into(), + ) + })?; - let tokens = tokens.parse_eth_address_array()?; - let caps = caps.parse_amount_array()?; - if tokens.len() != caps.len() { - Err(Error::Decode( - "UpdatedBridgeWhitelist received different number of token \ - address and token caps" - .into(), - )) - } else { Ok(Self { - nonce: nonce.parse_uint256()?, - whitelist: tokens - .into_iter() - .zip(caps.into_iter()) - .map(|(token, cap)| TokenWhitelist { token, cap }) - .collect(), + name: name.parse_string()?, + address: address.parse_eth_address()?, }) } - } - /// Serialize an instance [`UpdateBridgeWhitelist`] using Ethereum's - /// ABI serialization scheme. - #[cfg(test)] - fn encode(self) -> Vec { - let UpdateBridgeWhitelist { nonce, whitelist } = self; - - let (tokens, caps): (Vec, Vec) = whitelist - .into_iter() - .map(|TokenWhitelist { token, cap }| { - ( - Token::Address(token.0.into()), - Token::Uint(u64::from(cap).into()), - ) - }) - .unzip(); - encode(&[ - Token::Uint(nonce.into()), - Token::Array(tokens), - Token::Array(caps), - ]) + /// Serialize an instance [`ChangedContract`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + pub(super) fn encode(self) -> Vec { + let ChangedContract { name, address } = self; + encode(&[Token::String(name), Token::Address(address.0.into())]) + } } -} -/// Trait to add parsing methods to `Token`, which is a -/// foreign type -trait Parse { - fn parse_eth_address(self) -> Result; - fn parse_address(self) -> Result
; - fn parse_amount(self) -> Result; - fn parse_u32(self) -> Result; - fn parse_uint256(self) -> Result; - fn parse_bool(self) -> Result; - fn parse_string(self) -> Result; - fn parse_keccak(self) -> Result; - fn parse_amount_array(self) -> Result>; - fn parse_eth_address_array(self) -> Result>; - fn parse_address_array(self) -> Result>; - fn parse_string_array(self) -> Result>; -} + impl UpdateBridgeWhitelist { + /// Parse ABI serialized data from an Ethereum event into + /// an instance of [`UpdateBridgeWhitelist`] + fn decode(data: &[u8]) -> Result { + let [nonce, tokens, caps]: [Token; 3] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "UpdatedBridgeWhitelist signature should contain three types" + .into(), + ) + })?; -impl Parse for Token { - fn parse_eth_address(self) -> Result { - if let Token::Address(addr) = self { - Ok(EthAddress(addr.0)) - } else { - Err(Error::Decode(format!( - "Expected type `Address`, got {:?}", - self - ))) + let tokens = tokens.parse_eth_address_array()?; + let caps = caps.parse_amount_array()?; + if tokens.len() != caps.len() { + Err(Error::Decode( + "UpdatedBridgeWhitelist received different number of token \ + address and token caps" + .into(), + )) + } else { + Ok(Self { + nonce: nonce.parse_uint256()?, + whitelist: tokens + .into_iter() + .zip(caps.into_iter()) + .map(|(token, cap)| TokenWhitelist { token, cap }) + .collect(), + }) + } } - } - fn parse_address(self) -> Result
{ - if let Token::String(addr) = self { - Address::from_str(&addr) - .map_err(|err| Error::Decode(format!("{:?}", err))) - } else { - Err(Error::Decode(format!( - "Expected type `String`, got {:?}", - self - ))) + /// Serialize an instance [`UpdateBridgeWhitelist`] using Ethereum's + /// ABI serialization scheme. + #[cfg(test)] + fn encode(self) -> Vec { + let UpdateBridgeWhitelist { nonce, whitelist } = self; + + let (tokens, caps): (Vec, Vec) = whitelist + .into_iter() + .map(|TokenWhitelist { token, cap }| { + ( + Token::Address(token.0.into()), + Token::Uint(u64::from(cap).into()), + ) + }) + .unzip(); + encode(&[ + Token::Uint(nonce.into()), + Token::Array(tokens), + Token::Array(caps), + ]) } } - fn parse_amount(self) -> Result { - if let Token::Uint(amount) = self { - Ok(Amount::from(amount.as_u64())) - } else { - Err(Error::Decode(format!( - "Expected type `Uint`, got {:?}", - self - ))) - } + /// Trait to add parsing methods to `Token`, which is a + /// foreign type + trait Parse { + fn parse_eth_address(self) -> Result; + fn parse_address(self) -> Result
; + fn parse_amount(self) -> Result; + fn parse_u32(self) -> Result; + fn parse_uint256(self) -> Result; + fn parse_bool(self) -> Result; + fn parse_string(self) -> Result; + fn parse_keccak(self) -> Result; + fn parse_amount_array(self) -> Result>; + fn parse_eth_address_array(self) -> Result>; + fn parse_address_array(self) -> Result>; + fn parse_string_array(self) -> Result>; } - fn parse_u32(self) -> Result { - if let Token::Uint(amount) = self { - Ok(amount.as_u32()) - } else { - Err(Error::Decode(format!( - "Expected type `Uint`, got {:?}", - self - ))) + impl Parse for Token { + fn parse_eth_address(self) -> Result { + if let Token::Address(addr) = self { + Ok(EthAddress(addr.0)) + } else { + Err(Error::Decode(format!( + "Expected type `Address`, got {:?}", + self + ))) + } } - } - fn parse_uint256(self) -> Result { - if let Token::Uint(uint) = self { - Ok(uint.into()) - } else { - Err(Error::Decode(format!( - "Expected type `Uint`, got {:?}", - self - ))) + fn parse_address(self) -> Result
{ + if let Token::String(addr) = self { + Address::from_str(&addr) + .map_err(|err| Error::Decode(format!("{:?}", err))) + } else { + Err(Error::Decode(format!( + "Expected type `String`, got {:?}", + self + ))) + } } - } - fn parse_bool(self) -> Result { - if let Token::Bool(b) = self { - Ok(b) - } else { - Err(Error::Decode(format!( - "Expected type `bool`, got {:?}", - self - ))) + fn parse_amount(self) -> Result { + if let Token::Uint(amount) = self { + Ok(Amount::from(amount.as_u64())) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } } - } - fn parse_string(self) -> Result { - if let Token::String(string) = self { - Ok(string) - } else { - Err(Error::Decode(format!( - "Expected type `String`, got {:?}", - self - ))) + fn parse_u32(self) -> Result { + if let Token::Uint(amount) = self { + Ok(amount.as_u32()) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } } - } - fn parse_keccak(self) -> Result { - if let Token::FixedBytes(bytes) = self { - let bytes = bytes.try_into().map_err(|_| { - Error::Decode("Expect 32 bytes for a Keccak hash".into()) - })?; - Ok(KeccakHash(bytes)) - } else { - Err(Error::Decode(format!( - "Expected type `FixedBytes`, got {:?}", - self - ))) + fn parse_uint256(self) -> Result { + if let Token::Uint(uint) = self { + Ok(uint.into()) + } else { + Err(Error::Decode(format!( + "Expected type `Uint`, got {:?}", + self + ))) + } } - } - fn parse_amount_array(self) -> Result> { - let array = if let Token::Array(array) = self { - array - } else { - return Err(Error::Decode(format!( - "Expected type `Array`, got {:?}", - self - ))); - }; - let mut amounts = vec![]; - for token in array.into_iter() { - let amount = token.parse_amount()?; - amounts.push(amount); + fn parse_bool(self) -> Result { + if let Token::Bool(b) = self { + Ok(b) + } else { + Err(Error::Decode(format!( + "Expected type `bool`, got {:?}", + self + ))) + } } - Ok(amounts) - } - fn parse_eth_address_array(self) -> Result> { - let array = if let Token::Array(array) = self { - array - } else { - return Err(Error::Decode(format!( - "Expected type `Array`, got {:?}", - self - ))); - }; - let mut addrs = vec![]; - for token in array.into_iter() { - let addr = token.parse_eth_address()?; - addrs.push(addr); + fn parse_string(self) -> Result { + if let Token::String(string) = self { + Ok(string) + } else { + Err(Error::Decode(format!( + "Expected type `String`, got {:?}", + self + ))) + } } - Ok(addrs) - } - fn parse_address_array(self) -> Result> { - let array = if let Token::Array(array) = self { - array - } else { - return Err(Error::Decode(format!( - "Expected type `Array`, got {:?}", - self - ))); - }; - let mut addrs = vec![]; - for token in array.into_iter() { - let addr = token.parse_address()?; - addrs.push(addr); + fn parse_keccak(self) -> Result { + if let Token::FixedBytes(bytes) = self { + let bytes = bytes.try_into().map_err(|_| { + Error::Decode("Expect 32 bytes for a Keccak hash".into()) + })?; + Ok(KeccakHash(bytes)) + } else { + Err(Error::Decode(format!( + "Expected type `FixedBytes`, got {:?}", + self + ))) + } } - Ok(addrs) - } - fn parse_string_array(self) -> Result> { - let array = if let Token::Array(array) = self { - array - } else { - return Err(Error::Decode(format!( - "Expected type `Array`, got {:?}", - self - ))); - }; - let mut strings = vec![]; - for token in array.into_iter() { - let string = token.parse_string()?; - strings.push(string); + fn parse_amount_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut amounts = vec![]; + for token in array.into_iter() { + let amount = token.parse_amount()?; + amounts.push(amount); + } + Ok(amounts) + } + + fn parse_eth_address_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut addrs = vec![]; + for token in array.into_iter() { + let addr = token.parse_eth_address()?; + addrs.push(addr); + } + Ok(addrs) + } + + fn parse_address_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut addrs = vec![]; + for token in array.into_iter() { + let addr = token.parse_address()?; + addrs.push(addr); + } + Ok(addrs) } - Ok(strings) - } -} -#[cfg(test)] -mod test_events { - use super::*; - - /// For each of the basic types, test that roundtrip - /// encoding - decoding is a no-op - #[test] - fn test_round_trips() { - let erc = EthAddress([1; 20]); - let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") - .expect("Test failed"); - let amount = Amount::from(42u64); - let confs = 50u32; - let uint = Uint::from(42u64); - let boolean = true; - let string = String::from("test"); - let keccak = KeccakHash([2; 32]); - - let [token]: [Token; 1] = decode( - &[ParamType::Address], - encode(&[Token::Address(erc.0.into())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_eth_address().expect("Test failed"), erc); - - let [token]: [Token; 1] = decode( - &[ParamType::String], - encode(&[Token::String(address.to_string())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_address().expect("Test failed"), address); - - let [token]: [Token; 1] = decode( - &[ParamType::Uint(64)], - encode(&[Token::Uint(u64::from(amount).into())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_amount().expect("Test failed"), amount); - - let [token]: [Token; 1] = decode( - &[ParamType::Uint(32)], - encode(&[Token::Uint(confs.into())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_u32().expect("Test failed"), confs); - - let [token]: [Token; 1] = decode( - &[ParamType::Uint(256)], - encode(&[Token::Uint(uint.clone().into())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_uint256().expect("Test failed"), uint); - - let [token]: [Token; 1] = decode( - &[ParamType::Bool], - encode(&[Token::Bool(boolean)]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_bool().expect("Test failed"), boolean); - - let [token]: [Token; 1] = decode( - &[ParamType::String], - encode(&[Token::String(string.clone())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_string().expect("Test failed"), string); - - let [token]: [Token; 1] = decode( - &[ParamType::FixedBytes(32)], - encode(&[Token::FixedBytes(keccak.0.to_vec())]).as_slice(), - ) - .expect("Test failed") - .try_into() - .expect("Test failed"); - assert_eq!(token.parse_keccak().expect("Test failed"), keccak); + fn parse_string_array(self) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut strings = vec![]; + for token in array.into_iter() { + let string = token.parse_string()?; + strings.push(string); + } + Ok(strings) + } } - /// Test that serialization and deserialization of - /// complex composite types is a no-op - #[test] - fn test_complex_round_trips() { - let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") - .expect("Test failed"); - let nam_transfers = RawTransfersToNamada { - transfers: vec![ - TransferToNamada { - amount: Default::default(), - asset: EthAddress([0; 20]), - receiver: address, - }; - 2 - ], - nonce: Uint::from(1), - confirmations: 0, - }; - let eth_transfers = RawTransfersToEthereum { - transfers: vec![ - TransferToEthereum { - amount: Default::default(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]) - }; - 2 - ], - nonce: Uint::from(1), - confirmations: 0, - }; - let update = ValidatorSetUpdate { - nonce: Uint::from(1), - bridge_validator_hash: KeccakHash([1; 32]), - governance_validator_hash: KeccakHash([2; 32]), - }; - let changed = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - }; - let whitelist = UpdateBridgeWhitelist { - nonce: Uint::from(1), - whitelist: vec![ - TokenWhitelist { - token: EthAddress([0; 20]), - cap: Amount::from(1000), - }; - 2 - ], - }; - assert_eq!( - RawTransfersToNamada::decode(&nam_transfers.clone().encode()) - .expect("Test failed"), - nam_transfers - ); - assert_eq!( - RawTransfersToEthereum::decode(ð_transfers.clone().encode()) - .expect("Test failed"), - eth_transfers - ); - assert_eq!( - ValidatorSetUpdate::decode(&update.clone().encode()) - .expect("Test failed"), - update - ); - assert_eq!( - ChangedContract::decode(&changed.clone().encode()) - .expect("Test failed"), - changed - ); - assert_eq!( - UpdateBridgeWhitelist::decode(&whitelist.clone().encode()) - .expect("Test failed"), - whitelist - ); + #[cfg(test)] + mod test_events { + use super::*; + + /// For each of the basic types, test that roundtrip + /// encoding - decoding is a no-op + #[test] + fn test_round_trips() { + let erc = EthAddress([1; 20]); + let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") + .expect("Test failed"); + let amount = Amount::from(42u64); + let confs = 50u32; + let uint = Uint::from(42u64); + let boolean = true; + let string = String::from("test"); + let keccak = KeccakHash([2; 32]); + + let [token]: [Token; 1] = decode( + &[ParamType::Address], + encode(&[Token::Address(erc.0.into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_eth_address().expect("Test failed"), erc); + + let [token]: [Token; 1] = decode( + &[ParamType::String], + encode(&[Token::String(address.to_string())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_address().expect("Test failed"), address); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(64)], + encode(&[Token::Uint(u64::from(amount).into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_amount().expect("Test failed"), amount); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(32)], + encode(&[Token::Uint(confs.into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_u32().expect("Test failed"), confs); + + let [token]: [Token; 1] = decode( + &[ParamType::Uint(256)], + encode(&[Token::Uint(uint.clone().into())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_uint256().expect("Test failed"), uint); + + let [token]: [Token; 1] = decode( + &[ParamType::Bool], + encode(&[Token::Bool(boolean)]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_bool().expect("Test failed"), boolean); + + let [token]: [Token; 1] = decode( + &[ParamType::String], + encode(&[Token::String(string.clone())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_string().expect("Test failed"), string); + + let [token]: [Token; 1] = decode( + &[ParamType::FixedBytes(32)], + encode(&[Token::FixedBytes(keccak.0.to_vec())]).as_slice(), + ) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(token.parse_keccak().expect("Test failed"), keccak); + } + + /// Test that serialization and deserialization of + /// complex composite types is a no-op + #[test] + fn test_complex_round_trips() { + let address = Address::from_str("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90") + .expect("Test failed"); + let nam_transfers = RawTransfersToNamada { + transfers: vec![ + TransferToNamada { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: address, + }; + 2 + ], + nonce: Uint::from(1), + confirmations: 0, + }; + let eth_transfers = RawTransfersToEthereum { + transfers: vec![ + TransferToEthereum { + amount: Default::default(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]) + }; + 2 + ], + nonce: Uint::from(1), + confirmations: 0, + }; + let update = ValidatorSetUpdate { + nonce: Uint::from(1), + bridge_validator_hash: KeccakHash([1; 32]), + governance_validator_hash: KeccakHash([2; 32]), + }; + let changed = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let whitelist = UpdateBridgeWhitelist { + nonce: Uint::from(1), + whitelist: vec![ + TokenWhitelist { + token: EthAddress([0; 20]), + cap: Amount::from(1000), + }; + 2 + ], + }; + assert_eq!( + RawTransfersToNamada::decode(&nam_transfers.clone().encode()) + .expect("Test failed"), + nam_transfers + ); + assert_eq!( + RawTransfersToEthereum::decode(ð_transfers.clone().encode()) + .expect("Test failed"), + eth_transfers + ); + assert_eq!( + ValidatorSetUpdate::decode(&update.clone().encode()) + .expect("Test failed"), + update + ); + assert_eq!( + ChangedContract::decode(&changed.clone().encode()) + .expect("Test failed"), + changed + ); + assert_eq!( + UpdateBridgeWhitelist::decode(&whitelist.clone().encode()) + .expect("Test failed"), + whitelist + ); + } } } + +#[cfg(feature = "eth-fullnode")] +pub use eth_events::*; \ No newline at end of file diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index d29f0b2226..13977490f5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -6,7 +6,7 @@ pub mod test_tools; use std::ffi::OsString; #[cfg(not(feature = "eth-fullnode"))] -pub use test_tools::mock_oracle::{run_oracle, Oracle}; +pub use test_tools::mock_oracle::run_oracle; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index a156aaa6eb..847c2f9b5e 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,497 +1,504 @@ -use std::ops::Deref; - -use anoma::types::ethereum_events::{EthAddress, EthereumEvent}; -use clarity::Address; -use num256::Uint256; -use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::oneshot::Sender; -#[cfg(not(test))] -use web30::client::Web3; - -use super::events::{signatures, PendingEvent}; -#[cfg(test)] -use super::test_tools::mock_web3_client::Web3; - -/// Minimum number of confirmations needed to trust an Ethereum branch -pub(crate) const MIN_CONFIRMATIONS: u64 = 50; - -/// Dummy addresses for smart contracts -const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); -const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); - -/// A client that can talk to geth and parse -/// and relay events relevant to Anoma to the -/// ledger process -pub struct Oracle { - /// The client that talks to the Ethereum fullnode - client: Web3, - /// A channel for sending processed and confirmed - /// events to the ledger process - sender: UnboundedSender, - /// A channel to signal that the ledger should shut down - /// because the Oracle has stopped - abort: Option>, -} +#[cfg(feature = "eth-fullnode")] +pub mod oracle_process { + use std::ops::Deref; + + use anoma::types::ethereum_events::{EthAddress, EthereumEvent}; + use clarity::Address; + use num256::Uint256; + use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::oneshot::Sender; + #[cfg(not(test))] + use web30::client::Web3; + + use super::MIN_CONFIRMATIONS; + use super::super::events::{signatures, PendingEvent}; + #[cfg(test)] + use super::super::test_tools::mock_web3_client::Web3; + + /// Minimum number of confirmations needed to trust an Ethereum branch + pub(crate) const MIN_CONFIRMATIONS: u64 = 50; + + /// Dummy addresses for smart contracts + const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); + const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); + + /// A client that can talk to geth and parse + /// and relay events relevant to Anoma to the + /// ledger process + pub struct Oracle { + /// The client that talks to the Ethereum fullnode + client: Web3, + /// A channel for sending processed and confirmed + /// events to the ledger process + sender: UnboundedSender, + /// A channel to signal that the ledger should shut down + /// because the Oracle has stopped + abort: Option>, + } -impl Deref for Oracle { - type Target = Web3; + impl Deref for Oracle { + type Target = Web3; - fn deref(&self) -> &Self::Target { - &self.client + fn deref(&self) -> &Self::Target { + &self.client + } } -} -impl Drop for Oracle { - fn drop(&mut self) { - // send an abort signal to shut down the - // rest of the ledger gracefully - let abort = self.abort.take().unwrap(); - let _ = abort.send(()); + impl Drop for Oracle { + fn drop(&mut self) { + // send an abort signal to shut down the + // rest of the ledger gracefully + let abort = self.abort.take().unwrap(); + let _ = abort.send(()); + } } -} -impl Oracle { - /// Initialize a new [`Oracle`] - pub fn new( - url: &str, - sender: UnboundedSender, - abort: Sender<()>, - ) -> Self { - Self { - client: Web3::new(url, std::time::Duration::from_secs(30)), - sender, - abort: Some(abort), + impl Oracle { + /// Initialize a new [`Oracle`] + pub fn new( + url: &str, + sender: UnboundedSender, + abort: Sender<()>, + ) -> Self { + Self { + client: Web3::new(url, std::time::Duration::from_secs(30)), + sender, + abort: Some(abort), + } } - } - /// Send a series of [`EthereumEvent`]s to the Anoma - /// ledger. Returns a boolean indicating that all sent - /// successfully. If false is returned, the receiver - /// has hung up. - fn send(&self, events: Vec) -> bool { - events - .into_iter() - .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) - && !self.sender.is_closed() - } + /// Send a series of [`EthereumEvent`]s to the Anoma + /// ledger. Returns a boolean indicating that all sent + /// successfully. If false is returned, the receiver + /// has hung up. + fn send(&self, events: Vec) -> bool { + events + .into_iter() + .map(|event| self.sender.send(event)) + .all(|res| res.is_ok()) + && !self.sender.is_closed() + } - /// Check if the receiver in the ledger has hung up. - /// Used to help determine when to stop the oracle - fn connected(&self) -> bool { - !self.sender.is_closed() + /// Check if the receiver in the ledger has hung up. + /// Used to help determine when to stop the oracle + fn connected(&self) -> bool { + !self.sender.is_closed() + } } -} -/// Set up an Oracle and run the process where the Oracle -/// processes and forwards Ethereum events to the ledger -pub async fn run_oracle( - url: &str, - sender: UnboundedSender, - abort_sender: Sender<()>, -) { - let oracle = Oracle::new(url, sender, abort_sender); - run_oracle_aux(oracle).await; -} + /// Set up an Oracle and run the process where the Oracle + /// processes and forwards Ethereum events to the ledger + pub async fn run_oracle( + url: &str, + sender: UnboundedSender, + abort_sender: Sender<()>, + ) { + let oracle = Oracle::new(url, sender, abort_sender); + run_oracle_aux(oracle).await; + } -/// Given an oracle, watch for new Ethereum events, processing -/// them into Anoma native types. -/// -/// It also checks that once the specified number of confirmations -/// is reached, an event is forwarded to the ledger process -async fn run_oracle_aux(oracle: Oracle) { - // Initialize our local state. This includes - // the latest block height seen and a queue of events - // awaiting a certain number of confirmations - let mut latest_block; - let mut pending: Vec = Vec::new(); - loop { - // update the latest block height - latest_block = loop { - if let Ok(height) = oracle.eth_block_number().await { - break height; - } - if !oracle.connected() { - tracing::info!( + /// Given an oracle, watch for new Ethereum events, processing + /// them into Anoma native types. + /// + /// It also checks that once the specified number of confirmations + /// is reached, an event is forwarded to the ledger process + async fn run_oracle_aux(oracle: Oracle) { + // Initialize our local state. This includes + // the latest block height seen and a queue of events + // awaiting a certain number of confirmations + let mut latest_block; + let mut pending: Vec = Vec::new(); + loop { + // update the latest block height + latest_block = loop { + if let Ok(height) = oracle.eth_block_number().await { + break height; + } + if !oracle.connected() { + tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" ); - return; - } - }; - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - if !oracle.connected() { - tracing::info!( + return; + } + }; + // No blocks in existence yet with enough confirmations + if Uint256::from(MIN_CONFIRMATIONS) > latest_block { + if !oracle.connected() { + tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" ); - return; - } - continue; - } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); - // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. - for sig in signatures::SIGNATURES { - let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), - }; - // fetch the events for matching the given signature - let mut events = loop { - if let Ok(pending) = oracle - .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), - vec![addr], - vec![sig], - ) - .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) - { - break pending; + return; } - if !oracle.connected() { - tracing::info!( + continue; + } + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. + for sig in signatures::SIGNATURES { + let addr: Address = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => MINT_CONTRACT.0.into(), + signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), + }; + // fetch the events for matching the given signature + let mut events = loop { + if let Ok(pending) = oracle + .check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) + .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) + .ok() + }) + .collect::>() + }) + { + break pending; + } + if !oracle.connected() { + tracing::info!( "Ethereum oracle could not send events to the ledger; \ the receiver has hung up. Shutting down" ); - return; - } - }; - pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)) { - tracing::info!( + return; + } + }; + pending.append(&mut events); + if !oracle.send(process_queue(&latest_block, &mut pending)) { + tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" ); - return; + return; + } } } } -} -/// Check which events in the queue have reached their -/// required number of confirmations and remove them -/// from the queue of pending events -fn process_queue( - latest_block: &Uint256, - pending: &mut Vec, -) -> Vec { - let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); - std::mem::swap(&mut pending_tmp, pending); - let mut confirmed = vec![]; - for item in pending_tmp.into_iter() { - if item.is_confirmed(latest_block) { - confirmed.push(item.event); - } else { - pending.push(item); + /// Check which events in the queue have reached their + /// required number of confirmations and remove them + /// from the queue of pending events + fn process_queue( + latest_block: &Uint256, + pending: &mut Vec, + ) -> Vec { + let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); + std::mem::swap(&mut pending_tmp, pending); + let mut confirmed = vec![]; + for item in pending_tmp.into_iter() { + if item.is_confirmed(latest_block) { + confirmed.push(item.event); + } else { + pending.push(item); + } } + confirmed } - confirmed -} -#[cfg(test)] -mod test_oracle { - use anoma::types::ethereum_events::TransferToEthereum; - use tokio::sync::oneshot::{channel, Receiver}; - - use super::super::test_tools::mock_web3_client::{TestCmd, Web3}; - use super::*; - use crate::node::ledger::ethereum_node::events::{ - ChangedContract, RawTransfersToEthereum, - }; - use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::MockEventType; - - /// The data returned from setting up a test - struct TestPackage { - oracle: Oracle, - admin_channel: tokio::sync::mpsc::UnboundedSender, - eth_recv: tokio::sync::mpsc::UnboundedReceiver, - abort_recv: Receiver<()>, - } + #[cfg(test)] + mod test_oracle { + use anoma::types::ethereum_events::TransferToEthereum; + use tokio::sync::oneshot::{channel, Receiver}; - /// Set up an oracle with a mock web3 client that we can contr - fn setup() -> TestPackage { - let (admin_channel, client) = Web3::setup(); - let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); - let (abort, abort_recv) = channel(); - TestPackage { - oracle: Oracle { - client, - sender: eth_sender, - abort: Some(abort), - }, - admin_channel, - eth_recv: eth_receiver, - abort_recv, + use super::super::test_tools::mock_web3_client::{TestCmd, Web3}; + use super::*; + use crate::node::ledger::ethereum_node::events::{ + ChangedContract, RawTransfersToEthereum, + }; + use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::MockEventType; + + /// The data returned from setting up a test + struct TestPackage { + oracle: Oracle, + admin_channel: tokio::sync::mpsc::UnboundedSender, + eth_recv: tokio::sync::mpsc::UnboundedReceiver, + abort_recv: Receiver<()>, } - } - - /// Test that if the oracle shuts down, it - /// sends a message to the fullnode to stop - #[test] - fn test_abort_send() { - let TestPackage { - oracle, - mut abort_recv, - .. - } = setup(); - drop(oracle); - assert!(abort_recv.try_recv().is_ok()) - } - /// Test that if the fullnode stops, the oracle - /// shuts down, even if the web3 client is unresponsive - #[test] - fn test_shutdown() { - let TestPackage { - oracle, - eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); - drop(eth_recv); - oracle.join().expect("Test failed"); - } - - /// Test that if no logs are received from the web3 - /// client, no events are sent out - #[test] - fn test_no_logs_no_op() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - admin_channel - .send(TestCmd::NewHeight(Uint256::from(100u32))) - .expect("Test failed"); - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); + /// Set up an oracle with a mock web3 client that we can contr + fn setup() -> TestPackage { + let (admin_channel, client) = Web3::setup(); + let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (abort, abort_recv) = channel(); + TestPackage { + oracle: Oracle { + client, + sender: eth_sender, + abort: Some(abort), + }, + admin_channel, + eth_recv: eth_receiver, + abort_recv, + } } - drop(eth_recv); - oracle.join().expect("Test failed"); - } - /// Test that if a new block height doesn't increase, - /// no events are sent out even if there are - /// some in the logs. - #[test] - fn test_cant_get_new_height() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - let new_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: new_event, - height: 51, - }) - .expect("Test failed"); - // since height is not updating, we should not receive events - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); + /// Test that if the oracle shuts down, it + /// sends a message to the fullnode to stop + #[test] + fn test_abort_send() { + let TestPackage { + oracle, + mut abort_recv, + .. + } = setup(); + drop(oracle); + assert!(abort_recv.try_recv().is_ok()) } - drop(eth_recv); - oracle.join().expect("Test failed"); - } - /// Test that the oracle waits until new logs - /// are received before sending them on. - #[test] - fn test_wait_on_new_logs() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - let new_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: new_event, - height: 100, - }) - .expect("Test failed"); - - // we should not receive events even though the height is large - // enough - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); - admin_channel - .send(TestCmd::NewHeight(Uint256::from(101u32))) - .expect("Test failed"); - - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); - } - // check that when web3 becomes responsive, oracle sends event - admin_channel.send(TestCmd::Normal).expect("Test failed"); - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract { name, address } = event { - assert_eq!(name.as_str(), "Test"); - assert_eq!(address.0, [0; 20]); - } else { - panic!("Test failed"); + /// Test that if the fullnode stops, the oracle + /// shuts down, even if the web3 client is unresponsive + #[test] + fn test_shutdown() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); + drop(eth_recv); + oracle.join().expect("Test failed"); } - drop(eth_recv); - oracle.join().expect("Test failed"); - } - /// Test that events are only sent when they - /// reach the required number of confirmations - #[test] - fn test_finality_gadget() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - // confirmed after 50 blocks - let first_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); - - // confirmed after 75 blocks - let second_event = RawTransfersToEthereum { - transfers: vec![TransferToEthereum { - amount: Default::default(), - asset: EthAddress([0; 20]), - receiver: EthAddress([1; 20]), - }], - nonce: 1.into(), - confirmations: 75, + /// Test that if no logs are received from the web3 + /// client, no events are sent out + #[test] + fn test_no_logs_no_op() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(100u32))) + .expect("Test failed"); + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + drop(eth_recv); + oracle.join().expect("Test failed"); } - .encode(); - - // send in the events to the logs - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::TransferToEthereum, - data: second_event, - height: 125, - }) - .expect("Test failed"); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: first_event, - height: 100, - }) - .expect("Test failed"); - - // increase block height so first event is confirmed but second is not. - admin_channel - .send(TestCmd::NewHeight(Uint256::from(102u32))) - .expect("Test failed"); - // check the correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract { name, address } = event { - assert_eq!(name.as_str(), "Test"); - assert_eq!(address, EthAddress([0; 20])); - } else { - panic!("Test failed, {:?}", event); + + /// Test that if a new block height doesn't increase, + /// no events are sent out even if there are + /// some in the logs. + #[test] + fn test_cant_get_new_height() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: new_event, + height: 51, + }) + .expect("Test failed"); + // since height is not updating, we should not receive events + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + drop(eth_recv); + oracle.join().expect("Test failed"); } - // check no other events are received - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); + /// Test that the oracle waits until new logs + /// are received before sending them on. + #[test] + fn test_wait_on_new_logs() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: new_event, + height: 100, + }) + .expect("Test failed"); + + // we should not receive events even though the height is large + // enough + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(101u32))) + .expect("Test failed"); + + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + // check that when web3 becomes responsive, oracle sends event + admin_channel.send(TestCmd::Normal).expect("Test failed"); + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract { name, address } = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address.0, [0; 20]); + } else { + panic!("Test failed"); + } + drop(eth_recv); + oracle.join().expect("Test failed"); } - // increase block height so second event is confirmed - admin_channel - .send(TestCmd::NewHeight(Uint256::from(130u32))) - .expect("Test failed"); - // check correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event - { - assert_eq!(transfers.len(), 1); - let transfer = transfers.remove(0); - assert_eq!( - transfer, - TransferToEthereum { + /// Test that events are only sent when they + /// reach the required number of confirmations + #[test] + fn test_finality_gadget() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + // confirmed after 50 blocks + let first_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + + // confirmed after 75 blocks + let second_event = RawTransfersToEthereum { + transfers: vec![TransferToEthereum { amount: Default::default(), asset: EthAddress([0; 20]), receiver: EthAddress([1; 20]), - } - ); - } else { - panic!("Test failed"); - } + }], + nonce: 1.into(), + confirmations: 75, + } + .encode(); + + // send in the events to the logs + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::TransferToEthereum, + data: second_event, + height: 125, + }) + .expect("Test failed"); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: first_event, + height: 100, + }) + .expect("Test failed"); + + // increase block height so first event is confirmed but second is not. + admin_channel + .send(TestCmd::NewHeight(Uint256::from(102u32))) + .expect("Test failed"); + // check the correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract { name, address } = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address, EthAddress([0; 20])); + } else { + panic!("Test failed, {:?}", event); + } + + // check no other events are received + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } - drop(eth_recv); - oracle.join().expect("Test failed"); + // increase block height so second event is confirmed + admin_channel + .send(TestCmd::NewHeight(Uint256::from(130u32))) + .expect("Test failed"); + // check correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event + { + assert_eq!(transfers.len(), 1); + let transfer = transfers.remove(0); + assert_eq!( + transfer, + TransferToEthereum { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: EthAddress([1; 20]), + } + ); + } else { + panic!("Test failed"); + } + + drop(eth_recv); + oracle.join().expect("Test failed"); + } } } + +#[cfg(feature = "eth-fullnode")] +pub use oracle_process::*; \ No newline at end of file diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index ad89f82d2c..f646bc1d87 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -6,11 +6,10 @@ pub mod mock_eth_fullnode { use super::super::Result; pub struct EthereumNode { + #[allow(dead_code)] receiver: Receiver<()>, } - pub struct AbortSender; - impl EthereumNode { pub async fn new(_: &str) -> Result<(EthereumNode, Sender<()>)> { let (abort_sender, receiver) = channel(); @@ -32,8 +31,6 @@ pub mod mock_oracle { use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; - pub struct Oracle; - pub async fn run_oracle( _: &str, _: UnboundedSender, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1b27da09f4..1188310605 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -73,6 +73,7 @@ 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 +#[cfg(not(feature = "ABCI-plus-plus"))] #[test] fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes @@ -1558,6 +1559,7 @@ 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 +#[cfg(not(feature = "ABCI-plus-plus"))] #[test] fn test_genesis_validators() -> Result<()> { // This test is not using the `setup::network`, because we're setting up From a3707a3e485953c78cf266ecdfad9bbfd8f94fa8 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 5 Jul 2022 16:43:41 +0200 Subject: [PATCH 0060/1995] [fix]: More feature flag fiddling --- .../lib/node/ledger/ethereum_node/events.rs | 100 +++++++++--------- .../lib/node/ledger/ethereum_node/oracle.rs | 6 +- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 247bcc7972..15d63b90b4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,3 +1,42 @@ +#[cfg(any(test, feature = "eth-fullnode"))] +pub mod signatures { + pub const TRANSFER_TO_NAMADA_SIG: &str = + "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; + pub const TRANSFER_TO_ETHEREUM_SIG: &str = + "TransferToErc(uint256,address[],address[],uint256[],uint32)"; + pub const VALIDATOR_SET_UPDATE_SIG: &str = + "ValidatorSetUpdate(uint256,bytes32,bytes32)"; + pub const NEW_CONTRACT_SIG: &str = "NewContract(string,address)"; + pub const UPGRADED_CONTRACT_SIG: &str = "UpgradedContract(string,address)"; + pub const UPDATE_BRIDGE_WHITELIST_SIG: &str = + "UpdateBridgeWhiteList(uint256,address[],uint256[])"; + pub const SIGNATURES: [&str; 6] = [ + TRANSFER_TO_NAMADA_SIG, + TRANSFER_TO_ETHEREUM_SIG, + VALIDATOR_SET_UPDATE_SIG, + NEW_CONTRACT_SIG, + UPGRADED_CONTRACT_SIG, + UPDATE_BRIDGE_WHITELIST_SIG, + ]; + + /// Used to determine which smart contract address + /// a signature belongs to + pub enum SigType { + Bridge, + Governance, + } + + impl From<&str> for SigType { + fn from(sig: &str) -> Self { + match sig { + TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => { + SigType::Bridge + } + _ => SigType::Governance, + } + } + } +} #[cfg(feature = "eth-fullnode")] pub mod eth_events { @@ -19,6 +58,8 @@ pub mod eth_events { use num256::Uint256; use thiserror::Error; + pub use super::signatures; + #[derive(Error, Debug)] pub enum Error { #[error("Could not decode Ethereum event: {0}")] @@ -27,48 +68,9 @@ pub mod eth_events { pub type Result = std::result::Result; - pub mod signatures { - pub const TRANSFER_TO_NAMADA_SIG: &str = - "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; - pub const TRANSFER_TO_ETHEREUM_SIG: &str = - "TransferToErc(uint256,address[],address[],uint256[],uint32)"; - pub const VALIDATOR_SET_UPDATE_SIG: &str = - "ValidatorSetUpdate(uint256,bytes32,bytes32)"; - pub const NEW_CONTRACT_SIG: &str = "NewContract(string,address)"; - pub const UPGRADED_CONTRACT_SIG: &str = "UpgradedContract(string,address)"; - pub const UPDATE_BRIDGE_WHITELIST_SIG: &str = - "UpdateBridgeWhiteList(uint256,address[],uint256[])"; - pub const SIGNATURES: [&str; 6] = [ - TRANSFER_TO_NAMADA_SIG, - TRANSFER_TO_ETHEREUM_SIG, - VALIDATOR_SET_UPDATE_SIG, - NEW_CONTRACT_SIG, - UPGRADED_CONTRACT_SIG, - UPDATE_BRIDGE_WHITELIST_SIG, - ]; - - /// Used to determine which smart contract address - /// a signature belongs to - pub enum SigType { - Bridge, - Governance, - } - - impl From<&str> for SigType { - fn from(sig: &str) -> Self { - match sig { - TRANSFER_TO_NAMADA_SIG | TRANSFER_TO_ETHEREUM_SIG => { - SigType::Bridge - } - _ => SigType::Governance, - } - } - } - } - /// An event waiting for a certain number of confirmations /// before being sent to the ledger - pub(super) struct PendingEvent { + pub( in super::super) struct PendingEvent { /// number of confirmations to consider this event finalized confirmations: Uint256, /// the block height from which this event originated @@ -91,11 +93,11 @@ pub mod eth_events { /// Event indicating a new smart contract has been /// deployed or upgraded on Ethereum #[derive(Clone, Debug, PartialEq)] - pub(super) struct ChangedContract { + pub(in super::super) struct ChangedContract { /// Name of the contract - pub(super) name: String, + pub name: String, /// Address of the contract on Ethereum - pub(super) address: EthAddress, + pub address: EthAddress, } /// Event for whitelisting new tokens and their @@ -149,7 +151,7 @@ pub mod eth_events { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -161,21 +163,21 @@ pub mod eth_events { } signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data).map( |ChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::NewContract { name, address }, }, ), signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode(data) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpgradedContract { name, address }, }), signatures::UPDATE_BRIDGE_WHITELIST_SIG => { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { - confirmations: super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpdateBridgeWhitelist { nonce, @@ -210,7 +212,7 @@ pub mod eth_events { /// A batch of [`TransferToNamada`] from an Ethereum event #[derive(Clone, Debug, PartialEq)] - pub(super) struct RawTransfersToEthereum { + pub(in super::super) struct RawTransfersToEthereum { /// A list of transfers pub transfers: Vec, /// A monotonically increasing nonce @@ -482,7 +484,7 @@ pub mod eth_events { /// Serialize an instance [`ChangedContract`] using Ethereum's /// ABI serialization scheme. #[cfg(test)] - pub(super) fn encode(self) -> Vec { + pub fn encode(self) -> Vec { let ChangedContract { name, address } = self; encode(&[Token::String(name), Token::Address(address.0.into())]) } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 847c2f9b5e..8e16800603 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -10,7 +10,6 @@ pub mod oracle_process { #[cfg(not(test))] use web30::client::Web3; - use super::MIN_CONFIRMATIONS; use super::super::events::{signatures, PendingEvent}; #[cfg(test)] use super::super::test_tools::mock_web3_client::Web3; @@ -210,12 +209,11 @@ pub mod oracle_process { use anoma::types::ethereum_events::TransferToEthereum; use tokio::sync::oneshot::{channel, Receiver}; - use super::super::test_tools::mock_web3_client::{TestCmd, Web3}; use super::*; use crate::node::ledger::ethereum_node::events::{ ChangedContract, RawTransfersToEthereum, }; - use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::MockEventType; + use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::{MockEventType, TestCmd, Web3}; /// The data returned from setting up a test struct TestPackage { @@ -323,7 +321,7 @@ pub mod oracle_process { name: "Test".to_string(), address: EthAddress([0; 20]), } - .encode(); + .encode(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, From 211a3771f206a025da9d678157a8f8f5ed3c8dec Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 5 Jul 2022 16:54:58 +0200 Subject: [PATCH 0061/1995] [fix]: Fixed visibility tiny --- apps/src/lib/node/ledger/ethereum_node/events.rs | 2 +- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 15d63b90b4..db97db7b1b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -376,7 +376,7 @@ pub mod eth_events { /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's /// ABI serialization scheme. #[cfg(test)] - pub(super) fn encode(self) -> Vec { + pub fn encode(self) -> Vec { let RawTransfersToEthereum { transfers, nonce, diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 8e16800603..55a67fa6da 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -432,7 +432,7 @@ pub mod oracle_process { nonce: 1.into(), confirmations: 75, } - .encode(); + .encode(); // send in the events to the logs admin_channel From 5d4fdec9135e19fe3d7001296c7641d45b58dbb7 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 5 Jul 2022 17:06:41 +0200 Subject: [PATCH 0062/1995] [fix]: Tiny --- shared/src/types/ethereum_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index d77702a4e5..cfdca7f429 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -132,7 +132,7 @@ pub struct TransferToNamada { pub receiver: Address, } -/// An event transferring some kind of value from Ethereum to Anoma +/// An event transferring some kind of value from Anoma to Ethereum #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] From 41561d606adae11e4056ef5e25d7aba6e4a4cb44 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 6 Jul 2022 14:46:57 +0200 Subject: [PATCH 0063/1995] [fix]: Updated ABCI deps to prevent getrandoms js feature from corrupting wasm builds. Fixed the app hash mis match that prevented rebooting ledger in ABCI++ mode --- Cargo.lock | 147 ++---- apps/Cargo.toml | 10 +- apps/src/lib/node/ledger/shell/mod.rs | 9 +- shared/Cargo.toml | 8 +- tests/Cargo.toml | 8 +- tests/src/e2e/ledger_tests.rs | 2 +- wasm/checksums.json | 35 +- wasm/tx_template/Cargo.toml | 4 +- wasm/vp_template/Cargo.lock | 626 ++++++++++++++------------ wasm/vp_template/Cargo.toml | 4 +- wasm/wasm_source/Cargo.lock | 626 ++++++++++++++------------ wasm/wasm_source/Cargo.toml | 10 +- 12 files changed, 751 insertions(+), 738 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e155eb83e..04c9bd4d8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,9 +197,9 @@ 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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", "itertools 0.10.3", @@ -219,9 +219,9 @@ 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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -305,13 +305,13 @@ 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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -321,7 +321,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?branch=murisi/jsfix)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tracing 0.1.35", "tracing-log", @@ -386,10 +386,8 @@ 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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "test-log", "toml", "tracing 0.1.35", @@ -2705,10 +2703,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -3120,12 +3116,12 @@ 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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" 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)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=murisi/jsfix)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -3136,10 +3132,10 @@ dependencies = [ "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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.11", "tracing 0.1.35", ] @@ -3174,13 +3170,13 @@ 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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.138", - "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?branch=murisi/jsfix)", "tonic", ] @@ -4828,9 +4824,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8b1a9b2518dc799a2271eff1688707eb315f0d4697aa6b0871369ca4c4da55" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -5868,9 +5864,9 @@ dependencies = [ [[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", @@ -5888,9 +5884,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" @@ -6919,35 +6915,7 @@ dependencies = [ [[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.138", - "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.11", - "zeroize", -] - -[[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6967,7 +6935,7 @@ dependencies = [ "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)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.11", "zeroize", ] @@ -7003,12 +6971,12 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde 1.0.138", "serde_json", - "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?branch=murisi/jsfix)", "toml", "url 2.2.2", ] @@ -7029,13 +6997,13 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", "serde 1.0.138", - "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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.11", ] @@ -7055,24 +7023,7 @@ dependencies = [ [[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.138", - "serde_bytes", - "subtle-encoding", - "time 0.3.11", -] - -[[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes 1.1.0", "flex-error", @@ -7106,7 +7057,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "async-tungstenite", @@ -7124,9 +7075,9 @@ dependencies = [ "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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "thiserror", "time 0.3.11", "tokio", @@ -7172,7 +7123,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7180,7 +7131,7 @@ dependencies = [ "serde_json", "simple-error", "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?branch=murisi/jsfix)", "time 0.3.11", ] @@ -7639,13 +7590,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing#73e43bf79fb21b4969cc09f79a0a40ce4cc7bb52" +source = "git+https://github.com/heliaxdev/tower-abci?branch=murisi/jsfix#09de960c0e67491a65094608d0679b2ecc29e7c9" dependencies = [ "bytes 1.1.0", "futures 0.3.21", "pin-project 1.0.11", "prost 0.9.0", - "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?branch=murisi/jsfix)", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7946,9 +7897,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" @@ -8265,9 +8216,9 @@ checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" [[package]] name = "wasm-encoder" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" dependencies = [ "leb128", ] @@ -8525,9 +8476,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "42.0.0" +version = "43.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" dependencies = [ "leb128", "memchr", @@ -8537,9 +8488,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" dependencies = [ "wast", ] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 76d4242244..73b399baa4 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -129,12 +129,12 @@ 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-config-abci = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", 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-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", 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-rpc-abci = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true, features = ["http-client", "websocket-client"]} +tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -143,7 +143,7 @@ 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-old = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", branch = "murisi/jsfix", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4931fe3804..10c9167323 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -564,6 +564,7 @@ where /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { + let mut response = response::Commit::default(); // commit changes from the write-log to storage self.write_log .commit_block(&mut self.storage) @@ -582,7 +583,8 @@ where root, self.storage.last_height, ); - response::Commit::default() + response.data = root.0; + response } /// Validate a transaction request. On success, the transaction will @@ -595,9 +597,10 @@ where ) -> response::CheckTx { let mut response = response::CheckTx::default(); match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => {} - Err(_) => { + Ok(_) => response.log = String::from("Mempool validation passed"), + Err(msg) => { response.code = 1; + response.log = msg.to_string(); } } response diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 70751a2b48..0187c3cf21 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -83,9 +83,9 @@ 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-abci = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", branch = "murisi/jsfix", 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-proto-abci = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", branch = "murisi/jsfix", default-features = false, optional = true} ics23 = "0.6.7" itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} @@ -108,8 +108,8 @@ 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-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} +tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} 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 c8ef113c5b..27db73d68a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -34,10 +34,10 @@ 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} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} +tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} +tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", 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/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 33643bf0f0..b569ade9f0 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -470,7 +470,7 @@ fn invalid_transactions() -> Result<()> { &validator_one_rpc, ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + let mut client = run!(test, Bin::Client, tx_args, Some(60))?; if !cfg!(feature = "ABCI") { client.exp_string("Transaction accepted")?; } diff --git a/wasm/checksums.json b/wasm/checksums.json index ca6becd85c..9d7d4c8cc3 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.15aa5ee00346d5136effa8dc0594578bdefc5e1a05d6430e27e243b4fe003fb1.wasm", - "tx_ethereum_headers.wasm": "tx_ethereum_headers.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.543ce26d999944529d4846aea8d0128b5ab3147b8fa73827efe5ac617b8cdaa5.wasm", - "tx_ibc.wasm": "tx_ibc.3edeb5ac932afe56e3d4b420208979b7678a7f9e62e969a9fcea2aac5dfc723c.wasm", - "tx_init_account.wasm": "tx_init_account.3f5e29e2c4b3a865169df7f6673fbe5052c40f74f53ea386a29b33dc93028921.wasm", - "tx_init_nft.wasm": "tx_init_nft.803597a3f57a385c2b11e9251954422384b68be49114fa9a8fbaa5997648b522.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bb1dc7f83c6a2fd2f1dee65394efaac300bbc7d9e70af670a08fbe47d39c07d5.wasm", - "tx_init_validator.wasm": "tx_init_validator.9d1faa0dbd58fe626ae01d16723d5b8a556516ab61b00e1ea2247f93b7561bcf.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.0e65ba9430f82f689c8945a371595973a362312f48154ba27c788c48e10a4cbb.wasm", - "tx_transfer.wasm": "tx_transfer.27fd4bade4dad12afb09817113cd5e9bebc1bbdd7bfcb4ad483b50d87613be3f.wasm", - "tx_unbond.wasm": "tx_unbond.69c3664872c0472f3935362b51c152f73c0975f477f5a6c8f994069312808c37.wasm", - "tx_update_vp.wasm": "tx_update_vp.dedc48cec6f7296346f049530a61b224f7ec7a6f61a072df8985d115a5e3fa6f.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.95a1261876b1fd0f8e3bc1c8a3ee55c32ff8827f270ab3f18d35e639b186a069.wasm", - "tx_withdraw.wasm": "tx_withdraw.55dd4e5999a428fe8b697a4b4b87d3edd4dd6d6bf60db9a1bbb747f664abae11.wasm", - "vp_nft.wasm": "vp_nft.4a86dc837bae4b7acc8ef1a834bf4029c145d3e67a027b2ec47a5e1df190b417.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.2cf11c784acf48b3bd164615780e38c0becc2bfeff898b4056b3b12291a57fc5.wasm", - "vp_token.wasm": "vp_token.d8a1dff7b277da9528c343e117744ab4630e8720bbb3fcabbbbe6775ec54dbd9.wasm", - "vp_user.wasm": "vp_user.548b1c9548511ab6e070cac1f5aaaf31b808bd9baccea9a9300fabe711cec670.wasm" + "tx_bond.wasm": "tx_bond.714ebe7b14b5bcb5d27aabe1b5c7bdddecb1fd42f2ab4e037c72f94f8e792d9d.wasm", + "tx_from_intent.wasm": "tx_from_intent.d2ee629cfed4186074575cd64e5f9369bcea4ebe1de0eefcaf86f5953407c183.wasm", + "tx_ibc.wasm": "tx_ibc.bd4cf81c46ec4f07b024e67523451e3b5f29d27219dada05de06d29094898eac.wasm", + "tx_init_account.wasm": "tx_init_account.932fbd46289348272f1c26e1650ac53cc911813b1b07b659417b0033dc7a50ff.wasm", + "tx_init_nft.wasm": "tx_init_nft.540875e340cf9341f757fa9452acbd5072bf6bf22be603fae9088e0cff98e6f0.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.18ac38795001c5bee04d5795125740970ae8446c605df0efb1599e16a4d00905.wasm", + "tx_init_validator.wasm": "tx_init_validator.8b40e2ad447b77e31a46fb87071c7d027d8edf59b6bdb882d64d1ee3a5d9809e.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.93fac847c7ef3762b276d924dc0f000a8b2abbfa652fd17ddede84e5532b9f42.wasm", + "tx_transfer.wasm": "tx_transfer.ca65403409010a85b15a789968366e1f1038e4dcd9c49ccc15938acfdd6b92a0.wasm", + "tx_unbond.wasm": "tx_unbond.721a49ea61a9d2ae94846ef48e93874706a84332c9d46ce1030684ef4c5884e0.wasm", + "tx_update_vp.wasm": "tx_update_vp.11e9482db0b26d336b518ac07501b686ca37267d205755eaa71f27e2e25160e3.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.dad0034b6238635c4f666142843d5691d53a2916878cec5cecb134fe442c1ddc.wasm", + "tx_withdraw.wasm": "tx_withdraw.94d5eb892c5d46096de258e79c53452a6e31a77eff7918381b923e0d52e97cbb.wasm", + "vp_nft.wasm": "vp_nft.73dda71f88c7075ae0a1b3eae3f4db469f3e31d47f5501c3cee2cea697e46ad1.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3cb4b93f0e69c8da877fd1f70f6fdba5bc2e9c1be2982ff0b1a627b323b18c1d.wasm", + "vp_token.wasm": "vp_token.f4ae3d7a07d4624ae778574f567729908dc4ec60a8d394b144430d3608f22d63.wasm", + "vp_user.wasm": "vp_user.f11e68ec323fa2f5cc634cb96968dbfa895eb5abbc9f8454ddfdf0ffd3af651f.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 96103ce897..28c691957d 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -19,8 +19,8 @@ getrandom = { version = "0.2", features = ["custom"] } 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"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} # 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 b43a035b74..1a49d16845 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "ark-bls12-381" @@ -257,9 +257,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", @@ -267,9 +267,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", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -295,16 +295,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object", "rustc-demangle", ] @@ -355,9 +355,9 @@ dependencies = [ [[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", @@ -379,9 +379,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", ] @@ -398,7 +398,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]] @@ -435,9 +435,9 @@ 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 = "byte-slice-cast" @@ -447,9 +447,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" dependencies = [ "proc-macro2", "quote", @@ -532,9 +532,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[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", ] @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -631,25 +631,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" 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.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] @@ -660,9 +661,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" dependencies = [ "generic-array", "typenum", @@ -670,9 +671,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", @@ -696,9 +697,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", @@ -706,23 +707,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", @@ -766,16 +766,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", @@ -788,9 +788,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", @@ -799,18 +799,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", @@ -835,9 +835,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" @@ -861,18 +861,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", @@ -976,9 +976,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" @@ -1085,15 +1085,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", - "js-sys", "libc", - "wasi", - "wasm-bindgen", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -1115,18 +1113,18 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[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", @@ -1135,9 +1133,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", @@ -1148,7 +1146,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "tracing", ] @@ -1161,6 +1159,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -1187,9 +1194,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", @@ -1198,9 +1205,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", @@ -1209,9 +1216,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" @@ -1221,9 +1228,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ "bytes", "futures-channel", @@ -1258,7 +1265,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1278,14 +1285,14 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time 0.3.11", "tracing", ] [[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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1374,12 +1381,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.1", "serde", ] @@ -1403,24 +1410,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" 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" @@ -1436,15 +1443,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[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", @@ -1452,9 +1459,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", ] @@ -1506,15 +1513,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.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" dependencies = [ "libc", ] @@ -1536,34 +1543,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]] @@ -1578,15 +1574,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[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" @@ -1611,9 +1598,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", @@ -1633,9 +1620,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", ] @@ -1652,39 +1639,30 @@ 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" -dependencies = [ - "memchr", -] - -[[package]] -name = "object" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", - "hashbrown", + "hashbrown 0.11.2", "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" @@ -1726,9 +1704,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "peg" @@ -1774,9 +1752,9 @@ dependencies = [ [[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", @@ -1784,18 +1762,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", @@ -1804,9 +1782,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" @@ -1878,11 +1856,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2002,9 +1980,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.15" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -2062,9 +2040,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", @@ -2074,22 +2052,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.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] @@ -2107,9 +2084,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", @@ -2127,9 +2104,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" @@ -2174,12 +2151,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.1", "ptr_meta", "rend", "rkyv_derive", @@ -2188,9 +2165,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", @@ -2209,9 +2186,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" +checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" dependencies = [ "arrayvec", "num-traits", @@ -2247,9 +2224,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" [[package]] name = "rusty-fork" @@ -2265,9 +2242,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "safe-proc-macro2" @@ -2357,27 +2334,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -2386,9 +2363,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", @@ -2397,9 +2374,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", @@ -2475,15 +2452,15 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[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" @@ -2524,12 +2501,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[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" @@ -2553,13 +2524,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2582,9 +2553,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[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" @@ -2603,7 +2574,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2624,14 +2595,14 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time 0.3.11", "zeroize", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2644,20 +2615,20 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time 0.3.11", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2668,13 +2639,13 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.11", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2689,7 +2660,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time 0.3.11", "url", "uuid", "walkdir", @@ -2698,7 +2669,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2707,14 +2678,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time 0.3.11", ] [[package]] name = "test-log" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" +checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" dependencies = [ "proc-macro2", "quote", @@ -2723,18 +2694,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2757,15 +2728,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.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ "libc", "num_threads", @@ -2774,9 +2745,9 @@ 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 = "tiny-keccak" @@ -2789,9 +2760,9 @@ dependencies = [ [[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", ] @@ -2804,14 +2775,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes", "libc", "memchr", "mio", + "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2830,9 +2802,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", @@ -2841,9 +2813,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", @@ -2852,9 +2824,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", @@ -2866,23 +2838,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", ] @@ -2910,7 +2882,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2932,9 +2904,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", @@ -2944,7 +2916,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.0", + "tokio-util 0.7.3", "tower-layer", "tower-service", "tracing", @@ -2958,15 +2930,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.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", @@ -2977,9 +2949,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", @@ -2988,12 +2960,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -3008,12 +2979,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" dependencies = [ - "lazy_static", "matchers", + "once_cell", "regex", "sharded-slab", "thread_local", @@ -3035,9 +3006,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" @@ -3053,15 +3024,21 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[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", ] @@ -3080,9 +3057,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" @@ -3102,12 +3079,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" @@ -3161,11 +3132,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.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3173,9 +3150,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -3188,9 +3165,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3198,9 +3175,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -3211,9 +3188,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-encoder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3358,7 +3344,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object", "rkyv", "serde", "tempfile", @@ -3397,7 +3383,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.3", + "object", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3453,20 +3439,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "43.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" dependencies = [ "wast", ] @@ -3485,9 +3472,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", @@ -3525,6 +3512,49 @@ 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 = "wyz" version = "0.5.0" @@ -3536,9 +3566,9 @@ dependencies = [ [[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.toml b/wasm/vp_template/Cargo.toml index 20ac084350..bcd639df1b 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -19,8 +19,8 @@ getrandom = { version = "0.2", features = ["custom"] } 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"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} # 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 3e11f94eb7..934c40b4f2 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "ark-bls12-381" @@ -283,9 +283,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", @@ -293,9 +293,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", @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -321,16 +321,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object", "rustc-demangle", ] @@ -381,9 +381,9 @@ dependencies = [ [[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", @@ -405,9 +405,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", ] @@ -424,7 +424,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]] @@ -461,9 +461,9 @@ 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 = "byte-slice-cast" @@ -473,9 +473,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -483,9 +483,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" dependencies = [ "proc-macro2", "quote", @@ -558,9 +558,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[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", ] @@ -636,9 +636,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -657,25 +657,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" 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.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] @@ -686,9 +687,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" dependencies = [ "generic-array", "typenum", @@ -696,9 +697,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", @@ -722,9 +723,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", @@ -732,23 +733,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", @@ -792,16 +792,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", @@ -814,9 +814,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", @@ -825,18 +825,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", @@ -861,9 +861,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" @@ -887,18 +887,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", @@ -1002,9 +1002,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" @@ -1111,15 +1111,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", - "js-sys", "libc", - "wasi", - "wasm-bindgen", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -1141,18 +1139,18 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[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", @@ -1161,9 +1159,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", @@ -1174,7 +1172,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "tracing", ] @@ -1187,6 +1185,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -1213,9 +1220,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", @@ -1224,9 +1231,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", @@ -1235,9 +1242,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" @@ -1247,9 +1254,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ "bytes", "futures-channel", @@ -1284,7 +1291,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1304,14 +1311,14 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time 0.3.11", "tracing", ] [[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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1400,12 +1407,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.1", "serde", ] @@ -1429,24 +1436,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" 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" @@ -1462,15 +1469,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[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", @@ -1478,9 +1485,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", ] @@ -1532,15 +1539,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.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" dependencies = [ "libc", ] @@ -1562,34 +1569,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]] @@ -1604,15 +1600,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[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" @@ -1637,9 +1624,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", @@ -1659,9 +1646,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", ] @@ -1678,39 +1665,30 @@ 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" -dependencies = [ - "memchr", -] - -[[package]] -name = "object" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", - "hashbrown", + "hashbrown 0.11.2", "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" @@ -1752,9 +1730,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "peg" @@ -1800,9 +1778,9 @@ dependencies = [ [[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", @@ -1810,18 +1788,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", @@ -1830,9 +1808,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" @@ -1904,11 +1882,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2028,9 +2006,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.15" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -2088,9 +2066,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", @@ -2100,22 +2078,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.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ "bitflags", ] @@ -2133,9 +2110,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", @@ -2153,9 +2130,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" @@ -2200,12 +2177,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.1", "ptr_meta", "rend", "rkyv_derive", @@ -2214,9 +2191,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", @@ -2235,9 +2212,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" +checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" dependencies = [ "arrayvec", "num-traits", @@ -2273,9 +2250,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" [[package]] name = "rusty-fork" @@ -2291,9 +2268,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "safe-proc-macro2" @@ -2383,27 +2360,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -2412,9 +2389,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", @@ -2423,9 +2400,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", @@ -2501,15 +2478,15 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[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" @@ -2550,12 +2527,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[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" @@ -2579,13 +2550,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2608,9 +2579,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[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" @@ -2629,7 +2600,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2650,14 +2621,14 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time 0.3.11", "zeroize", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2670,20 +2641,20 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time 0.3.11", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2694,13 +2665,13 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.11", ] [[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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2715,7 +2686,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time 0.3.11", "url", "uuid", "walkdir", @@ -2724,7 +2695,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2733,14 +2704,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time 0.3.11", ] [[package]] name = "test-log" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" +checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" dependencies = [ "proc-macro2", "quote", @@ -2749,18 +2720,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2783,15 +2754,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.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" dependencies = [ "libc", "num_threads", @@ -2800,9 +2771,9 @@ 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 = "tiny-keccak" @@ -2815,9 +2786,9 @@ dependencies = [ [[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", ] @@ -2830,14 +2801,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ "bytes", "libc", "memchr", "mio", + "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2856,9 +2828,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", @@ -2867,9 +2839,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", @@ -2878,9 +2850,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", @@ -2892,23 +2864,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", ] @@ -2936,7 +2908,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2958,9 +2930,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", @@ -2970,7 +2942,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.0", + "tokio-util 0.7.3", "tower-layer", "tower-service", "tracing", @@ -2984,15 +2956,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.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", @@ -3003,9 +2975,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", @@ -3014,12 +2986,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -3034,12 +3005,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" dependencies = [ - "lazy_static", "matchers", + "once_cell", "regex", "sharded-slab", "thread_local", @@ -3061,9 +3032,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" @@ -3079,15 +3050,21 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[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", ] @@ -3106,9 +3083,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" @@ -3128,12 +3105,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" @@ -3176,11 +3147,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.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3188,9 +3165,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" dependencies = [ "bumpalo", "lazy_static", @@ -3203,9 +3180,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3213,9 +3190,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" dependencies = [ "proc-macro2", "quote", @@ -3226,9 +3203,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-encoder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3373,7 +3359,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object", "rkyv", "serde", "tempfile", @@ -3412,7 +3398,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.3", + "object", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3468,20 +3454,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "43.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" dependencies = [ "wast", ] @@ -3500,9 +3487,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", @@ -3540,6 +3527,49 @@ 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 = "wyz" version = "0.5.0" @@ -3551,9 +3581,9 @@ dependencies = [ [[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.toml b/wasm/wasm_source/Cargo.toml index 1cd672951e..6e19c8b23d 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -50,11 +50,11 @@ 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"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix"} # 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"} From 62518ea23fa3cf0e8c84ae9be0c216edd4d7c407 Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Wed, 6 Jul 2022 14:09:11 +0000 Subject: [PATCH 0064/1995] [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 9d7d4c8cc3..ff1eee4288 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.714ebe7b14b5bcb5d27aabe1b5c7bdddecb1fd42f2ab4e037c72f94f8e792d9d.wasm", - "tx_from_intent.wasm": "tx_from_intent.d2ee629cfed4186074575cd64e5f9369bcea4ebe1de0eefcaf86f5953407c183.wasm", - "tx_ibc.wasm": "tx_ibc.bd4cf81c46ec4f07b024e67523451e3b5f29d27219dada05de06d29094898eac.wasm", - "tx_init_account.wasm": "tx_init_account.932fbd46289348272f1c26e1650ac53cc911813b1b07b659417b0033dc7a50ff.wasm", - "tx_init_nft.wasm": "tx_init_nft.540875e340cf9341f757fa9452acbd5072bf6bf22be603fae9088e0cff98e6f0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.18ac38795001c5bee04d5795125740970ae8446c605df0efb1599e16a4d00905.wasm", - "tx_init_validator.wasm": "tx_init_validator.8b40e2ad447b77e31a46fb87071c7d027d8edf59b6bdb882d64d1ee3a5d9809e.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.93fac847c7ef3762b276d924dc0f000a8b2abbfa652fd17ddede84e5532b9f42.wasm", - "tx_transfer.wasm": "tx_transfer.ca65403409010a85b15a789968366e1f1038e4dcd9c49ccc15938acfdd6b92a0.wasm", - "tx_unbond.wasm": "tx_unbond.721a49ea61a9d2ae94846ef48e93874706a84332c9d46ce1030684ef4c5884e0.wasm", - "tx_update_vp.wasm": "tx_update_vp.11e9482db0b26d336b518ac07501b686ca37267d205755eaa71f27e2e25160e3.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.dad0034b6238635c4f666142843d5691d53a2916878cec5cecb134fe442c1ddc.wasm", - "tx_withdraw.wasm": "tx_withdraw.94d5eb892c5d46096de258e79c53452a6e31a77eff7918381b923e0d52e97cbb.wasm", - "vp_nft.wasm": "vp_nft.73dda71f88c7075ae0a1b3eae3f4db469f3e31d47f5501c3cee2cea697e46ad1.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3cb4b93f0e69c8da877fd1f70f6fdba5bc2e9c1be2982ff0b1a627b323b18c1d.wasm", - "vp_token.wasm": "vp_token.f4ae3d7a07d4624ae778574f567729908dc4ec60a8d394b144430d3608f22d63.wasm", - "vp_user.wasm": "vp_user.f11e68ec323fa2f5cc634cb96968dbfa895eb5abbc9f8454ddfdf0ffd3af651f.wasm" + "tx_bond.wasm": "tx_bond.18125703c49c23e6c4467716d75833ae52c340c6f34d185fe914b52946afa622.wasm", + "tx_from_intent.wasm": "tx_from_intent.844e15f208d9e05af2b59ad89d858b146b83273688735d1a42123bf68566b715.wasm", + "tx_ibc.wasm": "tx_ibc.777ef688fd2bd98174141b4bd5012e6ae3f86216ced416bef5103f71bb18a8c3.wasm", + "tx_init_account.wasm": "tx_init_account.9118b5a7af9d9a5a58605740acfdb4302820ac80582bfbc58d6b2bb2e4b11fc0.wasm", + "tx_init_nft.wasm": "tx_init_nft.dd142462f29a91dc6ce3a1fcbbfb032bfd7a7f7365af89226728b2c55f30a35c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6e9eba92c89909690d1b827d7e9b127c64124c5c704b1c0883ca7711e3d930c7.wasm", + "tx_init_validator.wasm": "tx_init_validator.339b002d78aeac05b787148e295880bfc8969910e4664187eb68746bca643653.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.82335a3219da2febd79258a79bc61e5df7f03377f0e98eac67a3ab6820063caf.wasm", + "tx_transfer.wasm": "tx_transfer.01a1b727f2f2ea1156af1bc9887a8c71b906a7042dde7670ed4939e44a3aa951.wasm", + "tx_unbond.wasm": "tx_unbond.2bd84dcbef9d820ae201268ad9f2a00484a572a9eda6240f6f2e924f365b09f2.wasm", + "tx_update_vp.wasm": "tx_update_vp.b0596e62b4984b52462040a1e3176980d9f02dfd67a3489605822579b380eefa.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.a82acb45c8971b2473265dfa364501cfa2bf62f8dda2ad8ed4f60533aaee1d12.wasm", + "tx_withdraw.wasm": "tx_withdraw.27aec118278975e1c130ef5641745c6cefc01f754b3146a3cd25e3d2067a4740.wasm", + "vp_nft.wasm": "vp_nft.6526b784276fe3283e0655996644a14d8484a187555023d96bda2873f749e7d1.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e5fe21b07946e4560dea8b127f44d3479ce93fd92f8108c348446f2bdd76449.wasm", + "vp_token.wasm": "vp_token.be4cf76687007fdb4351a8e8468dc0b0f13e9ba824d07fe145d49aefaf7d77d4.wasm", + "vp_user.wasm": "vp_user.bb39dde6552d165c1e1786f35ac813e607a12a32a27320b040832e67a4841b81.wasm" } \ No newline at end of file From 1a36687059bf7e97dbf990c475cd881db4459ec4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 24 Jun 2022 19:07:19 +0100 Subject: [PATCH 0065/1995] Start accepting EthereumEvents protocol transaction --- apps/src/lib/node/ledger/protocol/mod.rs | 16 +++++++++++++-- .../lib/node/ledger/shell/finalize_block.rs | 20 ++++++++++++------- .../lib/node/ledger/shell/prepare_proposal.rs | 1 - .../lib/node/ledger/shell/process_proposal.rs | 16 ++++++++++----- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 28e27a454c..237eb450f1 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -15,6 +15,7 @@ use anoma::ledger::treasury::TreasuryVp; use anoma::proto::{self, Tx}; use anoma::types::address::{Address, InternalAddress}; use anoma::types::storage; +use anoma::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use anoma::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; use anoma::vm::wasm::{TxCache, VpCache}; use anoma::vm::{self, wasm, WasmCacheAccess}; @@ -61,8 +62,6 @@ pub type Result = std::result::Result; /// Apply a given transaction /// -/// The only Tx Types that should be input here are `Decrypted` and `Wrapper` -/// /// If the given tx is a successfully decrypted payload apply the necessary /// vps. Otherwise, we include the tx on chain with the gas charge added /// but no further validations. @@ -120,6 +119,19 @@ where ibc_event, }) } + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::EthereumEvents(_), + .. + }) => { + tracing::debug!("Ethereum events received"); + let gas_used = block_gas_meter + .finalize_transaction() + .map_err(Error::GasError)?; + Ok(TxResult { + gas_used, + ..Default::default() + }) + } _ => { let gas_used = block_gas_meter .finalize_transaction() diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 46f025a74c..aab8d54bb1 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -10,6 +10,7 @@ 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::transaction::protocol::ProtocolTxType; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::Misbehavior as Evidence; #[cfg(not(feature = "ABCI"))] @@ -327,13 +328,18 @@ where ); continue; } - TxType::Protocol(_) => { - tracing::error!( - "Internal logic error: FinalizeBlock received a \ - TxType::Protocol transaction" - ); - continue; - } + TxType::Protocol(protocol_tx) => match protocol_tx.tx { + ProtocolTxType::EthereumEvents(_) => { + Event::new_tx_event(&tx_type, height.0) + } + _ => { + tracing::error!( + "Internal logic error: FinalizeBlock received an \ + unsupported TxType::Protocol transaction" + ); + continue; + } + }, }; match protocol::apply_tx( diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a1a559db8c..11daebcbe5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -33,7 +33,6 @@ mod prepare_block { // 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; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index b79279c248..1902301ba4 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,5 +1,6 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use anoma::types::transaction::protocol::ProtocolTxType; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -98,11 +99,16 @@ where are not supported" .into(), }, - TxType::Protocol(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Protocol transactions are a fun new feature that \ - is coming soon to a blockchain near you. Patience." - .into(), + TxType::Protocol(protocol_tx) => match protocol_tx.tx { + ProtocolTxType::EthereumEvents(_) => TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), + }, + _ => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Unsupported protocol transaction type".into(), + }, }, TxType::Decrypted(tx) => match self.next_wrapper() { Some(wrapper) => { From ed7d72bd89b58e69c681fc95854fd20bfce8f45f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 6 Jul 2022 17:56:42 +0100 Subject: [PATCH 0066/1995] Run make fmt --- .../lib/node/ledger/ethereum_node/events.rs | 190 ++++++++++-------- .../lib/node/ledger/ethereum_node/oracle.rs | 55 ++--- 2 files changed, 134 insertions(+), 111 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index db97db7b1b..aa87f82b77 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -46,8 +46,8 @@ pub mod eth_events { use anoma::types::address::Address; use anoma::types::ethereum_events::{ - EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, TransferToEthereum, - TransferToNamada, Uint, + EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, + TransferToEthereum, TransferToNamada, Uint, }; use anoma::types::token::Amount; use ethabi::decode; @@ -70,7 +70,7 @@ pub mod eth_events { /// An event waiting for a certain number of confirmations /// before being sent to the ledger - pub( in super::super) struct PendingEvent { + pub(in super::super) struct PendingEvent { /// number of confirmations to consider this event finalized confirmations: Uint256, /// the block height from which this event originated @@ -135,13 +135,15 @@ pub mod eth_events { }) } signatures::TRANSFER_TO_ETHEREUM_SIG => { - RawTransfersToEthereum::decode(data).map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), - block_height, - event: EthereumEvent::TransfersToEthereum { - nonce: txs.nonce, - transfers: txs.transfers, - }, + RawTransfersToEthereum::decode(data).map(|txs| { + PendingEvent { + confirmations: txs.confirmations.into(), + block_height, + event: EthereumEvent::TransfersToEthereum { + nonce: txs.nonce, + transfers: txs.transfers, + }, + } }) } signatures::VALIDATOR_SET_UPDATE_SIG => { @@ -151,7 +153,8 @@ pub mod eth_events { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: + super::super::oracle::MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -161,28 +164,35 @@ pub mod eth_events { }, ) } - signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data).map( - |ChangedContract { name, address }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::NewContract { name, address }, - }, - ), - signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode(data) + signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: super::super::oracle::MIN_CONFIRMATIONS + .into(), block_height, - event: EthereumEvent::UpgradedContract { name, address }, + event: EthereumEvent::NewContract { name, address }, }), + signatures::UPGRADED_CONTRACT_SIG => ChangedContract::decode( + data, + ) + .map(|ChangedContract { name, address }| PendingEvent { + confirmations: super::super::oracle::MIN_CONFIRMATIONS + .into(), + block_height, + event: EthereumEvent::UpgradedContract { name, address }, + }), signatures::UPDATE_BRIDGE_WHITELIST_SIG => { UpdateBridgeWhitelist::decode(data).map( - |UpdateBridgeWhitelist { nonce, whitelist }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS.into(), - block_height, - event: EthereumEvent::UpdateBridgeWhitelist { - nonce, - whitelist, - }, + |UpdateBridgeWhitelist { nonce, whitelist }| { + PendingEvent { + confirmations: + super::super::oracle::MIN_CONFIRMATIONS + .into(), + block_height, + event: EthereumEvent::UpdateBridgeWhitelist { + nonce, + whitelist, + }, + } }, ) } @@ -227,16 +237,17 @@ pub mod eth_events { /// Parse ABI serialized data from an Ethereum event into /// an instance of [`RawTransfersToNamada`] fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( - &[ - ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::String)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ParamType::Uint(32), - ], - data, - ) + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = + decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::String)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, + ) .map_err(|err| Error::Decode(format!("{:?}", err)))? .try_into() .map_err(|_| { @@ -252,13 +263,13 @@ pub mod eth_events { if assets.len() != amounts.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - transfer amounts" + transfer amounts" .into(), )) } else if receivers.len() != assets.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - target addresses" + target addresses" .into(), )) } else { @@ -322,21 +333,23 @@ pub mod eth_events { /// Parse ABI serialized data from an Ethereum event into /// an instance of [`RawTransfersToEthereum`] fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = decode( - &[ - ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ParamType::Uint(32), - ], - data, - ) + let [nonce, assets, receivers, amounts, confs]: [Token; 5] = + decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Address)), + ParamType::Array(Box::new(ParamType::Uint(256))), + ParamType::Uint(32), + ], + data, + ) .map_err(|err| Error::Decode(format!("{:?}", err)))? .try_into() .map_err(|_| { Error::Decode( - "TransferToERC signature should contain five types".to_string(), + "TransferToERC signature should contain five types" + .to_string(), ) })?; @@ -346,13 +359,13 @@ pub mod eth_events { if assets.len() != amounts.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - transfer amounts" + transfer amounts" .into(), )) } else if receivers.len() != assets.len() { Err(Error::Decode( "Number of source addresses is different from number of \ - target addresses" + target addresses" .into(), )) } else { @@ -502,21 +515,22 @@ pub mod eth_events { ], data, ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "UpdatedBridgeWhitelist signature should contain three types" - .into(), - ) - })?; + .map_err(|err| Error::Decode(format!("{:?}", err)))? + .try_into() + .map_err(|_| { + Error::Decode( + "UpdatedBridgeWhitelist signature should contain three \ + types" + .into(), + ) + })?; let tokens = tokens.parse_eth_address_array()?; let caps = caps.parse_amount_array()?; if tokens.len() != caps.len() { Err(Error::Decode( - "UpdatedBridgeWhitelist received different number of token \ - address and token caps" + "UpdatedBridgeWhitelist received different number of \ + token address and token caps" .into(), )) } else { @@ -755,72 +769,72 @@ pub mod eth_events { &[ParamType::Address], encode(&[Token::Address(erc.0.into())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_eth_address().expect("Test failed"), erc); let [token]: [Token; 1] = decode( &[ParamType::String], encode(&[Token::String(address.to_string())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_address().expect("Test failed"), address); let [token]: [Token; 1] = decode( &[ParamType::Uint(64)], encode(&[Token::Uint(u64::from(amount).into())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_amount().expect("Test failed"), amount); let [token]: [Token; 1] = decode( &[ParamType::Uint(32)], encode(&[Token::Uint(confs.into())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_u32().expect("Test failed"), confs); let [token]: [Token; 1] = decode( &[ParamType::Uint(256)], encode(&[Token::Uint(uint.clone().into())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_uint256().expect("Test failed"), uint); let [token]: [Token; 1] = decode( &[ParamType::Bool], encode(&[Token::Bool(boolean)]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_bool().expect("Test failed"), boolean); let [token]: [Token; 1] = decode( &[ParamType::String], encode(&[Token::String(string.clone())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_string().expect("Test failed"), string); let [token]: [Token; 1] = decode( &[ParamType::FixedBytes(32)], encode(&[Token::FixedBytes(keccak.0.to_vec())]).as_slice(), ) - .expect("Test failed") - .try_into() - .expect("Test failed"); + .expect("Test failed") + .try_into() + .expect("Test failed"); assert_eq!(token.parse_keccak().expect("Test failed"), keccak); } @@ -903,4 +917,4 @@ pub mod eth_events { } #[cfg(feature = "eth-fullnode")] -pub use eth_events::*; \ No newline at end of file +pub use eth_events::*; diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 55a67fa6da..fd4c6c8569 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -115,9 +115,9 @@ pub mod oracle_process { } if !oracle.connected() { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" - ); + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); return; } }; @@ -125,19 +125,23 @@ pub mod oracle_process { if Uint256::from(MIN_CONFIRMATIONS) > latest_block { if !oracle.connected() { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" - ); + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); return; } continue; } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); - // check for events with at least `[MIN_CONFIRMATIONS]` confirmations. + let block_to_check = + latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` + // confirmations. for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), + signatures::SigType::Governance => { + GOVERNANCE_CONTRACT.0.into() + } }; // fetch the events for matching the given signature let mut events = loop { @@ -157,7 +161,7 @@ pub mod oracle_process { block_to_check.clone(), log.data.0.as_slice(), ) - .ok() + .ok() }) .collect::>() }) @@ -166,18 +170,18 @@ pub mod oracle_process { } if !oracle.connected() { tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" - ); + "Ethereum oracle could not send events to the \ + ledger; the receiver has hung up. Shutting down" + ); return; } }; pending.append(&mut events); if !oracle.send(process_queue(&latest_block, &mut pending)) { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" - ); + "Ethereum oracle could not send events to the ledger; \ + the receiver has hung up. Shutting down" + ); return; } } @@ -191,7 +195,8 @@ pub mod oracle_process { latest_block: &Uint256, pending: &mut Vec, ) -> Vec { - let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); + let mut pending_tmp: Vec = + Vec::with_capacity(pending.len()); std::mem::swap(&mut pending_tmp, pending); let mut confirmed = vec![]; for item in pending_tmp.into_iter() { @@ -226,7 +231,8 @@ pub mod oracle_process { /// Set up an oracle with a mock web3 client that we can contr fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); - let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (eth_sender, eth_receiver) = + tokio::sync::mpsc::unbounded_channel(); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -361,7 +367,7 @@ pub mod oracle_process { name: "Test".to_string(), address: EthAddress([0; 20]), } - .encode(); + .encode(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, @@ -420,7 +426,7 @@ pub mod oracle_process { name: "Test".to_string(), address: EthAddress([0; 20]), } - .encode(); + .encode(); // confirmed after 75 blocks let second_event = RawTransfersToEthereum { @@ -450,7 +456,8 @@ pub mod oracle_process { }) .expect("Test failed"); - // increase block height so first event is confirmed but second is not. + // increase block height so first event is confirmed but second is + // not. admin_channel .send(TestCmd::NewHeight(Uint256::from(102u32))) .expect("Test failed"); @@ -476,7 +483,9 @@ pub mod oracle_process { .expect("Test failed"); // check correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event + if let EthereumEvent::TransfersToEthereum { + mut transfers, .. + } = event { assert_eq!(transfers.len(), 1); let transfer = transfers.remove(0); @@ -499,4 +508,4 @@ pub mod oracle_process { } #[cfg(feature = "eth-fullnode")] -pub use oracle_process::*; \ No newline at end of file +pub use oracle_process::*; From e4a71835d6104c2167106479629ede737100c75b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 6 Jul 2022 18:13:28 +0100 Subject: [PATCH 0067/1995] Update all Cargo.lock files --- wasm/tx_template/Cargo.lock | 18 ++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 18 ++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 95fa7fe7a0..b4e82ba83b 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1090,10 +1090,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]] @@ -1258,7 +1256,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1285,7 +1283,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -2603,7 +2601,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2631,7 +2629,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2644,7 +2642,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2657,7 +2655,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2674,7 +2672,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2698,7 +2696,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 3d96dfb9ec..1b7d152c63 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1112,10 +1112,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]] @@ -1289,7 +1287,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1316,7 +1314,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -2635,7 +2633,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2663,7 +2661,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2676,7 +2674,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2689,7 +2687,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2706,7 +2704,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2730,7 +2728,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", From f92b9c89eb6338122f5ae65a3b0f88328f3f1c54 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 11 Jul 2022 09:26:19 +0100 Subject: [PATCH 0068/1995] Use already pulled in num 0.4.0 dependency --- Cargo.lock | 2 +- shared/Cargo.toml | 2 +- shared/src/types/ethereum_events.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04c9bd4d8d..b6cf79077b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,7 +204,7 @@ dependencies = [ "ics23", "itertools 0.10.3", "loupe", - "num-rational 0.4.1", + "num 0.4.0", "parity-wasm", "pretty_assertions", "proptest", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 0187c3cf21..5d1701da26 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -78,7 +78,7 @@ ethabi = "17.0.0" eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} -num-rational = "0.4.1" +num = "0.4.0" 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. diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index cfdca7f429..08a443ca21 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -167,7 +167,7 @@ pub mod vote_extensions { use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; - use num_rational::Ratio; + use num::rational::Ratio; use super::EthereumEvent; use crate::proto::MultiSigned; From b9c08c72d68ae33e9e05a7670e38a5599506dd80 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 11 Jul 2022 09:36:07 +0100 Subject: [PATCH 0069/1995] Simplify FractionalVotingPower TryFrom to From --- shared/src/types/ethereum_events.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 08a443ca21..bba9cd3b1b 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -163,8 +163,6 @@ pub struct TokenWhitelist { /// Contains types necessary for processing Ethereum events /// in vote extensions pub mod vote_extensions { - use std::convert::TryFrom; - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use num::rational::Ratio; @@ -206,18 +204,7 @@ pub mod vote_extensions { &self, writer: &mut W, ) -> std::io::Result<()> { - let (numer, denom): (u64, u64) = - TryFrom::<&FractionalVotingPower>::try_from(self).map_err( - |err| { - std::io::Error::new( - std::io::ErrorKind::InvalidData, - format!( - "Could not serialize {:?} to Borsh: {:?}", - self, err - ), - ) - }, - )?; + let (numer, denom): (u64, u64) = self.into(); (numer, denom).serialize(writer) } } From e1657a64b86b6f9dc0833bfbba306ac2296a607e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 11 Jul 2022 09:50:34 +0100 Subject: [PATCH 0070/1995] Undo Cargo.lock upgrades unrelated to ethbridge --- Cargo.lock | 871 +++++++++++++------------- wasm/tx_template/Cargo.lock | 40 +- wasm/vp_template/Cargo.lock | 648 +++++++++---------- wasm/wasm_source/Cargo.lock | 648 +++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 40 +- 5 files changed, 1171 insertions(+), 1076 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6cf79077b..f55468d70c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ "pin-project-lite 0.2.9", "rand 0.8.5", "sha-1 0.10.0", - "smallvec 1.9.0", + "smallvec 1.8.0", "zstd", ] @@ -161,7 +161,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", "once_cell", "version_check 0.9.4", ] @@ -214,7 +214,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -227,7 +227,7 @@ dependencies = [ "thiserror", "tonic-build", "tracing 0.1.35", - "tracing-subscriber 0.3.14", + "tracing-subscriber 0.3.11", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -295,7 +295,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_regex", @@ -325,7 +325,7 @@ dependencies = [ "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tracing 0.1.35", "tracing-log", - "tracing-subscriber 0.3.14", + "tracing-subscriber 0.3.11", "web30", "websocket", "winapi 0.3.9", @@ -338,7 +338,7 @@ dependencies = [ "anoma", "borsh", "itertools 0.10.3", - "lazy_static", + "lazy_static 1.4.0", "madato", ] @@ -391,7 +391,7 @@ dependencies = [ "test-log", "toml", "tracing 0.1.35", - "tracing-subscriber 0.3.14", + "tracing-subscriber 0.3.11", ] [[package]] @@ -431,9 +431,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "ark-bls12-381" @@ -634,14 +634,14 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.2.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" dependencies = [ "async-channel", "async-executor", "async-io", - "async-lock", + "async-mutex", "blocking", "futures-lite", "num_cpus", @@ -676,6 +676,15 @@ 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" @@ -695,16 +704,16 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", "futures-channel", "futures-core", "futures-io", @@ -713,6 +722,7 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", + "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -818,12 +828,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" -version = "0.2.14" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" dependencies = [ - "hermit-abi", "libc", + "termion", "winapi 0.3.9", ] @@ -870,7 +880,7 @@ dependencies = [ "percent-encoding 2.1.0", "pin-project-lite 0.2.9", "rand 0.8.5", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", @@ -934,7 +944,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -946,7 +956,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static", + "lazy_static 1.4.0", "lazycell", "peeking_take_while", "proc-macro2", @@ -979,9 +989,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -1157,7 +1167,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "memchr", "regex-automata", ] @@ -1264,19 +1274,12 @@ 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" +version = "7.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" +checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" dependencies = [ - "camino", "clap 2.34.0", "log 0.4.17", "shell-escape", @@ -1399,6 +1402,7 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", + "term_size", "textwrap 0.11.0", "unicode-width", "vec_map", @@ -1412,7 +1416,7 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static", + "lazy_static 1.4.0", "os_str_bytes", "strsim 0.10.0", "termcolor", @@ -1427,31 +1431,18 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num-bigint 0.4.3", "num-traits 0.2.15", "num256", "secp256k1", - "serde 1.0.138", + "serde 1.0.137", "serde-rlp", "serde_bytes", "serde_derive", "sha3 0.10.1", ] -[[package]] -name = "clearscreen" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" -dependencies = [ - "nix 0.24.1", - "terminfo", - "thiserror", - "which", - "winapi 0.3.9", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -1489,20 +1480,10 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.28", + "tracing-core 0.1.27", "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" @@ -1528,10 +1509,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "nom 5.1.2", "rust-ini", - "serde 1.0.138", + "serde 1.0.137", "serde-hjson", "serde_json", "toml", @@ -1606,7 +1587,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.9.0", + "smallvec 1.8.0", "target-lexicon", ] @@ -1640,7 +1621,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.9.0", + "smallvec 1.8.0", "target-lexicon", ] @@ -1655,12 +1636,12 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", ] [[package]] @@ -1671,20 +1652,20 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", ] [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", + "lazy_static 1.4.0", "memoffset", - "once_cell", "scopeguard", ] @@ -1696,17 +1677,17 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static", + "lazy_static 1.4.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", - "once_cell", + "lazy_static 1.4.0", ] [[package]] @@ -1717,9 +1698,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array 0.14.5", "typenum", @@ -1984,9 +1965,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "difflib" @@ -2032,16 +2013,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" @@ -2083,7 +2054,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static", + "lazy_static 1.4.0", "proc-macro-error", "proc-macro2", "quote", @@ -2107,7 +2078,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", "signature", ] @@ -2120,7 +2091,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.3", - "serde 1.0.138", + "serde 1.0.137", "sha2 0.9.9", "thiserror", "zeroize", @@ -2136,7 +2107,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -2144,9 +2115,22 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "embed-resource" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version 0.4.0", + "toml", + "vswhom", + "winreg 0.10.1", +] [[package]] name = "encoding_rs" @@ -2210,6 +2194,15 @@ 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 = "error" version = "0.1.9" @@ -2228,7 +2221,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.138", + "serde 1.0.137", "serde_json", ] @@ -2242,7 +2235,7 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "sha3 0.10.1", "thiserror", @@ -2353,7 +2346,7 @@ dependencies = [ "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "serde_json", "subproductdomain", @@ -2370,7 +2363,7 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", ] @@ -2399,14 +2392,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.13", - "windows-sys", + "winapi 0.3.9", ] [[package]] @@ -2429,9 +2422,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" @@ -2698,13 +2691,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] @@ -2757,9 +2750,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" dependencies = [ "aho-corasick", "bstr", @@ -2877,7 +2870,11 @@ 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", ] @@ -3127,7 +3124,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", "serde_json", "sha2 0.10.2", @@ -3136,7 +3133,7 @@ dependencies = [ "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", - "time 0.3.11", + "time 0.3.9", "tracing 0.1.35", ] @@ -3154,7 +3151,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", "serde_json", "sha2 0.10.2", @@ -3163,7 +3160,7 @@ dependencies = [ "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)", - "time 0.3.11", + "time 0.3.9", "tracing 0.1.35", ] @@ -3175,7 +3172,7 @@ dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tonic", ] @@ -3188,7 +3185,7 @@ dependencies = [ "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tonic", ] @@ -3298,7 +3295,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -3320,13 +3317,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.12.1", - "serde 1.0.138", + "hashbrown 0.11.2", + "serde 1.0.137", ] [[package]] @@ -3372,9 +3369,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "iovec" @@ -3438,9 +3435,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -3452,7 +3449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" dependencies = [ "log 0.4.17", - "serde 1.0.138", + "serde 1.0.137", "serde_json", ] @@ -3493,6 +3490,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[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" @@ -3562,7 +3565,7 @@ dependencies = [ "atomic", "bytes 1.1.0", "futures 0.3.21", - "lazy_static", + "lazy_static 1.4.0", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -3587,8 +3590,8 @@ dependencies = [ "libp2p-yamux", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.11", - "smallvec 1.9.0", + "pin-project 1.0.10", + "smallvec 1.8.0", "wasm-timer", ] @@ -3604,21 +3607,21 @@ dependencies = [ "fnv", "futures 0.3.21", "futures-timer", - "lazy_static", + "lazy_static 1.4.0", "libsecp256k1", "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.11", + "pin-project 1.0.10", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", "ring", "rw-stream-sink", "sha2 0.9.9", - "smallvec 1.9.0", + "smallvec 1.8.0", "thiserror", "unsigned-varint 0.7.1", "void", @@ -3644,7 +3647,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "log 0.4.17", - "smallvec 1.9.0", + "smallvec 1.8.0", "trust-dns-resolver", ] @@ -3662,7 +3665,7 @@ dependencies = [ "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.9.0", + "smallvec 1.8.0", ] [[package]] @@ -3685,7 +3688,7 @@ dependencies = [ "rand 0.7.3", "regex", "sha2 0.9.9", - "smallvec 1.9.0", + "smallvec 1.8.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3701,7 +3704,7 @@ dependencies = [ "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "smallvec 1.9.0", + "smallvec 1.8.0", "wasm-timer", ] @@ -3723,7 +3726,7 @@ dependencies = [ "prost-build 0.7.0", "rand 0.7.3", "sha2 0.9.9", - "smallvec 1.9.0", + "smallvec 1.8.0", "uint", "unsigned-varint 0.7.1", "void", @@ -3740,12 +3743,12 @@ dependencies = [ "dns-parser", "futures 0.3.21", "if-watch", - "lazy_static", + "lazy_static 1.4.0", "libp2p-core", "libp2p-swarm", "log 0.4.17", "rand 0.8.5", - "smallvec 1.9.0", + "smallvec 1.8.0", "socket2 0.4.4", "void", ] @@ -3763,7 +3766,7 @@ dependencies = [ "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", - "smallvec 1.9.0", + "smallvec 1.8.0", "unsigned-varint 0.7.1", ] @@ -3775,7 +3778,7 @@ dependencies = [ "bytes 1.1.0", "curve25519-dalek", "futures 0.3.21", - "lazy_static", + "lazy_static 1.4.0", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3825,7 +3828,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.11", + "pin-project 1.0.10", "rand 0.7.3", "salsa20", "sha3 0.9.1", @@ -3843,11 +3846,11 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log 0.4.17", - "pin-project 1.0.11", + "pin-project 1.0.10", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.9.0", + "smallvec 1.8.0", "unsigned-varint 0.7.1", "void", "wasm-timer", @@ -3867,7 +3870,7 @@ dependencies = [ "lru", "minicbor", "rand 0.7.3", - "smallvec 1.9.0", + "smallvec 1.8.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3882,7 +3885,7 @@ dependencies = [ "libp2p-core", "log 0.4.17", "rand 0.7.3", - "smallvec 1.9.0", + "smallvec 1.8.0", "void", "wasm-timer", ] @@ -4024,11 +4027,12 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", + "serde_test", ] [[package]] @@ -4144,7 +4148,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", "serde_yaml", ] @@ -4238,12 +4242,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", "integer-encoding", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "mio 0.7.14", - "serde 1.0.138", + "serde 1.0.137", "strum", "tungstenite 0.16.0", "url 2.2.2", @@ -4353,9 +4357,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log 0.4.17", @@ -4430,7 +4434,7 @@ dependencies = [ "good_lp", "petgraph 0.5.1", "rust_decimal", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "tokio", ] @@ -4482,8 +4486,8 @@ dependencies = [ "bytes 1.1.0", "futures 0.3.21", "log 0.4.17", - "pin-project 1.0.11", - "smallvec 1.9.0", + "pin-project 1.0.10", + "smallvec 1.8.0", "unsigned-varint 0.7.1", ] @@ -4493,7 +4497,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "libc", "log 0.4.17", "openssl", @@ -4531,9 +4535,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" dependencies = [ "bitflags", "cc", @@ -4544,9 +4548,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", @@ -4654,10 +4658,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.2", + "num-complex 0.4.1", "num-integer", "num-iter", - "num-rational 0.4.1", + "num-rational 0.4.0", "num-traits 0.2.15", ] @@ -4681,7 +4685,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -4696,9 +4700,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" dependencies = [ "num-traits 0.2.15", ] @@ -4749,9 +4753,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg 1.1.0", "num-bigint 0.4.3", @@ -4783,11 +4787,11 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", ] @@ -4810,6 +4814,12 @@ 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" @@ -4824,9 +4834,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "opaque-debug" @@ -4892,7 +4902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.7", + "getrandom 0.2.6", "subtle 2.4.1", "zeroize", ] @@ -4929,7 +4939,7 @@ dependencies = [ "data-encoding", "multihash", "percent-encoding 2.1.0", - "serde 1.0.138", + "serde 1.0.137", "static_assertions", "unsigned-varint 0.7.1", "url 2.2.2", @@ -4946,7 +4956,7 @@ dependencies = [ "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -5036,7 +5046,7 @@ dependencies = [ "instant", "libc", "redox_syscall 0.2.13", - "smallvec 1.9.0", + "smallvec 1.8.0", "winapi 0.3.9", ] @@ -5049,7 +5059,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.13", - "smallvec 1.9.0", + "smallvec 1.8.0", "windows-sys", ] @@ -5127,7 +5137,7 @@ checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset 0.2.0", "indexmap", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", ] @@ -5137,71 +5147,33 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.2", + "fixedbitset 0.4.1", "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" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" +checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" dependencies = [ - "pin-project-internal 0.4.30", + "pin-project-internal 0.4.29", ] [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ - "pin-project-internal 1.0.11", + "pin-project-internal 1.0.10", ] [[package]] name = "pin-project-internal" -version = "0.4.30" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" +checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" dependencies = [ "proc-macro2", "quote", @@ -5210,9 +5182,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -5382,9 +5354,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ "unicode-ident", ] @@ -5397,7 +5369,7 @@ dependencies = [ "bit-set", "bitflags", "byteorder", - "lazy_static", + "lazy_static 1.4.0", "num-traits 0.2.15", "quick-error 2.0.1", "rand 0.8.5", @@ -5455,7 +5427,7 @@ dependencies = [ "bytes 1.1.0", "heck 0.3.3", "itertools 0.10.3", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "multimap", "petgraph 0.6.2", @@ -5577,9 +5549,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.20" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -5604,7 +5576,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg 0.1.2", + "rand_pcg", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5620,7 +5592,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", - "rand_pcg 0.2.1", ] [[package]] @@ -5694,7 +5665,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", ] [[package]] @@ -5759,15 +5730,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" @@ -5812,7 +5774,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.10", + "crossbeam-utils 0.8.8", "num_cpus", ] @@ -5840,13 +5802,22 @@ 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.7", + "getrandom 0.2.6", "redox_syscall 0.2.13", "thiserror", ] @@ -5859,14 +5830,14 @@ checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log 0.4.17", "rustc-hash", - "smallvec 1.9.0", + "smallvec 1.8.0", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -5884,9 +5855,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "region" @@ -5920,9 +5891,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64 0.13.0", "bytes 1.1.0", @@ -5936,18 +5907,17 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.9", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5993,9 +5963,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" dependencies = [ "bytecheck", "hashbrown 0.12.1", @@ -6007,9 +5977,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" dependencies = [ "proc-macro2", "quote", @@ -6063,13 +6033,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.25.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -6114,7 +6084,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.12", + "semver 1.0.10", ] [[package]] @@ -6144,9 +6114,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rusty-fork" @@ -6167,7 +6137,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ "futures 0.3.21", - "pin-project 0.4.30", + "pin-project 0.4.29", "static_assertions", ] @@ -6254,7 +6224,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "windows-sys", ] @@ -6341,9 +6311,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.12" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" +checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" [[package]] name = "semver-parser" @@ -6368,9 +6338,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.138" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -6381,7 +6351,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num-traits 0.1.43", "regex", "serde 0.8.23", @@ -6396,7 +6366,7 @@ dependencies = [ "byteorder", "error", "num 0.2.1", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -6405,14 +6375,14 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", ] [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -6421,14 +6391,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -6438,7 +6408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -6452,6 +6422,15 @@ dependencies = [ "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" @@ -6461,7 +6440,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -6472,7 +6451,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.138", + "serde 1.0.137", "yaml-rust", ] @@ -6576,7 +6555,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", ] [[package]] @@ -6622,12 +6601,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.6" @@ -6645,9 +6618,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "snow" @@ -6753,15 +6726,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stderrlog" -version = "0.5.3" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460" +checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" dependencies = [ "atty", "chrono", "log 0.4.17", "termcolor", - "thread_local", + "thread_local 0.3.4", ] [[package]] @@ -6778,18 +6751,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -6840,9 +6813,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" dependencies = [ "proc-macro2", "quote", @@ -6927,7 +6900,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", @@ -6936,7 +6909,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", - "time 0.3.11", + "time 0.3.9", "zeroize", ] @@ -6955,7 +6928,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", @@ -6964,7 +6937,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "time 0.3.11", + "time 0.3.9", "zeroize", ] @@ -6974,7 +6947,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "toml", @@ -6987,7 +6960,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", @@ -7001,10 +6974,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix#d26 dependencies = [ "derive_more", "flex-error", - "serde 1.0.138", + "serde 1.0.137", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", - "time 0.3.11", + "time 0.3.9", ] [[package]] @@ -7014,10 +6987,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.138", + "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.11", + "time 0.3.9", ] [[package]] @@ -7031,10 +7004,10 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.11", + "time 0.3.9", ] [[package]] @@ -7048,10 +7021,10 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.11", + "time 0.3.9", ] [[package]] @@ -7064,14 +7037,14 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.7", + "getrandom 0.2.6", "http", "hyper 0.14.19", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.11", - "serde 1.0.138", + "pin-project 1.0.10", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -7079,7 +7052,7 @@ dependencies = [ "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "thiserror", - "time 0.3.11", + "time 0.3.9", "tokio", "tracing 0.1.35", "url 2.2.2", @@ -7097,14 +7070,14 @@ dependencies = [ "bytes 1.1.0", "flex-error", "futures 0.3.21", - "getrandom 0.2.7", + "getrandom 0.2.6", "http", "hyper 0.14.19", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.11", - "serde 1.0.138", + "pin-project 1.0.10", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -7112,7 +7085,7 @@ dependencies = [ "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)", "thiserror", - "time 0.3.11", + "time 0.3.9", "tokio", "tracing 0.1.35", "url 2.2.2", @@ -7127,12 +7100,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix#d26 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", - "time 0.3.11", + "time 0.3.9", ] [[package]] @@ -7142,12 +7115,22 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.138", + "serde 1.0.137", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "time 0.3.11", + "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", ] [[package]] @@ -7160,16 +7143,15 @@ dependencies = [ ] [[package]] -name = "terminfo" -version = "0.7.3" +name = "termion" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ - "dirs", - "fnv", - "nom 5.1.2", - "phf", - "phf_codegen", + "libc", + "numtoa", + "redox_syscall 0.2.13", + "redox_termios", ] [[package]] @@ -7195,6 +7177,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ + "term_size", "unicode-width", ] @@ -7227,6 +7210,16 @@ 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" @@ -7249,9 +7242,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "itoa", "libc", @@ -7283,7 +7276,7 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.11", + "time 0.3.9", "url 2.2.2", ] @@ -7311,7 +7304,7 @@ dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.4", + "mio 0.8.3", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -7405,7 +7398,7 @@ checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "mio 0.6.23", "num_cpus", @@ -7520,7 +7513,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.138", + "serde 1.0.137", ] [[package]] @@ -7541,7 +7534,7 @@ dependencies = [ "hyper 0.14.19", "hyper-timeout", "percent-encoding 2.1.0", - "pin-project 1.0.11", + "pin-project 1.0.10", "prost 0.9.0", "prost-derive 0.9.0", "tokio", @@ -7568,15 +7561,15 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.11", + "pin-project 1.0.10", "pin-project-lite 0.2.9", "rand 0.8.5", "slab", @@ -7594,7 +7587,7 @@ source = "git+https://github.com/heliaxdev/tower-abci?branch=murisi/jsfix#09de96 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.11", + "pin-project 1.0.10", "prost 0.9.0", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tokio", @@ -7612,7 +7605,7 @@ source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.11", + "pin-project 1.0.10", "prost 0.9.0", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tokio", @@ -7640,9 +7633,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" @@ -7664,8 +7657,8 @@ dependencies = [ "cfg-if 1.0.0", "log 0.4.17", "pin-project-lite 0.2.9", - "tracing-attributes 0.1.22", - "tracing-core 0.1.28", + "tracing-attributes 0.1.21", + "tracing-core 0.1.27", ] [[package]] @@ -7680,9 +7673,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -7694,14 +7687,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", + "lazy_static 1.4.0", ] [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" dependencies = [ "once_cell", "valuable", @@ -7723,7 +7716,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.11", + "pin-project 1.0.10", "tracing 0.1.35", ] @@ -7742,9 +7735,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", - "tracing-core 0.1.28", + "tracing-core 0.1.27", ] [[package]] @@ -7754,25 +7747,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local", - "tracing-core 0.1.28", + "thread_local 1.1.4", + "tracing-core 0.1.27", ] [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ "ansi_term", + "lazy_static 1.4.0", "matchers", - "once_cell", "regex", "sharded-slab", - "smallvec 1.9.0", - "thread_local", + "smallvec 1.8.0", + "thread_local 1.1.4", "tracing 0.1.35", - "tracing-core 0.1.28", + "tracing-core 0.1.27", "tracing-log", ] @@ -7811,10 +7804,10 @@ dependencies = [ "futures-util", "idna 0.2.3", "ipnet", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "rand 0.8.5", - "smallvec 1.9.0", + "smallvec 1.8.0", "thiserror", "tinyvec", "url 2.2.2", @@ -7829,12 +7822,12 @@ dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", - "smallvec 1.9.0", + "smallvec 1.8.0", "thiserror", "trust-dns-proto", ] @@ -7897,9 +7890,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" @@ -7939,15 +7932,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -7980,6 +7973,15 @@ dependencies = [ "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" @@ -8045,7 +8047,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", ] [[package]] @@ -8094,6 +8096,26 @@ 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" @@ -8150,9 +8172,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8160,12 +8182,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "proc-macro2", "quote", @@ -8175,9 +8197,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8187,9 +8209,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8197,9 +8219,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -8210,15 +8232,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wasm-encoder" -version = "0.14.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" +checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" dependencies = [ "leb128", ] @@ -8285,9 +8307,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", - "smallvec 1.9.0", + "smallvec 1.8.0", "target-lexicon", "thiserror", "wasmer-types", @@ -8308,7 +8330,7 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.9.0", + "smallvec 1.8.0", "target-lexicon", "tracing 0.1.35", "wasmer-compiler", @@ -8325,11 +8347,11 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static", + "lazy_static 1.4.0", "loupe", "more-asserts", "rayon", - "smallvec 1.9.0", + "smallvec 1.8.0", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -8355,12 +8377,12 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static", + "lazy_static 1.4.0", "loupe", "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.138", + "serde 1.0.137", "serde_bytes", "target-lexicon", "thiserror", @@ -8383,7 +8405,7 @@ dependencies = [ "loupe", "object", "rkyv", - "serde 1.0.138", + "serde 1.0.137", "tempfile", "tracing 0.1.35", "wasmer-compiler", @@ -8435,7 +8457,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.138", + "serde 1.0.137", "thiserror", ] @@ -8456,7 +8478,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.138", + "serde 1.0.137", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -8476,9 +8498,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "43.0.0" +version = "42.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" +checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" dependencies = [ "leb128", "memchr", @@ -8488,27 +8510,28 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.45" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" +checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" dependencies = [ "wast", ] [[package]] name = "watchexec" -version = "1.17.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52e0868bc57765fa91593a173323f464855e53b27f779e1110ba0fb4cb6b406" +checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" dependencies = [ - "clearscreen", - "command-group", + "clap 2.34.0", "derive_builder", + "embed-resource", + "env_logger", "glob", "globset", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", - "nix 0.22.3", + "nix 0.20.2", "notify", "walkdir", "winapi 0.3.9", @@ -8516,9 +8539,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -8526,18 +8549,18 @@ dependencies = [ [[package]] name = "web30" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463b0185237bb5c10abf6ce7a5923b03503aea0bb2ff5d9ad930d790e69c0cb0" +checksum = "c2a1cd88f65c3315ffda8722ee2de20ae5e9c607ebd009377fbfd2ea68375af3" dependencies = [ "awc", "clarity", "futures 0.3.21", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.138", + "serde 1.0.137", "serde_derive", "serde_json", "tokio", @@ -8619,7 +8642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", - "lazy_static", + "lazy_static 1.4.0", "libc", ] diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index b4e82ba83b..f47631084a 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num-rational", + "num", "parity-wasm", "proptest", "prost", @@ -343,9 +343,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -1585,6 +1585,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1596,6 +1610,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1617,6 +1640,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 1a49d16845..14c1d6e9f6 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num-rational", + "num", "parity-wasm", "proptest", "prost", @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "ark-bls12-381" @@ -257,9 +257,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ "async-stream-impl", "futures-core", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2", "quote", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -295,16 +295,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object", + "object 0.27.1", "rustc-demangle", ] @@ -343,9 +343,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -355,9 +355,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", "arrayvec", @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ "generic-array", ] @@ -398,7 +398,7 @@ version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ "borsh-derive", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -435,9 +435,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-slice-cast" @@ -447,9 +447,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" dependencies = [ "proc-macro2", "quote", @@ -532,9 +532,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -610,9 +610,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -631,26 +631,25 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" dependencies = [ - "autocfg", "cfg-if 1.0.0", "crossbeam-utils", + "lazy_static", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", - "once_cell", + "lazy_static", ] [[package]] @@ -661,9 +660,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", "typenum", @@ -671,9 +670,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", @@ -697,9 +696,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" dependencies = [ "darling_core", "darling_macro", @@ -707,22 +706,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" dependencies = [ "darling_core", "quote", @@ -766,16 +766,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.0", "crypto-common", "subtle", ] [[package]] name = "dynasm" -version = "1.2.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" dependencies = [ "bitflags", "byteorder", @@ -788,9 +788,9 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.2.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" dependencies = [ "byteorder", "dynasm", @@ -799,18 +799,18 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" dependencies = [ "signature", ] [[package]] name = "ed25519-consensus" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" +checksum = "542217a53411d471743362251a5a1770a667cb0cc0384c9be2c0952bd70a7275" dependencies = [ "curve25519-dalek-ng", "hex", @@ -835,9 +835,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "enum-iterator" @@ -861,18 +861,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" dependencies = [ "darling", "proc-macro2", @@ -976,9 +976,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flex-error" @@ -1085,13 +1085,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1113,18 +1113,18 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "gumdrop" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" dependencies = [ "gumdrop_derive", ] [[package]] name = "gumdrop_derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" dependencies = [ "proc-macro2", "quote", @@ -1133,9 +1133,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -1146,7 +1146,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.6.9", "tracing", ] @@ -1159,15 +1159,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" -dependencies = [ - "ahash", -] - [[package]] name = "heck" version = "0.3.3" @@ -1194,9 +1185,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.8" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", @@ -1205,9 +1196,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", "http", @@ -1216,9 +1207,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "httpdate" @@ -1228,9 +1219,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" dependencies = [ "bytes", "futures-channel", @@ -1285,7 +1276,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.11", + "time 0.3.7", "tracing", ] @@ -1381,12 +1372,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown 0.12.1", + "hashbrown", "serde", ] @@ -1410,24 +1401,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "lazy_static" @@ -1443,15 +1434,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1459,9 +1450,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", ] @@ -1513,15 +1504,15 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -1543,23 +1534,34 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", ] [[package]] @@ -1574,6 +1576,29 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1585,6 +1610,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1598,14 +1632,25 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -1620,9 +1665,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -1639,30 +1684,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.28.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ "crc32fast", - "hashbrown 0.11.2", + "hashbrown", "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "opaque-debug" @@ -1704,9 +1758,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "peg" @@ -1752,9 +1806,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", "indexmap", @@ -1762,18 +1816,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -1782,9 +1836,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -1856,11 +1910,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ - "unicode-ident", + "unicode-xid", ] [[package]] @@ -1980,9 +2034,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.20" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -2040,9 +2094,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ "autocfg", "crossbeam-deque", @@ -2052,21 +2106,22 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", + "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" dependencies = [ "bitflags", ] @@ -2084,9 +2139,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -2104,9 +2159,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "region" @@ -2151,12 +2206,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "49a37de5dfc60bae2d94961dacd03c7b80e426b66a99fa1b17799570dbdd8f96" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown", "ptr_meta", "rend", "rkyv_derive", @@ -2165,9 +2220,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "719d447dd0e84b23cee6cb5b32d97e21efb112a3e3c636c8da36647b938475a1" dependencies = [ "proc-macro2", "quote", @@ -2186,9 +2241,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.25.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" dependencies = [ "arrayvec", "num-traits", @@ -2224,9 +2279,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rusty-fork" @@ -2242,9 +2297,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "safe-proc-macro2" @@ -2334,27 +2389,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.138" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -2363,9 +2418,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", @@ -2374,9 +2429,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ "proc-macro2", "quote", @@ -2452,15 +2507,15 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" @@ -2501,6 +2556,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[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" @@ -2524,13 +2585,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "unicode-xid", ] [[package]] @@ -2553,9 +2614,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.4" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" +checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" [[package]] name = "tempfile" @@ -2595,7 +2656,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.11", + "time 0.3.7", "zeroize", ] @@ -2622,7 +2683,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.11", + "time 0.3.7", ] [[package]] @@ -2639,7 +2700,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.11", + "time 0.3.7", ] [[package]] @@ -2660,7 +2721,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.11", + "time 0.3.7", "url", "uuid", "walkdir", @@ -2678,14 +2739,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.11", + "time 0.3.7", ] [[package]] name = "test-log" -version = "0.2.10" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" +checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" dependencies = [ "proc-macro2", "quote", @@ -2694,18 +2755,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -2728,15 +2789,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", "winapi", ] [[package]] name = "time" -version = "0.3.11" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ "libc", "num_threads", @@ -2745,9 +2806,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" [[package]] name = "tiny-keccak" @@ -2760,9 +2821,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] @@ -2775,15 +2836,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes", "libc", "memchr", "mio", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2802,9 +2862,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -2813,9 +2873,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", @@ -2824,9 +2884,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ "bytes", "futures-core", @@ -2838,23 +2898,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" dependencies = [ "bytes", "futures-core", "futures-sink", + "log", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -2882,7 +2942,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util 0.6.9", "tower", "tower-layer", "tower-service", @@ -2904,9 +2964,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", @@ -2916,7 +2976,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.0", "tower-layer", "tower-service", "tracing", @@ -2930,15 +2990,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "log", @@ -2949,9 +3009,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -2960,11 +3020,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ - "once_cell", + "lazy_static", + "valuable", ] [[package]] @@ -2979,12 +3040,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" dependencies = [ + "lazy_static", "matchers", - "once_cell", "regex", "sharded-slab", "thread_local", @@ -3006,9 +3067,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" @@ -3024,21 +3085,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -3057,9 +3112,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" @@ -3079,6 +3134,12 @@ 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" @@ -3132,17 +3193,11 @@ 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.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3150,9 +3205,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -3165,9 +3220,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3175,9 +3230,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -3188,18 +3243,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" - -[[package]] -name = "wasm-encoder" -version = "0.14.0" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" -dependencies = [ - "leb128", -] +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "wasmer" @@ -3344,7 +3390,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.3", "rkyv", "serde", "tempfile", @@ -3383,7 +3429,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object", + "object 0.28.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3439,21 +3485,20 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "43.0.0" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" +checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.45" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" +checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" dependencies = [ "wast", ] @@ -3472,9 +3517,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ "either", "lazy_static", @@ -3512,49 +3557,6 @@ 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 = "wyz" version = "0.5.0" @@ -3566,9 +3568,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" dependencies = [ "zeroize_derive", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 934c40b4f2..6d89246b4c 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num-rational", + "num", "parity-wasm", "proptest", "prost", @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.58" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "ark-bls12-381" @@ -283,9 +283,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ "async-stream-impl", "futures-core", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2", "quote", @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -321,16 +321,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object", + "object 0.27.1", "rustc-demangle", ] @@ -369,9 +369,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" dependencies = [ "arrayref", "arrayvec", @@ -405,9 +405,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ "generic-array", ] @@ -424,7 +424,7 @@ version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ "borsh-derive", - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -461,9 +461,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-slice-cast" @@ -473,9 +473,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -483,9 +483,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" dependencies = [ "proc-macro2", "quote", @@ -558,9 +558,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -636,9 +636,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -657,26 +657,25 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" dependencies = [ - "autocfg", "cfg-if 1.0.0", "crossbeam-utils", + "lazy_static", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", - "once_cell", + "lazy_static", ] [[package]] @@ -687,9 +686,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", "typenum", @@ -697,9 +696,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", @@ -723,9 +722,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" dependencies = [ "darling_core", "darling_macro", @@ -733,22 +732,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" dependencies = [ "darling_core", "quote", @@ -792,16 +792,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.0", "crypto-common", "subtle", ] [[package]] name = "dynasm" -version = "1.2.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" +checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" dependencies = [ "bitflags", "byteorder", @@ -814,9 +814,9 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.2.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" +checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" dependencies = [ "byteorder", "dynasm", @@ -825,18 +825,18 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" dependencies = [ "signature", ] [[package]] name = "ed25519-consensus" -version = "1.2.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" +checksum = "542217a53411d471743362251a5a1770a667cb0cc0384c9be2c0952bd70a7275" dependencies = [ "curve25519-dalek-ng", "hex", @@ -861,9 +861,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "enum-iterator" @@ -887,18 +887,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" dependencies = [ "darling", "proc-macro2", @@ -1002,9 +1002,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flex-error" @@ -1111,13 +1111,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1139,18 +1139,18 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "gumdrop" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" dependencies = [ "gumdrop_derive", ] [[package]] name = "gumdrop_derive" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" dependencies = [ "proc-macro2", "quote", @@ -1159,9 +1159,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -1172,7 +1172,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.6.9", "tracing", ] @@ -1185,15 +1185,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "hashbrown" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" -dependencies = [ - "ahash", -] - [[package]] name = "heck" version = "0.3.3" @@ -1220,9 +1211,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.8" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", @@ -1231,9 +1222,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", "http", @@ -1242,9 +1233,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "httpdate" @@ -1254,9 +1245,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" dependencies = [ "bytes", "futures-channel", @@ -1311,7 +1302,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.11", + "time 0.3.7", "tracing", ] @@ -1407,12 +1398,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown 0.12.1", + "hashbrown", "serde", ] @@ -1436,24 +1427,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "lazy_static" @@ -1469,15 +1460,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1485,9 +1476,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", ] @@ -1539,15 +1530,15 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -1569,23 +1560,34 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", ] [[package]] @@ -1600,6 +1602,29 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1611,6 +1636,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1624,14 +1658,25 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -1646,9 +1691,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", ] @@ -1665,30 +1710,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.28.4" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ "crc32fast", - "hashbrown 0.11.2", + "hashbrown", "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "opaque-debug" @@ -1730,9 +1784,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "peg" @@ -1778,9 +1832,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", "indexmap", @@ -1788,18 +1842,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -1808,9 +1862,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -1882,11 +1936,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ - "unicode-ident", + "unicode-xid", ] [[package]] @@ -2006,9 +2060,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.20" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -2066,9 +2120,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" dependencies = [ "autocfg", "crossbeam-deque", @@ -2078,21 +2132,22 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", + "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" dependencies = [ "bitflags", ] @@ -2110,9 +2165,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -2130,9 +2185,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "region" @@ -2177,12 +2232,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "49a37de5dfc60bae2d94961dacd03c7b80e426b66a99fa1b17799570dbdd8f96" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown", "ptr_meta", "rend", "rkyv_derive", @@ -2191,9 +2246,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "719d447dd0e84b23cee6cb5b32d97e21efb112a3e3c636c8da36647b938475a1" dependencies = [ "proc-macro2", "quote", @@ -2212,9 +2267,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.25.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" dependencies = [ "arrayvec", "num-traits", @@ -2250,9 +2305,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rusty-fork" @@ -2268,9 +2323,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "safe-proc-macro2" @@ -2360,27 +2415,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.138" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -2389,9 +2444,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", @@ -2400,9 +2455,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" dependencies = [ "proc-macro2", "quote", @@ -2478,15 +2533,15 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" @@ -2527,6 +2582,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[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" @@ -2550,13 +2611,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.98" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "unicode-xid", ] [[package]] @@ -2579,9 +2640,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.4" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" +checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" [[package]] name = "tempfile" @@ -2621,7 +2682,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.11", + "time 0.3.7", "zeroize", ] @@ -2648,7 +2709,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.11", + "time 0.3.7", ] [[package]] @@ -2665,7 +2726,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.11", + "time 0.3.7", ] [[package]] @@ -2686,7 +2747,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.11", + "time 0.3.7", "url", "uuid", "walkdir", @@ -2704,14 +2765,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.11", + "time 0.3.7", ] [[package]] name = "test-log" -version = "0.2.10" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" +checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" dependencies = [ "proc-macro2", "quote", @@ -2720,18 +2781,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -2754,15 +2815,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", "winapi", ] [[package]] name = "time" -version = "0.3.11" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ "libc", "num_threads", @@ -2771,9 +2832,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" [[package]] name = "tiny-keccak" @@ -2786,9 +2847,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] @@ -2801,15 +2862,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes", "libc", "memchr", "mio", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2828,9 +2888,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -2839,9 +2899,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", @@ -2850,9 +2910,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.10" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" dependencies = [ "bytes", "futures-core", @@ -2864,23 +2924,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" dependencies = [ "bytes", "futures-core", "futures-sink", + "log", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -2908,7 +2968,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.10", + "tokio-util 0.6.9", "tower", "tower-layer", "tower-service", @@ -2930,9 +2990,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", @@ -2942,7 +3002,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.0", "tower-layer", "tower-service", "tracing", @@ -2956,15 +3016,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "log", @@ -2975,9 +3035,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -2986,11 +3046,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ - "once_cell", + "lazy_static", + "valuable", ] [[package]] @@ -3005,12 +3066,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" dependencies = [ + "lazy_static", "matchers", - "once_cell", "regex", "sharded-slab", "thread_local", @@ -3032,9 +3093,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" @@ -3050,21 +3111,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.1" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] @@ -3083,9 +3138,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" @@ -3105,6 +3160,12 @@ 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" @@ -3147,17 +3208,11 @@ 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.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3165,9 +3220,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -3180,9 +3235,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3190,9 +3245,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -3203,18 +3258,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" - -[[package]] -name = "wasm-encoder" -version = "0.14.0" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61" -dependencies = [ - "leb128", -] +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "wasmer" @@ -3359,7 +3405,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.3", "rkyv", "serde", "tempfile", @@ -3398,7 +3444,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object", + "object 0.28.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3454,21 +3500,20 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "43.0.0" +version = "39.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "408feaebf6dbf9d154957873b14d00e8fba4cbc17a8cbb1bc9e4c1db425c50a8" +checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.45" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" +checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" dependencies = [ "wast", ] @@ -3487,9 +3532,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ "either", "lazy_static", @@ -3527,49 +3572,6 @@ 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 = "wyz" version = "0.5.0" @@ -3581,9 +3583,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" dependencies = [ "zeroize_derive", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1b7d152c63..0780257deb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num-rational", + "num", "parity-wasm", "proptest", "prost", @@ -364,9 +364,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -1617,6 +1617,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1628,6 +1642,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -1649,6 +1672,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" From a15d2727377cc5e72b574e311890c017825e1ad2 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 11 Jul 2022 09:54:09 +0100 Subject: [PATCH 0071/1995] Revert to using num-rational 0.4.1 Less dependencies pulled into wasm crates --- Cargo.lock | 8 +++--- shared/Cargo.toml | 2 +- shared/src/types/ethereum_events.rs | 2 +- wasm/tx_template/Cargo.lock | 36 +-------------------------- wasm/vp_template/Cargo.lock | 36 +-------------------------- wasm/wasm_source/Cargo.lock | 36 +-------------------------- wasm_for_tests/wasm_source/Cargo.lock | 36 +-------------------------- 7 files changed, 10 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f55468d70c..5ecd530cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,7 +204,7 @@ dependencies = [ "ics23", "itertools 0.10.3", "loupe", - "num 0.4.0", + "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", @@ -4661,7 +4661,7 @@ dependencies = [ "num-complex 0.4.1", "num-integer", "num-iter", - "num-rational 0.4.0", + "num-rational 0.4.1", "num-traits 0.2.15", ] @@ -4753,9 +4753,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 0.4.3", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 5d1701da26..0187c3cf21 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -78,7 +78,7 @@ ethabi = "17.0.0" eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} -num = "0.4.0" +num-rational = "0.4.1" 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. diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index bba9cd3b1b..9b77173ef0 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -165,7 +165,7 @@ pub struct TokenWhitelist { pub mod vote_extensions { use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; - use num::rational::Ratio; + use num_rational::Ratio; use super::EthereumEvent; use crate::proto::MultiSigned; diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index f47631084a..427fdccec0 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1585,20 +1585,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1610,15 +1596,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1640,17 +1617,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 14c1d6e9f6..45ffd02fb6 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1585,20 +1585,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1610,15 +1596,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1640,17 +1617,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 6d89246b4c..ce84c5f64e 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1611,20 +1611,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1636,15 +1622,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1666,17 +1643,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 0780257deb..d53ec4fbd5 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -59,7 +59,7 @@ dependencies = [ "ics23", "itertools", "loupe", - "num", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1617,20 +1617,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1642,15 +1628,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1672,17 +1649,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" From bf08589bd96baa4b6d3894a9d21e7553b9510cbc Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 11 Jul 2022 10:02:34 +0100 Subject: [PATCH 0072/1995] Add BlockHeight to MultiSignedEthEvent --- shared/src/types/ethereum_events.rs | 3 ++- shared/src/types/storage.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 9b77173ef0..bd81e17a52 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -170,6 +170,7 @@ pub mod vote_extensions { use super::EthereumEvent; use crate::proto::MultiSigned; use crate::types::address::Address; + use crate::types::storage::BlockHeight; /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. @@ -245,7 +246,7 @@ pub mod vote_extensions { /// Address and voting power of the signing validators pub signers: Vec<(Address, FractionalVotingPower)>, /// Events as signed by validators - pub event: MultiSigned, + pub event: MultiSigned<(EthereumEvent, BlockHeight)>, } #[cfg(test)] diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 028d46470c..e789a95dc4 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -51,6 +51,7 @@ pub const RESERVED_VP_KEY: &str = "?"; Copy, BorshSerialize, BorshDeserialize, + BorshSchema, PartialEq, Eq, PartialOrd, From 12cca9f3b34f8ce35e4daf5b7b3c79f84329177d Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 00:48:24 +0200 Subject: [PATCH 0073/1995] [feat]: Added in vote extensions that include ethereum events. --- apps/src/lib/node/ledger/shell/mod.rs | 23 +- apps/src/lib/node/ledger/shell/queries.rs | 86 +++- .../lib/node/ledger/shell/vote_extensions.rs | 469 ++++++++++++++++++ shared/src/types/ethereum_events.rs | 210 +++++++- shared/src/types/storage.rs | 1 + 5 files changed, 748 insertions(+), 41 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/vote_extensions.rs diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 10c9167323..3509436016 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 vote_extensions; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; @@ -32,6 +33,8 @@ use anoma::ledger::storage::{ use anoma::ledger::{ibc, parameters, pos}; use anoma::proto::{self, Tx}; use anoma::types::chain::ChainId; +#[cfg(not(feature = "ABCI"))] +use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; use anoma::types::ethereum_events::EthereumEvent; use anoma::types::key::*; use anoma::types::storage::{BlockHeight, Key}; @@ -541,26 +544,6 @@ where } } - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn extend_vote( - &self, - _req: request::ExtendVote, - ) -> response::ExtendVote { - Default::default() - } - - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn verify_vote_extension( - &self, - _req: request::VerifyVoteExtension, - ) -> response::VerifyVoteExtension { - response::VerifyVoteExtension { - status: VerifyStatus::Accept as i32, - } - } - /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cb750ce122..bc486eeddc 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,11 +2,13 @@ use std::cmp::max; use anoma::ledger::parameters::EpochDuration; +#[cfg(not(feature = "ABCI"))] +use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; use anoma::ledger::pos::PosParams; use anoma::types::address::Address; use anoma::types::key; use anoma::types::key::dkg_session_keys::DkgPublicKey; -use anoma::types::storage::{Key, PrefixValue}; +use anoma::types::storage::{Epoch, Key, PrefixValue}; use anoma::types::token::{self, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; @@ -299,16 +301,17 @@ where pub fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, + epoch: Option, ) -> Option> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); // get the current epoch - let (current_epoch, _) = self.storage.get_current_epoch(); + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() - .get(current_epoch) + .get(epoch) .expect("Validators for the next epoch should be known") .active .iter() @@ -342,4 +345,81 @@ where } }) } + + /// Lookup data about a validator from their address + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Option<(VotingPower, common::PublicKey)> { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for the next epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .storage + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.voting_power, protocol_pk) + }) + } + + /// Lookup the total voting power for an epoch + #[cfg(not(feature = "ABCI"))] + pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .map(|validators| { + validators + .active + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + }) + .unwrap_or_default() + } + + /// Get the voting power of this node (as a fraction of this epochs total + /// voting power) if it is a validator. Else return None + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_voting_power(&self) -> Option { + let power = if let Some(secret_key) = self.mode.get_protocol_key() { + match self + .get_validator_from_protocol_pk(&secret_key.ref_to(), None) + { + Some(validator) => Some(validator.power), + _ => None, + } + } else { + None + }; + let total = u64::from(self.get_total_voting_power(None)); + match power { + Some(power) if total > 0 => { + FractionalVotingPower::new(power, total).ok() + } + _ => None, + } + } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs new file mode 100644 index 0000000000..e0236052eb --- /dev/null +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -0,0 +1,469 @@ +#[cfg(not(feature = "ABCI"))] +mod extend_votes { + use anoma::types::ethereum_events::vote_extensions::{ + EpochPower, SignedEthEvent, SignedEvent, + }; + + use super::super::*; + + /// The data we include in a vote extension + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] + pub struct VoteExtension { + /// Ethereum events seen since last round + ethereum_events: Vec, + } + + impl Shell + where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, + { + /// INVARIANT: This method must be stateless. + pub fn extend_vote( + &mut self, + _req: request::ExtendVote, + ) -> response::ExtendVote { + response::ExtendVote { + vote_extension: VoteExtension { + ethereum_events: self.new_ethereum_events(), + } + .try_to_vec() + .unwrap(), + } + } + + /// At present this checks the signature on all Ethereum headers + /// + /// INVARIANT: This method must be stateless. + pub fn verify_vote_extension( + &self, + req: request::VerifyVoteExtension, + ) -> response::VerifyVoteExtension { + if let Ok(VoteExtension { ethereum_events }) = + VoteExtension::try_from_slice(&req.vote_extension[..]) + { + return if ethereum_events.iter().all(|event| { + self.validate_ethereum_event( + self.storage.last_height + 1, + event, + ) + }) { + response::VerifyVoteExtension { + status: VerifyStatus::Accept.into(), + } + } else { + response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + } + }; + } + Default::default() + } + + /// Checks the channel from the Ethereum oracle monitoring + /// the fullnode and retrieves all messages sent. These are + /// signed and prepared for inclusion in a vote extension. + pub fn new_ethereum_events(&mut self) -> Vec { + let mut events = vec![]; + let voting_power = self.get_validator_voting_power(); + let address = self.mode.get_validator_address().cloned(); + if let ShellMode::Validator { + ref mut ethereum_recv, + data: + ValidatorData { + keys: + ValidatorKeys { + protocol_keypair, .. + }, + .. + }, + .. + } = &mut self.mode + { + let (voting_power, address) = + voting_power.zip(address).unwrap(); + while let Ok(eth_event) = ethereum_recv.try_recv() { + events.push(SignedEthEvent::new( + eth_event, + address.clone(), + voting_power.clone(), + self.storage.last_height + 1, + protocol_keypair, + )); + } + } + events + } + + /// Verify that each ethereum header in a vote extension was signed by + /// a validator in the correct epoch, the stated voting power is + /// correct, and the signature is correct. + pub fn validate_ethereum_event( + &self, + height: BlockHeight, + event: &impl SignedEvent, + ) -> bool { + let epoch = self.storage.block.pred_epochs.get_epoch(height); + let total_voting_power = self.get_total_voting_power(epoch); + + // Get the public keys of each validator. Filter out those that + // inaccurately stated their voting power at a given block height + let public_keys: Vec = event + .get_voting_powers() + .into_iter() + .filter_map( + |EpochPower { + validator, + voting_power, + block_height, + }| { + if block_height != height { + return None; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + FractionalVotingPower::new( + power, + total_voting_power, + ) + .ok() + .and_then(|power| { + if power == voting_power { + Some(pk) + } else { + None + } + }) + } else { + None + } + }, + ) + .collect(); + // check that we found all the public keys and + // check that the signatures are valid + public_keys.len() == event.number_of_signers() + && event.verify_signatures(&public_keys).is_ok() + } + } + + #[cfg(test)] + mod test_vote_extensions { + use std::convert::TryInto; + + use anoma::ledger::pos; + use anoma::ledger::pos::anoma_proof_of_stake::PosBase; + use anoma::types::ethereum_events::vote_extensions::{ + FractionalVotingPower, MultiSignedEthEvent, SignedEthEvent, + }; + use anoma::types::ethereum_events::{ + EthAddress, EthereumEvent, TransferToEthereum, + }; + use anoma::types::key::*; + use anoma::types::storage::{BlockHeight, Epoch}; + use borsh::{BorshDeserialize, BorshSerialize}; + use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + use tower_abci::request; + + use crate::node::ledger::shell::test_utils::*; + use crate::node::ledger::shell::vote_extensions::VoteExtension; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + + /// Test that we successfully receive ethereum events + /// from the channel to fullnode process + /// + /// We further check that ledger side buffering is done if multiple + /// events are in the channel + #[test] + fn test_get_eth_events() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = shell + .new_ethereum_events() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + } + + /// Test that ethereum events are added to vote extensions. + /// Check that vote extensions pass verification. + #[test] + fn test_eth_events_vote_extension() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let vote_extension: VoteExtension = + BorshDeserialize::try_from_slice( + &shell.extend_vote(Default::default()).vote_extension[..], + ) + .expect("Test failed"); + + let [event_first, event_second]: [EthereumEvent; 2] = + vote_extension + .ethereum_events + .clone() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: vec![], + height: 0, + vote_extension: vote_extension + .try_to_vec() + .expect("Test failed"), + }; + let res = shell.verify_vote_extension(req); + assert_eq!(res.status, i32::from(VerifyStatus::Accept)); + } + + /// Test that Ethereum headers signed by a non-validator is rejected + #[test] + fn test_eth_events_must_be_signed_by_validator() { + let (shell, _, _) = setup(); + let signing_key = gen_keypair(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that validation of vote extensions cast during the + /// previous block are accepted for the current block. This + /// should pass even if the epoch changed resulting in a + /// change to the validator set. + #[test] + fn test_validate_vote_extensions() { + let (mut shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let height = shell.storage.last_height + 1; + + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert_eq!(shell.storage.get_current_epoch().0.0, 0); + // We make a change so that there are no + // validators in the next epoch + let mut current_validators = shell.storage.read_validator_set(); + current_validators.data.insert( + 1, + Some(pos::types::ValidatorSet { + active: Default::default(), + inactive: Default::default(), + }), + ); + shell.storage.write_validator_set(¤t_validators); + // we advance forward to the next epoch + let mut req = FinalizeBlock::default(); + req.header.time = anoma::types::time::DateTimeUtc::now(); + shell.storage.last_height = BlockHeight(11); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + assert_eq!(shell.storage.get_current_epoch().0.0, 1); + assert!( + shell + .get_validator_from_protocol_pk(&signing_key.ref_to(), None) + .is_none() + ); + let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); + assert!( + shell + .shell + .get_validator_from_protocol_pk( + &signing_key.ref_to(), + Some(prev_epoch) + ) + .is_some() + ); + assert!(shell.validate_ethereum_event(height, &signed_event)); + assert!(shell.validate_ethereum_event( + height, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that if the declared voting power is not correct, + /// the signed event is rejected + #[test] + fn reject_incorrect_voting_power() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = 99u64; + let total_voting_power = + u64::from(shell.get_total_voting_power(None)); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + FractionalVotingPower::new(voting_power, total_voting_power) + .expect("Test failed"), + shell.storage.last_height + 1, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event that incorrectly labels what block it was + /// included in a vote extension on is rejected + #[test] + fn reject_incorrect_block_number() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event with an incorrect address + /// included in a vote extension is rejected + #[test] + fn reject_incorrect_address() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + crate::wallet::defaults::bertha_address(), + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + } +} + +#[cfg(not(feature = "ABCI"))] +pub use extend_votes::*; diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index cfdca7f429..3096952a64 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -44,7 +44,9 @@ pub struct EthAddress(pub [u8; 20]); pub struct KeccakHash(pub [u8; 32]); /// An Ethereum event to be processed by the Anoma ledger -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub enum EthereumEvent { /// Event transferring batches of ether or Ethereum based ERC20 tokens /// from Ethereum to wrapped assets on Anoma @@ -163,15 +165,20 @@ pub struct TokenWhitelist { /// Contains types necessary for processing Ethereum events /// in vote extensions pub mod vote_extensions { + use std::cmp::Ordering; use std::convert::TryFrom; + use std::hash::Hasher; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use num_rational::Ratio; use super::EthereumEvent; - use crate::proto::MultiSigned; + use crate::proto::{MultiSigned, Signed}; use crate::types::address::Address; + use crate::types::key::common::PublicKey; + use crate::types::key::{common, VerifySigError}; + use crate::types::storage::BlockHeight; /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. @@ -181,7 +188,12 @@ pub mod vote_extensions { impl FractionalVotingPower { /// Create a new FractionalVotingPower. It must be between zero and one /// inclusive. - pub fn new(numer: u64, denom: u64) -> Result { + pub fn new( + numer: impl Into, + denom: impl Into, + ) -> Result { + let numer = numer.into(); + let denom = denom.into(); if denom == 0 { return Err(eyre!("denominator can't be zero")); } @@ -201,6 +213,12 @@ pub mod vote_extensions { } } + impl From for (u64, u64) { + fn from(ratio: FractionalVotingPower) -> Self { + (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) + } + } + impl BorshSerialize for FractionalVotingPower { fn serialize( &self, @@ -251,14 +269,170 @@ pub mod vote_extensions { } } + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] + /// Struct that represents the voting power + /// of a validator at given block height + pub struct EpochPower { + /// Address of a validator + pub validator: Address, + /// voting power of validator at block `block_height` + pub voting_power: FractionalVotingPower, + /// The height of the block at which the validator has this voting + /// power + pub block_height: BlockHeight, + } + + impl PartialOrd for EpochPower { + fn partial_cmp(&self, other: &Self) -> Option { + self.validator.partial_cmp(&other.validator) + } + } + + impl PartialEq for EpochPower { + fn eq(&self, other: &Self) -> bool { + self.validator.eq(&other.validator) + } + } + + impl Eq for EpochPower {} + + impl core::hash::Hash for EpochPower { + fn hash(&self, state: &mut H) { + ::hash( + self.validator.encode().as_str(), + state, + ) + } + } + + /// A uniform interface for signed and multi-signed ethereum events + pub trait SignedEvent { + /// Get the block height at which this event was seen + fn get_height(&self) -> BlockHeight; + /// Get the normalized voting power of each signer + fn get_voting_powers(&self) -> Vec; + /// Get the number of signers whose signature is included + fn number_of_signers(&self) -> usize; + /// Verify the signatures of a signed event + fn verify_signatures( + &self, + public_keys: &[common::PublicKey], + ) -> Result<(), VerifySigError>; + } + + /// A struct used by validators to sign that they have seen a particular + /// ethereum event. These are included in vote extensions + #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] + pub struct SignedEthEvent { + /// The address of the signing validator + pub signer: Address, + /// The proportion of the total voting power held by the validator + pub power: FractionalVotingPower, + /// The event being signed and the block height at which + /// it was seen. We include the height as part of enforcing + /// that a block proposer submits vote extensions from + /// **the previous round only** + pub event: Signed<(EthereumEvent, BlockHeight)>, + } + + impl SignedEthEvent { + /// Sign an Ethereum event + block height + pub fn new( + event: EthereumEvent, + signer: Address, + power: FractionalVotingPower, + height: BlockHeight, + key: &common::SecretKey, + ) -> Self { + Self { + signer, + power, + event: Signed::new(key, (event, height)), + } + } + } + + impl SignedEvent for SignedEthEvent { + fn get_height(&self) -> BlockHeight { + let Signed { + data: (_, height), .. + } = self.event; + height + } + + fn get_voting_powers(&self) -> Vec { + vec![EpochPower { + validator: self.signer.clone(), + voting_power: self.power.clone(), + block_height: self.get_height(), + }] + } + + fn number_of_signers(&self) -> usize { + 1 + } + + fn verify_signatures( + &self, + public_keys: &[PublicKey], + ) -> Result<(), VerifySigError> { + self.event.verify(&public_keys[0]) + } + } + /// This is created by the block proposer based on the Ethereum events - /// included in the vote extensions of the previous Tendermint round + /// included in the vote extensions of the previous Tendermint round. + /// This is an aggregation meant to reduce space taken up in blocks. #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedEthEvent { /// Address and voting power of the signing validators pub signers: Vec<(Address, FractionalVotingPower)>, /// Events as signed by validators - pub event: MultiSigned, + pub event: MultiSigned<(EthereumEvent, BlockHeight)>, + } + + impl SignedEvent for MultiSignedEthEvent { + fn get_height(&self) -> BlockHeight { + let MultiSigned { + data: (_, height), .. + } = self.event; + height + } + + fn get_voting_powers(&self) -> Vec { + let height = self.get_height(); + self.signers + .iter() + .map(|(addr, power)| EpochPower { + validator: addr.clone(), + voting_power: power.clone(), + block_height: height, + }) + .collect() + } + + fn number_of_signers(&self) -> usize { + self.signers.len() + } + + fn verify_signatures( + &self, + public_keys: &[PublicKey], + ) -> Result<(), VerifySigError> { + self.event.verify(public_keys) + } + } + + impl From for MultiSignedEthEvent { + fn from(event: SignedEthEvent) -> Self { + Self { + signers: vec![(event.signer, event.power)], + event: MultiSigned { + data: event.event.data, + sigs: vec![event.event.sig], + }, + } + } } #[cfg(test)] @@ -294,28 +468,28 @@ pub mod vote_extensions { #[test] fn test_fractional_voting_power_ord_eq() { assert!( - FractionalVotingPower::new(2, 3).unwrap() - > FractionalVotingPower::new(1, 4).unwrap() + FractionalVotingPower::new(2u64, 3u64).unwrap() + > FractionalVotingPower::new(1u64, 4u64).unwrap() ); assert!( - FractionalVotingPower::new(1, 3).unwrap() - > FractionalVotingPower::new(1, 4).unwrap() + FractionalVotingPower::new(1u64, 3u64).unwrap() + > FractionalVotingPower::new(1u64, 4u64).unwrap() ); - assert!( - FractionalVotingPower::new(1, 3).unwrap() - == FractionalVotingPower::new(2, 6).unwrap() + assert_eq!( + FractionalVotingPower::new(1u64, 3u64).unwrap(), + FractionalVotingPower::new(2u64, 6u64).unwrap() ); } /// Test error handling on the FractionalVotingPower type #[test] fn test_fractional_voting_power_valid_fractions() { - assert!(FractionalVotingPower::new(0, 0).is_err()); - assert!(FractionalVotingPower::new(1, 0).is_err()); - assert!(FractionalVotingPower::new(0, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 2).is_ok()); - assert!(FractionalVotingPower::new(3, 2).is_err()); + assert!(FractionalVotingPower::new(0u64, 0u64).is_err()); + assert!(FractionalVotingPower::new(1u64, 0u64).is_err()); + assert!(FractionalVotingPower::new(0u64, 1u64).is_ok()); + assert!(FractionalVotingPower::new(1u64, 1u64).is_ok()); + assert!(FractionalVotingPower::new(1u64, 2u64).is_ok()); + assert!(FractionalVotingPower::new(3u64, 2u64).is_err()); } } } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 028d46470c..e789a95dc4 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -51,6 +51,7 @@ pub const RESERVED_VP_KEY: &str = "?"; Copy, BorshSerialize, BorshDeserialize, + BorshSchema, PartialEq, Eq, PartialOrd, From db34def0b9b47ad220dbe189137a5b5255f7efe2 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 11:40:54 +0200 Subject: [PATCH 0074/1995] [cleanup]: Refactored some ugly functional code that was bugging me --- .../lib/node/ledger/shell/vote_extensions.rs | 57 ++++++++----------- shared/src/types/ethereum_events.rs | 4 -- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e0236052eb..672781e5a6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -105,42 +105,33 @@ mod extend_votes { ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); let total_voting_power = self.get_total_voting_power(epoch); + if u64::from(total_voting_power) == 0 { + return false; + } // Get the public keys of each validator. Filter out those that // inaccurately stated their voting power at a given block height - let public_keys: Vec = event - .get_voting_powers() - .into_iter() - .filter_map( - |EpochPower { - validator, - voting_power, - block_height, - }| { - if block_height != height { - return None; - } - if let Some((power, pk)) = - self.get_validator_from_address(&validator, epoch) - { - FractionalVotingPower::new( - power, - total_voting_power, - ) - .ok() - .and_then(|power| { - if power == voting_power { - Some(pk) - } else { - None - } - }) - } else { - None - } - }, - ) - .collect(); + let mut public_keys = vec![]; + for EpochPower { + validator, + voting_power, + block_height, + } in event.get_voting_powers().into_iter() + { + if block_height != height { + continue; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + let power = + FractionalVotingPower::new(power, total_voting_power) + .unwrap(); + if power == voting_power { + public_keys.push(pk); + } + } + } // check that we found all the public keys and // check that the signatures are valid public_keys.len() == event.number_of_signers() diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index fb923673bc..761a4d8e8c 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -165,12 +165,9 @@ pub struct TokenWhitelist { /// Contains types necessary for processing Ethereum events /// in vote extensions pub mod vote_extensions { - use std::cmp::Ordering; - use std::convert::TryFrom; use std::hash::Hasher; - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use num_rational::Ratio; @@ -424,7 +421,6 @@ pub mod vote_extensions { }, } } - } #[cfg(test)] From bdd0ab5a86b8c653f59ac6b3f36dd9bacf094c7f Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:28 +0200 Subject: [PATCH 0075/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index bc486eeddc..4ae8ac9010 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -353,7 +353,6 @@ where address: &Address, epoch: Option, ) -> Option<(VotingPower, common::PublicKey)> { - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From 7d69aaf67f9521fe565304172ed16ec39533948b Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:35 +0200 Subject: [PATCH 0076/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 4ae8ac9010..7dd310239f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -306,7 +306,6 @@ where let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From 81898ab23887adbf2236fac7f3d3d9a63e9f88c3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 13 Jul 2022 12:41:51 +0100 Subject: [PATCH 0077/1995] Change apply_tx to accept a ShellParams containing wasm_dir From bat/eth-header-vp branch --- apps/src/lib/node/ledger/protocol/mod.rs | 49 ++++++++++++++++--- .../lib/node/ledger/shell/finalize_block.rs | 25 ++-------- apps/src/lib/node/ledger/shell/mod.rs | 24 +++++---- 3 files changed, 61 insertions(+), 37 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 237eb450f1..bf04fa4726 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -22,6 +22,8 @@ use anoma::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; +use crate::node::ledger::shell::Shell; + #[derive(Error, Debug)] pub enum Error { #[error("Storage error: {0}")] @@ -58,6 +60,38 @@ pub enum Error { AccessForbidden(InternalAddress), } +pub(crate) struct ShellParams<'a, D, H, CA> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + CA: 'static + WasmCacheAccess + Sync, +{ + pub block_gas_meter: &'a mut BlockGasMeter, + pub write_log: &'a mut WriteLog, + pub storage: &'a Storage, + pub wasm_dir: &'a std::path::Path, + pub vp_wasm_cache: &'a mut VpCache, + pub tx_wasm_cache: &'a mut TxCache, +} + +impl<'a, D, H> From<&'a mut Shell> + for ShellParams<'a, D, H, anoma::vm::WasmCacheRwAccess> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + fn from(shell: &'a mut Shell) -> Self { + Self { + block_gas_meter: &mut shell.gas_meter, + write_log: &mut shell.write_log, + storage: &mut shell.storage, + wasm_dir: shell.wasm_dir.as_path(), + vp_wasm_cache: &mut shell.vp_wasm_cache, + tx_wasm_cache: &mut shell.tx_wasm_cache, + } + } +} + pub type Result = std::result::Result; /// Apply a given transaction @@ -65,14 +99,17 @@ pub type Result = std::result::Result; /// If the given tx is a successfully decrypted payload apply the necessary /// vps. Otherwise, we include the tx on chain with the gas charge added /// but no further validations. -pub fn apply_tx( +pub(crate) fn apply_tx<'a, D, H, CA>( tx: TxType, tx_length: usize, - block_gas_meter: &mut BlockGasMeter, - write_log: &mut WriteLog, - storage: &Storage, - vp_wasm_cache: &mut VpCache, - tx_wasm_cache: &mut TxCache, + ShellParams { + block_gas_meter, + write_log, + storage, + wasm_dir: _wasm_dir, + vp_wasm_cache, + tx_wasm_cache, + }: ShellParams<'a, D, H, CA>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index aab8d54bb1..0eeb68caf2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -116,17 +116,8 @@ where .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, - ); + let tx_result = + protocol::apply_tx(tx_type, 0, self.into()); self.storage .delete(&pending_execution_key) .expect( @@ -342,16 +333,8 @@ where }, }; - match protocol::apply_tx( - tx_type, - tx_length, - &mut self.gas_meter, - &mut self.write_log, - &self.storage, - &mut self.vp_wasm_cache, - &mut self.tx_wasm_cache, - ) - .map_err(Error::TxApply) + match protocol::apply_tx(tx_type, tx_length, self.into()) + .map_err(Error::TxApply) { Ok(result) => { if result.is_accepted() { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 10c9167323..e5867fddb4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -73,6 +73,7 @@ use tower_abci_old::{request, response}; use super::rpc; use crate::config::{genesis, TendermintMode}; use crate::node::ledger::events::Event; +use crate::node::ledger::protocol::ShellParams; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{protocol, storage, tendermint_node}; @@ -222,9 +223,9 @@ where /// The persistent storage pub(super) storage: Storage, /// Gas meter for the current block - gas_meter: BlockGasMeter, + pub(super) gas_meter: BlockGasMeter, /// Write log for the current block - write_log: WriteLog, + pub(super) write_log: WriteLog, /// Byzantine validators given from ABCI++ `prepare_proposal` are stored in /// this field. They will be slashed when we finalize the block. byzantine_validators: Vec, @@ -232,14 +233,14 @@ where #[allow(dead_code)] base_dir: PathBuf, /// Path to the WASM directory for files used in the genesis block. - wasm_dir: PathBuf, + pub(super) wasm_dir: PathBuf, /// Information about the running shell instance #[allow(dead_code)] mode: ShellMode, /// VP WASM compilation cache - vp_wasm_cache: VpCache, + pub(super) vp_wasm_cache: VpCache, /// Tx WASM compilation cache - tx_wasm_cache: TxCache, + pub(super) tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, } @@ -619,11 +620,14 @@ where match protocol::apply_tx( tx, tx_bytes.len(), - &mut gas_meter, - &mut write_log, - &self.storage, - &mut vp_wasm_cache, - &mut tx_wasm_cache, + ShellParams { + block_gas_meter: &mut gas_meter, + write_log: &mut write_log, + storage: &self.storage, + wasm_dir: self.wasm_dir.as_path(), + vp_wasm_cache: &mut vp_wasm_cache, + tx_wasm_cache: &mut tx_wasm_cache, + }, ) .map_err(Error::TxApply) { From 0373e86a318243b7ff99d432f76892c217460941 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 13 Jul 2022 12:55:48 +0100 Subject: [PATCH 0078/1995] Put back comment --- apps/src/lib/node/ledger/shell/finalize_block.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 0eeb68caf2..269998d5b2 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -116,8 +116,13 @@ where .expect( "Should be able to write to storage.", ); - let tx_result = - protocol::apply_tx(tx_type, 0, self.into()); + 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. */ + self.into(), + ); self.storage .delete(&pending_execution_key) .expect( From 70b1548ce436d0743fcf5bf18d1f7c6866e29ac1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 14:32:06 +0100 Subject: [PATCH 0079/1995] Move vote_extensions to its own file --- shared/src/types/ethereum_events.rs | 150 +----------------- .../types/ethereum_events/vote_extensions.rs | 146 +++++++++++++++++ 2 files changed, 148 insertions(+), 148 deletions(-) create mode 100644 shared/src/types/ethereum_events/vote_extensions.rs diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index bd81e17a52..2ea93d5f17 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -1,5 +1,7 @@ //! Types representing data intended for Anoma via Ethereum events +pub mod vote_extensions; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; @@ -159,151 +161,3 @@ pub struct TokenWhitelist { /// Maximum amount of token allowed on the bridge pub cap: Amount, } - -/// Contains types necessary for processing Ethereum events -/// in vote extensions -pub mod vote_extensions { - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; - use eyre::{eyre, Result}; - use num_rational::Ratio; - - use super::EthereumEvent; - use crate::proto::MultiSigned; - use crate::types::address::Address; - use crate::types::storage::BlockHeight; - - /// A fraction of the total voting power. This should always be a reduced - /// fraction that is between zero and one inclusive. - #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] - pub struct FractionalVotingPower(Ratio); - - impl FractionalVotingPower { - /// Create a new FractionalVotingPower. It must be between zero and one - /// inclusive. - pub fn new(numer: u64, denom: u64) -> Result { - if denom == 0 { - return Err(eyre!("denominator can't be zero")); - } - let ratio: Ratio = (numer, denom).into(); - if ratio > 1.into() { - return Err(eyre!( - "fractional voting power cannot be greater than one" - )); - } - Ok(Self(ratio)) - } - } - - impl From<&FractionalVotingPower> for (u64, u64) { - fn from(ratio: &FractionalVotingPower) -> Self { - (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) - } - } - - impl BorshSerialize for FractionalVotingPower { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let (numer, denom): (u64, u64) = self.into(); - (numer, denom).serialize(writer) - } - } - - impl BorshDeserialize for FractionalVotingPower { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let (numer, denom): (u64, u64) = - BorshDeserialize::deserialize(buf)?; - Ok(FractionalVotingPower(Ratio::::new(numer, denom))) - } - } - - impl BorshSchema for FractionalVotingPower { - fn add_definitions_recursively( - definitions: &mut std::collections::HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - u64::declaration(), - u64::declaration() - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "FractionalVotingPower".into() - } - } - - /// This is created by the block proposer based on the Ethereum events - /// included in the vote extensions of the previous Tendermint round - #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] - pub struct MultiSignedEthEvent { - /// Address and voting power of the signing validators - pub signers: Vec<(Address, FractionalVotingPower)>, - /// Events as signed by validators - pub event: MultiSigned<(EthereumEvent, BlockHeight)>, - } - - #[cfg(test)] - mod tests { - use super::*; - use crate::types::ethereum_events::Uint; - use crate::types::hash::Hash; - - /// Test the hashing of an Ethereum event - #[test] - fn test_ethereum_event_hash() { - let nonce = Uint::from(123u64); - let event = EthereumEvent::TransfersToNamada { - nonce, - transfers: vec![], - }; - let hash = event.hash().unwrap(); - - assert_eq!( - hash, - Hash([ - 94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, - 236, 209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, - 217, 204, 80, 38, 224, 23 - ]) - ); - } - - /// This test is ultimately just exercising the underlying - /// library we use for fractions, we want to make sure - /// operators work as expected with our FractionalVotingPower - /// type itself - #[test] - fn test_fractional_voting_power_ord_eq() { - assert!( - FractionalVotingPower::new(2, 3).unwrap() - > FractionalVotingPower::new(1, 4).unwrap() - ); - assert!( - FractionalVotingPower::new(1, 3).unwrap() - > FractionalVotingPower::new(1, 4).unwrap() - ); - assert!( - FractionalVotingPower::new(1, 3).unwrap() - == FractionalVotingPower::new(2, 6).unwrap() - ); - } - - /// Test error handling on the FractionalVotingPower type - #[test] - fn test_fractional_voting_power_valid_fractions() { - assert!(FractionalVotingPower::new(0, 0).is_err()); - assert!(FractionalVotingPower::new(1, 0).is_err()); - assert!(FractionalVotingPower::new(0, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 2).is_ok()); - assert!(FractionalVotingPower::new(3, 2).is_err()); - } - } -} diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs new file mode 100644 index 0000000000..654ebc088b --- /dev/null +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -0,0 +1,146 @@ +//! Contains types necessary for processing Ethereum events +//! in vote extensions. + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::{eyre, Result}; +use num_rational::Ratio; + +use super::EthereumEvent; +use crate::proto::MultiSigned; +use crate::types::address::Address; +use crate::types::storage::BlockHeight; + +/// A fraction of the total voting power. This should always be a reduced +/// fraction that is between zero and one inclusive. +#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +pub struct FractionalVotingPower(Ratio); + +impl FractionalVotingPower { + /// Create a new FractionalVotingPower. It must be between zero and one + /// inclusive. + pub fn new(numer: u64, denom: u64) -> Result { + if denom == 0 { + return Err(eyre!("denominator can't be zero")); + } + let ratio: Ratio = (numer, denom).into(); + if ratio > 1.into() { + return Err(eyre!( + "fractional voting power cannot be greater than one" + )); + } + Ok(Self(ratio)) + } +} + +impl From<&FractionalVotingPower> for (u64, u64) { + fn from(ratio: &FractionalVotingPower) -> Self { + (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) + } +} + +impl BorshSerialize for FractionalVotingPower { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let (numer, denom): (u64, u64) = self.into(); + (numer, denom).serialize(writer) + } +} + +impl BorshDeserialize for FractionalVotingPower { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let (numer, denom): (u64, u64) = + BorshDeserialize::deserialize(buf)?; + Ok(FractionalVotingPower(Ratio::::new(numer, denom))) + } +} + +impl BorshSchema for FractionalVotingPower { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + u64::declaration(), + u64::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "FractionalVotingPower".into() + } +} + +/// This is created by the block proposer based on the Ethereum events +/// included in the vote extensions of the previous Tendermint round +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedEthEvent { + /// Address and voting power of the signing validators + pub signers: Vec<(Address, FractionalVotingPower)>, + /// Events as signed by validators + pub event: MultiSigned<(EthereumEvent, BlockHeight)>, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::ethereum_events::Uint; + use crate::types::hash::Hash; + + /// Test the hashing of an Ethereum event + #[test] + fn test_ethereum_event_hash() { + let nonce = Uint::from(123u64); + let event = EthereumEvent::TransfersToNamada { + nonce, + transfers: vec![], + }; + let hash = event.hash().unwrap(); + + assert_eq!( + hash, + Hash([ + 94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, + 236, 209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, + 217, 204, 80, 38, 224, 23 + ]) + ); + } + + /// This test is ultimately just exercising the underlying + /// library we use for fractions, we want to make sure + /// operators work as expected with our FractionalVotingPower + /// type itself + #[test] + fn test_fractional_voting_power_ord_eq() { + assert!( + FractionalVotingPower::new(2, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + == FractionalVotingPower::new(2, 6).unwrap() + ); + } + + /// Test error handling on the FractionalVotingPower type + #[test] + fn test_fractional_voting_power_valid_fractions() { + assert!(FractionalVotingPower::new(0, 0).is_err()); + assert!(FractionalVotingPower::new(1, 0).is_err()); + assert!(FractionalVotingPower::new(0, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 2).is_ok()); + assert!(FractionalVotingPower::new(3, 2).is_err()); + } +} From 73b5a0a62f2e0c8dfb2755497c8eb7080b4a552b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 14:32:37 +0100 Subject: [PATCH 0080/1995] Add VoteExtension type --- shared/src/types/ethereum_events/vote_extensions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 654ebc088b..7ee758350b 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -10,6 +10,16 @@ use crate::proto::MultiSigned; use crate::types::address::Address; use crate::types::storage::BlockHeight; +/// This struct will be created and signed over by each +/// validator as their vote extension. +pub struct VoteExtension { + /// The current height of Anoma. + pub block_height: BlockHeight, + /// The new ethereum events seen. These should be + /// deterministically ordered. + pub ethereum_events: Vec +} + /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] From 106438fc521f7f9f96d59d7b3adf3cac36a525ed Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 14:37:43 +0100 Subject: [PATCH 0081/1995] Mock impl for VoteExtension --- shared/src/types/ethereum_events/vote_extensions.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 7ee758350b..c353622f7d 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -20,6 +20,18 @@ pub struct VoteExtension { pub ethereum_events: Vec } +impl VoteExtension { + /// Order `ethereum_events` deterministically and wrap them + /// up in this `VoteExtension` instance, along with the block height + /// they were observed at. + pub fn from_ethereum_events( + _ethereum_events: Vec, + _block_height: BlockHeight + ) -> Self { + todo!() + } +} + /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] From 8eb2d9c99427fd4406fd043450d78ae0a127da88 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 15:11:19 +0100 Subject: [PATCH 0082/1995] WIP: VoteExtensionDigest --- .../types/ethereum_events/vote_extensions.rs | 75 +++++++++++++++++-- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index c353622f7d..f4afae786b 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -1,6 +1,8 @@ //! Contains types necessary for processing Ethereum events //! in vote extensions. +use std::collections::HashSet; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use num_rational::Ratio; @@ -8,7 +10,12 @@ use num_rational::Ratio; use super::EthereumEvent; use crate::proto::MultiSigned; use crate::types::address::Address; +use crate::types::key::common::Signature; use crate::types::storage::BlockHeight; +use crate::proto::types::Signed; +use crate::ledger::storage::{ + DB, DBIter, Storage, StorageHasher, +}; /// This struct will be created and signed over by each /// validator as their vote extension. @@ -99,14 +106,68 @@ impl BorshSchema for FractionalVotingPower { } } -/// This is created by the block proposer based on the Ethereum events -/// included in the vote extensions of the previous Tendermint round -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +/// Aggregates an Ethereum event with the corresponding +// validators who saw this event. +#[derive(BorshSerialize, BorshDeserialize)] pub struct MultiSignedEthEvent { - /// Address and voting power of the signing validators - pub signers: Vec<(Address, FractionalVotingPower)>, - /// Events as signed by validators - pub event: MultiSigned<(EthereumEvent, BlockHeight)>, + /// The Ethereum event that was signed. + pub event: EthereumEvent, + /// List of addresses of validators who signed this event + pub signers: HashSet
, +} + +/// Compresses a set of signed `VoteExtension` instances, to save +/// space on a block. +#[derive(BorshSerialize, BorshDeserialize)] +pub struct VoteExtensionDigest { + /// The signatures and signing address of each VoteExtension + pub signatures: Vec<(Signature, Address)>, + /// The events that were reported + pub events: Vec, + /// The validators who saw no events + pub nulls: HashSet
+} + +impl VoteExtensionDigest { + /// Decompresses a set of signed `VoteExtension` instances. + pub fn decompress(self, storage: &Storage) -> Vec> + where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, + { + let VoteExtensionDigest { + signatures, + events, + nulls, + } = digest; + + let mut extensions = vec![]; + + for (sig, addr) in signatures.into_iter() { + let mut ext = VoteExtension { + block_height: self.storage.last_height, + events: vec![], + }; + + let ext = if nulls.contains(&addr) { + ext + } else { + for event in events { + if event.signers.contains(&addr) { + ext.events.push(event.clone()); + } + } + ext.events.sort(); + }; + + let signed = Signed { + data: ext, + sig, + }; + extensions.push(sig); + } + extensions + } } #[cfg(test)] From a540d5707968a7377bb110a2c0afb34e0522fa8c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 15:24:31 +0100 Subject: [PATCH 0083/1995] Remove MultiSigned --- shared/src/proto/mod.rs | 2 +- shared/src/proto/types.rs | 121 -------------------------------------- 2 files changed, 1 insertion(+), 122 deletions(-) diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index adacda806e..46b0027d36 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -4,7 +4,7 @@ pub mod generated; mod types; pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, MultiSigned, Signed, + Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, }; diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 9c77e267ec..ace163e99f 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -144,127 +144,6 @@ where } } -/// A generic mulit-signed data wrapper for Borsh encode-able data. -#[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub struct MultiSigned { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sigs: Vec, -} - -impl PartialEq for MultiSigned -where - T: BorshSerialize + BorshDeserialize + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.data == other.data && self.sigs == other.sigs - } -} - -impl Eq for MultiSigned where - T: BorshSerialize + BorshDeserialize + Eq + PartialEq -{ -} - -impl Hash for MultiSigned -where - T: BorshSerialize + BorshDeserialize + Hash, -{ - fn hash(&self, state: &mut H) { - self.data.hash(state); - self.sigs.hash(state); - } -} - -impl PartialOrd for MultiSigned -where - T: BorshSerialize + BorshDeserialize + PartialOrd, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.data.partial_cmp(&other.data) - } -} - -impl From> for MultiSigned -where - T: BorshSerialize + BorshDeserialize, -{ - fn from(Signed:: { data, sig }: Signed) -> Self { - Self { - data, - sigs: vec![sig], - } - } -} - -impl BorshSchema for MultiSigned -where - T: BorshSerialize + BorshDeserialize + BorshSchema, -{ - fn add_definitions_recursively( - definitions: &mut HashMap, - ) { - let fields = borsh::schema::Fields::NamedFields(borsh::maybestd::vec![ - ("data".to_string(), T::declaration()), - ("sigs".to_string(), >::declaration()) - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - T::add_definitions_recursively(definitions); - >::add_definitions_recursively(definitions); - } - - fn declaration() -> borsh::schema::Declaration { - format!("MultiSigned<{}>", T::declaration()) - } -} - -impl MultiSigned -where - T: BorshSerialize + BorshDeserialize, -{ - /// Initialize a new multi-signed data. - pub fn new(keypair: &common::SecretKey, data: T) -> Self { - let to_sign = data - .try_to_vec() - .expect("Encoding data for signing shouldn't fail"); - let sigs = vec![common::SigScheme::sign(keypair, &to_sign)]; - Self { data, sigs } - } - - /// Verify that the data has been signed by the secret key - /// counterpart of the given public keys. **Public keys and - /// signatures must be in same order** - pub fn verify( - &self, - pks: &[common::PublicKey], - ) -> std::result::Result<(), VerifySigError> { - let bytes = self - .data - .try_to_vec() - .expect("Encoding data for verifying signature shouldn't fail"); - if pks.len() != self.sigs.len() { - return Err(VerifySigError::InsufficientKeys); - } - for (pk, sig) in pks.iter().zip(&self.sigs) { - common::SigScheme::verify_signature_raw(pk, &bytes, sig)?; - } - Ok(()) - } - - /// Add a new signature to the data. - pub fn add_sig(&mut self, keypair: &common::SecretKey) { - let to_sign = self - .data - .try_to_vec() - .expect("Encoding data for signing shouldn't fail"); - self.sigs.push(common::SigScheme::sign(keypair, &to_sign)); - } -} - #[derive( Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, )] From f709e3868046874f182f64c3f17e9d27be756bec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 15:58:08 +0100 Subject: [PATCH 0084/1995] WIP --- .../types/ethereum_events/vote_extensions.rs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index f4afae786b..98fab93974 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -8,17 +8,14 @@ use eyre::{eyre, Result}; use num_rational::Ratio; use super::EthereumEvent; -use crate::proto::MultiSigned; +use crate::proto::Signed; use crate::types::address::Address; use crate::types::key::common::Signature; use crate::types::storage::BlockHeight; -use crate::proto::types::Signed; -use crate::ledger::storage::{ - DB, DBIter, Storage, StorageHasher, -}; /// This struct will be created and signed over by each /// validator as their vote extension. +#[derive(BorshSerialize, BorshDeserialize)] pub struct VoteExtension { /// The current height of Anoma. pub block_height: BlockHeight, @@ -28,14 +25,12 @@ pub struct VoteExtension { } impl VoteExtension { - /// Order `ethereum_events` deterministically and wrap them - /// up in this `VoteExtension` instance, along with the block height - /// they were observed at. - pub fn from_ethereum_events( - _ethereum_events: Vec, - _block_height: BlockHeight - ) -> Self { - todo!() + /// Creates a [`VoteExtension`] without any Ethereum events. + pub fn empty(block_height: BlockHeight) -> Self { + Self { + block_height, + ethereum_events: Vec::new(), + } } } @@ -108,7 +103,7 @@ impl BorshSchema for FractionalVotingPower { /// Aggregates an Ethereum event with the corresponding // validators who saw this event. -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedEthEvent { /// The Ethereum event that was signed. pub event: EthereumEvent, @@ -130,35 +125,27 @@ pub struct VoteExtensionDigest { impl VoteExtensionDigest { /// Decompresses a set of signed `VoteExtension` instances. - pub fn decompress(self, storage: &Storage) -> Vec> - where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, - { + pub fn decompress(self, last_height: BlockHeight) -> Vec> { let VoteExtensionDigest { signatures, events, nulls, - } = digest; + } = self; let mut extensions = vec![]; for (sig, addr) in signatures.into_iter() { - let mut ext = VoteExtension { - block_height: self.storage.last_height, - events: vec![], - }; + let mut ext = VoteExtension::empty(last_height); - let ext = if nulls.contains(&addr) { - ext - } else { + if !nulls.contains(&addr) { for event in events { if event.signers.contains(&addr) { - ext.events.push(event.clone()); + ext.ethereum_events.push(event.clone()); } } - ext.events.sort(); - }; + // TODO: we need to implement `Ord` for `EthereumEvent` + //ext.ethereum_events.sort(); + } let signed = Signed { data: ext, From 0150d6a25a0c438bb0be20bb3b6b25090a857199 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 15 Jul 2022 16:04:11 +0100 Subject: [PATCH 0085/1995] WIP --- shared/src/proto/mod.rs | 3 +- .../types/ethereum_events/vote_extensions.rs | 35 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index 46b0027d36..aa971f0b96 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -4,8 +4,7 @@ pub mod generated; mod types; pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, - SignedTxData, Tx, + Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, }; #[cfg(test)] diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 98fab93974..95999fd83e 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -21,7 +21,7 @@ pub struct VoteExtension { pub block_height: BlockHeight, /// The new ethereum events seen. These should be /// deterministically ordered. - pub ethereum_events: Vec + pub ethereum_events: Vec, } impl VoteExtension { @@ -74,8 +74,7 @@ impl BorshSerialize for FractionalVotingPower { impl BorshDeserialize for FractionalVotingPower { fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let (numer, denom): (u64, u64) = - BorshDeserialize::deserialize(buf)?; + let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; Ok(FractionalVotingPower(Ratio::::new(numer, denom))) } } @@ -103,7 +102,7 @@ impl BorshSchema for FractionalVotingPower { /// Aggregates an Ethereum event with the corresponding // validators who saw this event. -#[derive(Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedEthEvent { /// The Ethereum event that was signed. pub event: EthereumEvent, @@ -113,19 +112,22 @@ pub struct MultiSignedEthEvent { /// Compresses a set of signed `VoteExtension` instances, to save /// space on a block. -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct VoteExtensionDigest { /// The signatures and signing address of each VoteExtension pub signatures: Vec<(Signature, Address)>, /// The events that were reported pub events: Vec, /// The validators who saw no events - pub nulls: HashSet
+ pub nulls: HashSet
, } impl VoteExtensionDigest { /// Decompresses a set of signed `VoteExtension` instances. - pub fn decompress(self, last_height: BlockHeight) -> Vec> { + pub fn decompress( + self, + last_height: BlockHeight, + ) -> Vec> { let VoteExtensionDigest { signatures, events, @@ -138,20 +140,17 @@ impl VoteExtensionDigest { let mut ext = VoteExtension::empty(last_height); if !nulls.contains(&addr) { - for event in events { + for event in events.iter() { if event.signers.contains(&addr) { - ext.ethereum_events.push(event.clone()); + ext.ethereum_events.push(event.event.clone()); } } // TODO: we need to implement `Ord` for `EthereumEvent` - //ext.ethereum_events.sort(); + // ext.ethereum_events.sort(); } - let signed = Signed { - data: ext, - sig, - }; - extensions.push(sig); + let signed = Signed { data: ext, sig }; + extensions.push(signed); } extensions } @@ -176,9 +175,9 @@ mod tests { assert_eq!( hash, Hash([ - 94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, - 236, 209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, - 217, 204, 80, 38, 224, 23 + 94, 131, 116, 129, 41, 204, 178, 144, 24, 8, 185, 16, 103, 236, + 209, 191, 20, 89, 145, 17, 41, 233, 31, 98, 185, 6, 217, 204, + 80, 38, 224, 23 ]) ); } From cbe294268d1cd38908689f59c1e8d60e07e45452 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 15 Jul 2022 16:15:27 +0100 Subject: [PATCH 0086/1995] Finalize types for eth bridge integration --- shared/src/types/ethereum_events.rs | 72 +++++++++++++++++-- .../types/ethereum_events/vote_extensions.rs | 7 +- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 2ea93d5f17..c7e1930b5c 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -11,7 +11,15 @@ use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct Uint(pub [u64; 4]); @@ -35,18 +43,44 @@ impl From for Uint { /// Representation of address on Ethereum #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct EthAddress(pub [u8; 20]); /// A Keccak hash #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct KeccakHash(pub [u8; 32]); /// An Ethereum event to be processed by the Anoma ledger -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] pub enum EthereumEvent { /// Event transferring batches of ether or Ethereum based ERC20 tokens /// from Ethereum to wrapped assets on Anoma @@ -123,7 +157,15 @@ impl EthereumEvent { /// An event transferring some kind of value from Ethereum to Anoma #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct TransferToNamada { /// Quantity of the ERC20 token in the transfer @@ -136,7 +178,15 @@ pub struct TransferToNamada { /// An event transferring some kind of value from Anoma to Ethereum #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] pub struct TransferToEthereum { /// Quantity of wrapped Asset in the transfer @@ -152,7 +202,15 @@ pub struct TransferToEthereum { /// a cap on the max amount of this token allowed to be /// held by the bridge. #[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, )] #[allow(dead_code)] pub struct TokenWhitelist { diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 95999fd83e..e622a9e396 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -145,8 +145,11 @@ impl VoteExtensionDigest { ext.ethereum_events.push(event.event.clone()); } } - // TODO: we need to implement `Ord` for `EthereumEvent` - // ext.ethereum_events.sort(); + // TODO: we probably need a manual `Ord` impl for + // `EthereumEvent`, such that this `sort()` is + // always deterministic, regardless + // of crate versions changing and such + ext.ethereum_events.sort(); } let signed = Signed { data: ext, sig }; From 35b883b7c47478c35c0178127f9aa869fb423302 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 18 Jul 2022 12:52:13 +0100 Subject: [PATCH 0087/1995] Unit test for VoteExtensionDigest::decompress --- .../types/ethereum_events/vote_extensions.rs | 83 ++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index e622a9e396..3c1064564b 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -15,7 +15,7 @@ use crate::types::storage::BlockHeight; /// This struct will be created and signed over by each /// validator as their vote extension. -#[derive(BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct VoteExtension { /// The current height of Anoma. pub block_height: BlockHeight, @@ -124,7 +124,7 @@ pub struct VoteExtensionDigest { impl VoteExtensionDigest { /// Decompresses a set of signed `VoteExtension` instances. - pub fn decompress( + pub fn decompress( self, last_height: BlockHeight, ) -> Vec> { @@ -161,9 +161,17 @@ impl VoteExtensionDigest { #[cfg(test)] mod tests { + use std::collections::HashSet; + + use super::super::EthereumEvent; use super::*; + use crate::proto::Signed; + use crate::types::address::Address; use crate::types::ethereum_events::Uint; use crate::types::hash::Hash; + use crate::types::key; + use crate::types::key::RefTo; + use crate::types::storage::BlockHeight; /// Test the hashing of an Ethereum event #[test] @@ -215,4 +223,75 @@ mod tests { assert!(FractionalVotingPower::new(1, 2).is_ok()); assert!(FractionalVotingPower::new(3, 2).is_err()); } + + /// Test decompression of a set of Ethereum events + #[test] + fn test_decompress_ethereum_events() { + // we need to construct a `Vec>` + let sk_1 = key::testing::keypair_1(); + let sk_2 = key::testing::keypair_2(); + + let last_block_height = BlockHeight(123); + + let ev_1 = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ev_2 = EthereumEvent::TransfersToEthereum { + nonce: 2u64.into(), + transfers: vec![], + }; + + let ext = { + let mut ext = VoteExtension::empty(last_block_height); + + ext.ethereum_events.push(ev_1.clone()); + ext.ethereum_events.push(ev_2.clone()); + ext.ethereum_events.sort(); + + ext + }; + + // assume both v1 and v2 saw the same events, + // so each of them signs `ext` with their respective sk + let ext_1 = Signed::new(&sk_1, ext.clone()); + let ext_2 = Signed::new(&sk_2, ext); + + let ext = vec![ext_1, ext_2]; + + // we have the `Signed` instances we need, + // let us now compress them into a single `VoteExtensionDigest` + let signatures: Vec<(_, Address)> = vec![ + (ext[0].sig.clone(), (&sk_1.ref_to()).into()), + (ext[1].sig.clone(), (&sk_2.ref_to()).into()), + ]; + let signers = { + let mut s = HashSet::new(); + s.insert(signatures[0].1.clone()); + s.insert(signatures[1].1.clone()); + s + }; + let events = vec![ + MultiSignedEthEvent { + event: ev_1.clone(), + signers: signers.clone(), + }, + MultiSignedEthEvent { + event: ev_2.clone(), + signers, + }, + ]; + + let digest = VoteExtensionDigest { + events, + signatures, + nulls: HashSet::new(), + }; + + // finally, decompress the `VoteExtensionDigest` back into a + // `Vec>` + let decompressed = digest.decompress(last_block_height); + + assert_eq!(ext, decompressed); + } } From 49988834bc943e178d9c61a8ffdfcea62476a45e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 18 Jul 2022 13:30:47 +0100 Subject: [PATCH 0088/1995] Add a TODO on decompress --- shared/src/types/ethereum_events/vote_extensions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 3c1064564b..87d30de3cd 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -139,6 +139,8 @@ impl VoteExtensionDigest { for (sig, addr) in signatures.into_iter() { let mut ext = VoteExtension::empty(last_height); + // TODO: perhaps remove the `nulls` field, + // as this code will behave much the same without it if !nulls.contains(&addr) { for event in events.iter() { if event.signers.contains(&addr) { From 2aae06953fbc23a19cefd46426b2f1b69f37ab3d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 18 Jul 2022 14:04:00 +0100 Subject: [PATCH 0089/1995] Protocol txs now use VoteExtensionDigest --- shared/src/types/ethereum_events/vote_extensions.rs | 2 +- shared/src/types/transaction/protocol.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 87d30de3cd..006e3c3620 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -112,7 +112,7 @@ pub struct MultiSignedEthEvent { /// Compresses a set of signed `VoteExtension` instances, to save /// space on a block. -#[derive(Debug, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct VoteExtensionDigest { /// The signatures and signing address of each VoteExtension pub signatures: Vec<(Signature, Address)>, diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index 2e9be6746e..05b8bec010 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -33,7 +33,7 @@ mod protocol_txs { use super::*; use crate::proto::Tx; - use crate::types::ethereum_events::vote_extensions::MultiSignedEthEvent; + use crate::types::ethereum_events::vote_extensions::VoteExtensionDigest; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; @@ -78,7 +78,7 @@ mod protocol_txs { /// Tx requesting a new DKG session keypair NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions - EthereumEvents(Vec), + EthereumEvents(VoteExtensionDigest), } impl ProtocolTxType { From 573d7f2951927e7b3c802674fb7c9c5d524452bf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 08:58:21 +0100 Subject: [PATCH 0090/1995] Update VoteExtension docs Co-authored-by: James --- shared/src/types/ethereum_events/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 006e3c3620..45f3b998fd 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -17,7 +17,7 @@ use crate::types::storage::BlockHeight; /// validator as their vote extension. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct VoteExtension { - /// The current height of Anoma. + /// The block height for which this [`VoteExtension`] was made. pub block_height: BlockHeight, /// The new ethereum events seen. These should be /// deterministically ordered. From 9e7c7129db56e071de7ff0c53effce40b3e7f753 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 12:51:34 +0100 Subject: [PATCH 0091/1995] Remove nulls from VoteExtensionDigest --- .../types/ethereum_events/vote_extensions.rs | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 45f3b998fd..dc004fdab7 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -118,8 +118,6 @@ pub struct VoteExtensionDigest { pub signatures: Vec<(Signature, Address)>, /// The events that were reported pub events: Vec, - /// The validators who saw no events - pub nulls: HashSet
, } impl VoteExtensionDigest { @@ -131,7 +129,6 @@ impl VoteExtensionDigest { let VoteExtensionDigest { signatures, events, - nulls, } = self; let mut extensions = vec![]; @@ -139,21 +136,18 @@ impl VoteExtensionDigest { for (sig, addr) in signatures.into_iter() { let mut ext = VoteExtension::empty(last_height); - // TODO: perhaps remove the `nulls` field, - // as this code will behave much the same without it - if !nulls.contains(&addr) { - for event in events.iter() { - if event.signers.contains(&addr) { - ext.ethereum_events.push(event.event.clone()); - } + for event in events.iter() { + if event.signers.contains(&addr) { + ext.ethereum_events.push(event.event.clone()); } - // TODO: we probably need a manual `Ord` impl for - // `EthereumEvent`, such that this `sort()` is - // always deterministic, regardless - // of crate versions changing and such - ext.ethereum_events.sort(); } + // TODO: we probably need a manual `Ord` impl for + // `EthereumEvent`, such that this `sort()` is + // always deterministic, regardless + // of crate versions changing and such + ext.ethereum_events.sort(); + let signed = Signed { data: ext, sig }; extensions.push(signed); } @@ -287,7 +281,6 @@ mod tests { let digest = VoteExtensionDigest { events, signatures, - nulls: HashSet::new(), }; // finally, decompress the `VoteExtensionDigest` back into a From 078a5e86dd7dd89989a6eb34a7a64831ae25f9ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 12:54:56 +0100 Subject: [PATCH 0092/1995] Verify signatures from decompressed digest --- shared/src/types/ethereum_events/vote_extensions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index dc004fdab7..86fc32a17e 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -288,5 +288,7 @@ mod tests { let decompressed = digest.decompress(last_block_height); assert_eq!(ext, decompressed); + assert!(decompressed[0].verify(&sk_1.ref_to()).is_ok()); + assert!(decompressed[1].verify(&sk_2.ref_to()).is_ok()); } } From 1dbff541ebe14d3b937401c78e55df638e6e5a1c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 00:48:24 +0200 Subject: [PATCH 0093/1995] [feat]: Added in vote extensions that include ethereum events. --- apps/src/lib/node/ledger/shell/mod.rs | 23 +- apps/src/lib/node/ledger/shell/queries.rs | 86 +++- .../lib/node/ledger/shell/vote_extensions.rs | 469 ++++++++++++++++++ 3 files changed, 555 insertions(+), 23 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/vote_extensions.rs diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e5867fddb4..52c443fe06 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 vote_extensions; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; @@ -32,6 +33,8 @@ use anoma::ledger::storage::{ use anoma::ledger::{ibc, parameters, pos}; use anoma::proto::{self, Tx}; use anoma::types::chain::ChainId; +#[cfg(not(feature = "ABCI"))] +use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; use anoma::types::ethereum_events::EthereumEvent; use anoma::types::key::*; use anoma::types::storage::{BlockHeight, Key}; @@ -542,26 +545,6 @@ where } } - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn extend_vote( - &self, - _req: request::ExtendVote, - ) -> response::ExtendVote { - Default::default() - } - - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn verify_vote_extension( - &self, - _req: request::VerifyVoteExtension, - ) -> response::VerifyVoteExtension { - response::VerifyVoteExtension { - status: VerifyStatus::Accept as i32, - } - } - /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cb750ce122..bc486eeddc 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,11 +2,13 @@ use std::cmp::max; use anoma::ledger::parameters::EpochDuration; +#[cfg(not(feature = "ABCI"))] +use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; use anoma::ledger::pos::PosParams; use anoma::types::address::Address; use anoma::types::key; use anoma::types::key::dkg_session_keys::DkgPublicKey; -use anoma::types::storage::{Key, PrefixValue}; +use anoma::types::storage::{Epoch, Key, PrefixValue}; use anoma::types::token::{self, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; @@ -299,16 +301,17 @@ where pub fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, + epoch: Option, ) -> Option> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); // get the current epoch - let (current_epoch, _) = self.storage.get_current_epoch(); + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() - .get(current_epoch) + .get(epoch) .expect("Validators for the next epoch should be known") .active .iter() @@ -342,4 +345,81 @@ where } }) } + + /// Lookup data about a validator from their address + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Option<(VotingPower, common::PublicKey)> { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for the next epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .storage + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.voting_power, protocol_pk) + }) + } + + /// Lookup the total voting power for an epoch + #[cfg(not(feature = "ABCI"))] + pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .map(|validators| { + validators + .active + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + }) + .unwrap_or_default() + } + + /// Get the voting power of this node (as a fraction of this epochs total + /// voting power) if it is a validator. Else return None + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_voting_power(&self) -> Option { + let power = if let Some(secret_key) = self.mode.get_protocol_key() { + match self + .get_validator_from_protocol_pk(&secret_key.ref_to(), None) + { + Some(validator) => Some(validator.power), + _ => None, + } + } else { + None + }; + let total = u64::from(self.get_total_voting_power(None)); + match power { + Some(power) if total > 0 => { + FractionalVotingPower::new(power, total).ok() + } + _ => None, + } + } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs new file mode 100644 index 0000000000..e0236052eb --- /dev/null +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -0,0 +1,469 @@ +#[cfg(not(feature = "ABCI"))] +mod extend_votes { + use anoma::types::ethereum_events::vote_extensions::{ + EpochPower, SignedEthEvent, SignedEvent, + }; + + use super::super::*; + + /// The data we include in a vote extension + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] + pub struct VoteExtension { + /// Ethereum events seen since last round + ethereum_events: Vec, + } + + impl Shell + where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, + { + /// INVARIANT: This method must be stateless. + pub fn extend_vote( + &mut self, + _req: request::ExtendVote, + ) -> response::ExtendVote { + response::ExtendVote { + vote_extension: VoteExtension { + ethereum_events: self.new_ethereum_events(), + } + .try_to_vec() + .unwrap(), + } + } + + /// At present this checks the signature on all Ethereum headers + /// + /// INVARIANT: This method must be stateless. + pub fn verify_vote_extension( + &self, + req: request::VerifyVoteExtension, + ) -> response::VerifyVoteExtension { + if let Ok(VoteExtension { ethereum_events }) = + VoteExtension::try_from_slice(&req.vote_extension[..]) + { + return if ethereum_events.iter().all(|event| { + self.validate_ethereum_event( + self.storage.last_height + 1, + event, + ) + }) { + response::VerifyVoteExtension { + status: VerifyStatus::Accept.into(), + } + } else { + response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + } + }; + } + Default::default() + } + + /// Checks the channel from the Ethereum oracle monitoring + /// the fullnode and retrieves all messages sent. These are + /// signed and prepared for inclusion in a vote extension. + pub fn new_ethereum_events(&mut self) -> Vec { + let mut events = vec![]; + let voting_power = self.get_validator_voting_power(); + let address = self.mode.get_validator_address().cloned(); + if let ShellMode::Validator { + ref mut ethereum_recv, + data: + ValidatorData { + keys: + ValidatorKeys { + protocol_keypair, .. + }, + .. + }, + .. + } = &mut self.mode + { + let (voting_power, address) = + voting_power.zip(address).unwrap(); + while let Ok(eth_event) = ethereum_recv.try_recv() { + events.push(SignedEthEvent::new( + eth_event, + address.clone(), + voting_power.clone(), + self.storage.last_height + 1, + protocol_keypair, + )); + } + } + events + } + + /// Verify that each ethereum header in a vote extension was signed by + /// a validator in the correct epoch, the stated voting power is + /// correct, and the signature is correct. + pub fn validate_ethereum_event( + &self, + height: BlockHeight, + event: &impl SignedEvent, + ) -> bool { + let epoch = self.storage.block.pred_epochs.get_epoch(height); + let total_voting_power = self.get_total_voting_power(epoch); + + // Get the public keys of each validator. Filter out those that + // inaccurately stated their voting power at a given block height + let public_keys: Vec = event + .get_voting_powers() + .into_iter() + .filter_map( + |EpochPower { + validator, + voting_power, + block_height, + }| { + if block_height != height { + return None; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + FractionalVotingPower::new( + power, + total_voting_power, + ) + .ok() + .and_then(|power| { + if power == voting_power { + Some(pk) + } else { + None + } + }) + } else { + None + } + }, + ) + .collect(); + // check that we found all the public keys and + // check that the signatures are valid + public_keys.len() == event.number_of_signers() + && event.verify_signatures(&public_keys).is_ok() + } + } + + #[cfg(test)] + mod test_vote_extensions { + use std::convert::TryInto; + + use anoma::ledger::pos; + use anoma::ledger::pos::anoma_proof_of_stake::PosBase; + use anoma::types::ethereum_events::vote_extensions::{ + FractionalVotingPower, MultiSignedEthEvent, SignedEthEvent, + }; + use anoma::types::ethereum_events::{ + EthAddress, EthereumEvent, TransferToEthereum, + }; + use anoma::types::key::*; + use anoma::types::storage::{BlockHeight, Epoch}; + use borsh::{BorshDeserialize, BorshSerialize}; + use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + use tower_abci::request; + + use crate::node::ledger::shell::test_utils::*; + use crate::node::ledger::shell::vote_extensions::VoteExtension; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + + /// Test that we successfully receive ethereum events + /// from the channel to fullnode process + /// + /// We further check that ledger side buffering is done if multiple + /// events are in the channel + #[test] + fn test_get_eth_events() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = shell + .new_ethereum_events() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + } + + /// Test that ethereum events are added to vote extensions. + /// Check that vote extensions pass verification. + #[test] + fn test_eth_events_vote_extension() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let vote_extension: VoteExtension = + BorshDeserialize::try_from_slice( + &shell.extend_vote(Default::default()).vote_extension[..], + ) + .expect("Test failed"); + + let [event_first, event_second]: [EthereumEvent; 2] = + vote_extension + .ethereum_events + .clone() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: vec![], + height: 0, + vote_extension: vote_extension + .try_to_vec() + .expect("Test failed"), + }; + let res = shell.verify_vote_extension(req); + assert_eq!(res.status, i32::from(VerifyStatus::Accept)); + } + + /// Test that Ethereum headers signed by a non-validator is rejected + #[test] + fn test_eth_events_must_be_signed_by_validator() { + let (shell, _, _) = setup(); + let signing_key = gen_keypair(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that validation of vote extensions cast during the + /// previous block are accepted for the current block. This + /// should pass even if the epoch changed resulting in a + /// change to the validator set. + #[test] + fn test_validate_vote_extensions() { + let (mut shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let height = shell.storage.last_height + 1; + + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert_eq!(shell.storage.get_current_epoch().0.0, 0); + // We make a change so that there are no + // validators in the next epoch + let mut current_validators = shell.storage.read_validator_set(); + current_validators.data.insert( + 1, + Some(pos::types::ValidatorSet { + active: Default::default(), + inactive: Default::default(), + }), + ); + shell.storage.write_validator_set(¤t_validators); + // we advance forward to the next epoch + let mut req = FinalizeBlock::default(); + req.header.time = anoma::types::time::DateTimeUtc::now(); + shell.storage.last_height = BlockHeight(11); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + assert_eq!(shell.storage.get_current_epoch().0.0, 1); + assert!( + shell + .get_validator_from_protocol_pk(&signing_key.ref_to(), None) + .is_none() + ); + let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); + assert!( + shell + .shell + .get_validator_from_protocol_pk( + &signing_key.ref_to(), + Some(prev_epoch) + ) + .is_some() + ); + assert!(shell.validate_ethereum_event(height, &signed_event)); + assert!(shell.validate_ethereum_event( + height, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that if the declared voting power is not correct, + /// the signed event is rejected + #[test] + fn reject_incorrect_voting_power() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = 99u64; + let total_voting_power = + u64::from(shell.get_total_voting_power(None)); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + FractionalVotingPower::new(voting_power, total_voting_power) + .expect("Test failed"), + shell.storage.last_height + 1, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event that incorrectly labels what block it was + /// included in a vote extension on is rejected + #[test] + fn reject_incorrect_block_number() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event with an incorrect address + /// included in a vote extension is rejected + #[test] + fn reject_incorrect_address() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + crate::wallet::defaults::bertha_address(), + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + } +} + +#[cfg(not(feature = "ABCI"))] +pub use extend_votes::*; From 0aaab89b7fb118d0a6d6c54576034a6c121a1366 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 11:40:54 +0200 Subject: [PATCH 0094/1995] [cleanup]: Refactored some ugly functional code that was bugging me --- .../lib/node/ledger/shell/vote_extensions.rs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e0236052eb..672781e5a6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -105,42 +105,33 @@ mod extend_votes { ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); let total_voting_power = self.get_total_voting_power(epoch); + if u64::from(total_voting_power) == 0 { + return false; + } // Get the public keys of each validator. Filter out those that // inaccurately stated their voting power at a given block height - let public_keys: Vec = event - .get_voting_powers() - .into_iter() - .filter_map( - |EpochPower { - validator, - voting_power, - block_height, - }| { - if block_height != height { - return None; - } - if let Some((power, pk)) = - self.get_validator_from_address(&validator, epoch) - { - FractionalVotingPower::new( - power, - total_voting_power, - ) - .ok() - .and_then(|power| { - if power == voting_power { - Some(pk) - } else { - None - } - }) - } else { - None - } - }, - ) - .collect(); + let mut public_keys = vec![]; + for EpochPower { + validator, + voting_power, + block_height, + } in event.get_voting_powers().into_iter() + { + if block_height != height { + continue; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + let power = + FractionalVotingPower::new(power, total_voting_power) + .unwrap(); + if power == voting_power { + public_keys.push(pk); + } + } + } // check that we found all the public keys and // check that the signatures are valid public_keys.len() == event.number_of_signers() From f44f4e6c8ff1db8570fc62e2659bfe94ffd979f4 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:28 +0200 Subject: [PATCH 0095/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index bc486eeddc..4ae8ac9010 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -353,7 +353,6 @@ where address: &Address, epoch: Option, ) -> Option<(VotingPower, common::PublicKey)> { - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From 0d3c594ea2415c7774f02e465a07ed13840fa25a Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:35 +0200 Subject: [PATCH 0096/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 4ae8ac9010..7dd310239f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -306,7 +306,6 @@ where let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From 96315851997ef1c6f4a96b28c84177426753598e Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 19 Jul 2022 15:26:32 +0200 Subject: [PATCH 0097/1995] [feat]: Added query method to get anoma native validator address from tendermint address --- apps/src/lib/node/ledger/shell/queries.rs | 95 +++++++++++++++---- .../types/ethereum_events/vote_extensions.rs | 10 +- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 7dd310239f..8335713ad8 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -13,12 +13,16 @@ use anoma::types::token::{self, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; #[cfg(not(feature = "ABCI"))] +use tendermint_proto::abci::Validator; +#[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::abci::Validator; +#[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::{ProofOp, ProofOps}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf; @@ -28,6 +32,31 @@ use tendermint_proto_abci::types::EvidenceParams; use super::*; use crate::node::ledger::response; +#[derive(Error, Debug)] +pub enum Error { + #[error( + "The address '{:?}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorAddress(Address, Epoch), + #[error( + "The public key '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKey(String, Epoch), + #[error( + "The public key hash '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKeyHash(String, Epoch), + #[error("There are currently no staked validators")] + NoStakingValidators, + #[error("This node is not a validator")] + NotValidator, + #[error("Invalid validator tendermint address")] + InvalidTMAddress, +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -302,7 +331,7 @@ where &self, pk: &key::common::PublicKey, epoch: Option, - ) -> Option> { + ) -> std::result::Result, Error> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); @@ -311,7 +340,7 @@ where self.storage .read_validator_set() .get(epoch) - .expect("Validators for the next epoch should be known") + .expect("Validators for an epoch should be known") .active .iter() .find(|validator| { @@ -343,6 +372,7 @@ where public_key: dkg_publickey.into(), } }) + .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) } /// Lookup data about a validator from their address @@ -351,13 +381,13 @@ where &self, address: &Address, epoch: Option, - ) -> Option<(VotingPower, common::PublicKey)> { + ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() .get(epoch) - .expect("Validators for the next epoch should be known") + .expect("Validators for an epoch should be known") .active .iter() .find(|validator| address == &validator.address) @@ -376,6 +406,7 @@ where ); (validator.voting_power, protocol_pk) }) + .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } /// Lookup the total voting power for an epoch @@ -401,23 +432,53 @@ where /// Get the voting power of this node (as a fraction of this epochs total /// voting power) if it is a validator. Else return None #[cfg(not(feature = "ABCI"))] - pub fn get_validator_voting_power(&self) -> Option { + pub fn get_validator_voting_power( + &self, + ) -> std::result::Result { let power = if let Some(secret_key) = self.mode.get_protocol_key() { - match self - .get_validator_from_protocol_pk(&secret_key.ref_to(), None) - { - Some(validator) => Some(validator.power), - _ => None, - } + self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) + .map(|validator| validator.power)? } else { - None + return Err(Error::NotValidator); }; let total = u64::from(self.get_total_voting_power(None)); - match power { - Some(power) if total > 0 => { - FractionalVotingPower::new(power, total).ok() - } - _ => None, + if total > 0 { + Ok(FractionalVotingPower::new(power, total).unwrap()) + } else { + Err(Error::NoStakingValidators) + } + } + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. Returns an error + /// if no matching validator is found in the active validator + /// set. + pub fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + ) -> std::result::Result { + let epoch = self.storage.get_current_epoch().0; + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| Error::InvalidTMAddress)?; + let address = self.storage + .read_validator_address_raw_hash(&validator_raw_hash) + .ok_or_else(|| Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch + ))?; + if self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .is_some() + { + Ok(address) + } else { + Err(Error::NotValidatorAddress(address, epoch)) } } } diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 86fc32a17e..9c9f207f0c 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -126,10 +126,7 @@ impl VoteExtensionDigest { self, last_height: BlockHeight, ) -> Vec> { - let VoteExtensionDigest { - signatures, - events, - } = self; + let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -278,10 +275,7 @@ mod tests { }, ]; - let digest = VoteExtensionDigest { - events, - signatures, - }; + let digest = VoteExtensionDigest { events, signatures }; // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` From b36e14fb05113a1ec140075684a16cfb52679b0e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 19 Jul 2022 14:56:35 +0100 Subject: [PATCH 0098/1995] Fixes/formatting to the previous merge commit --- Cargo.lock | 711 +++++++++++++++--- .../lib/node/ledger/ethereum_node/events.rs | 12 +- .../lib/node/ledger/ethereum_node/oracle.rs | 4 +- .../node/ledger/ethereum_node/test_tools.rs | 2 +- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- documentation/dev/.gitignore | 1 - documentation/dev/Makefile | 16 - documentation/dev/book.toml | 6 - documentation/dev/src/SUMMARY.md | 3 - documentation/dev/src/chapter_1.md | 1 - documentation/docs/src/chapter_1.md | 1 - documentation/spec/.gitignore | 1 - documentation/spec/Makefile | 16 - documentation/spec/book.toml | 6 - documentation/spec/src/SUMMARY.md | 3 - documentation/spec/src/chapter_1.md | 1 - .../types/ethereum_events/vote_extensions.rs | 10 +- wasm/tx_template/Cargo.lock | 288 ++++++- wasm/vp_template/Cargo.lock | 288 ++++++- wasm/wasm_source/Cargo.lock | 288 ++++++- wasm_for_tests/wasm_source/Cargo.lock | 288 ++++++- 23 files changed, 1725 insertions(+), 227 deletions(-) delete mode 100644 documentation/dev/.gitignore delete mode 100644 documentation/dev/Makefile delete mode 100644 documentation/dev/book.toml delete mode 100644 documentation/dev/src/SUMMARY.md delete mode 100644 documentation/dev/src/chapter_1.md delete mode 100644 documentation/docs/src/chapter_1.md delete mode 100644 documentation/spec/.gitignore delete mode 100644 documentation/spec/Makefile delete mode 100644 documentation/spec/book.toml delete mode 100644 documentation/spec/src/SUMMARY.md delete mode 100644 documentation/spec/src/chapter_1.md diff --git a/Cargo.lock b/Cargo.lock index 1b962cedbf..43f8c7bb31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes 1.1.0", + "futures-core", + "futures-sink", + "log 0.4.17", + "memchr", + "pin-project-lite 0.2.9", + "tokio", + "tokio-util 0.7.3", +] + +[[package]] +name = "actix-http" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.13.0", + "bitflags", + "bytes 1.1.0", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags 0.3.2", + "local-channel", + "log 0.4.17", + "mime 0.3.16", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", + "rand 0.8.5", + "sha-1 0.10.0", + "smallvec 1.8.0", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite 0.2.9", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log 0.4.17", + "openssl", + "pin-project-lite 0.2.9", + "tokio-openssl", + "tokio-util 0.7.3", +] + +[[package]] +name = "actix-utils" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" +dependencies = [ + "local-waker", + "pin-project-lite 0.2.9", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -135,7 +238,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "paste", "rustc_version 0.3.3", @@ -158,7 +261,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "quote", "syn", @@ -504,6 +607,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c60c44fbf3c8cee365e86b97d706e513b733c4eeb16437b45b88d2fffe889a" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash", + "base64 0.13.0", + "bytes 1.1.0", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log 0.4.17", + "mime 0.3.16", + "openssl", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", + "rand 0.8.5", + "serde 1.0.137", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.65" @@ -605,6 +742,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.9.2" @@ -784,6 +933,12 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "byte-tools" version = "0.3.1" @@ -848,6 +1003,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "bytestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" +dependencies = [ + "bytes 1.1.0", +] + [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -1016,6 +1180,24 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clarity" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" +dependencies = [ + "lazy_static 1.4.0", + "num-bigint 0.4.3", + "num-traits 0.2.15", + "num256", + "secp256k1", + "serde 1.0.137", + "serde-rlp", + "serde_bytes", + "serde_derive", + "sha3 0.10.1", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1107,6 +1289,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1523,8 +1711,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn", ] @@ -1768,6 +1958,16 @@ dependencies = [ "log 0.4.17", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +dependencies = [ + "traitobject", + "typeable", +] + [[package]] name = "escargot" version = "0.5.7" @@ -1780,6 +1980,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde 1.0.137", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -1854,7 +2098,7 @@ dependencies = [ "itertools 0.10.3", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde 1.0.137", @@ -1913,6 +2157,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.2.0" @@ -2024,6 +2280,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2189,10 +2451,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]] @@ -2498,7 +2758,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags", + "language-tags 0.2.2", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -2598,12 +2858,12 @@ 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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" 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)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=murisi/jsfix)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2614,10 +2874,10 @@ dependencies = [ "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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.9", "tracing 0.1.35", ] @@ -2652,13 +2912,13 @@ 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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" 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)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tonic", ] @@ -2687,7 +2947,7 @@ dependencies = [ "prost 0.9.0", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2756,6 +3016,44 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde 1.0.137", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2931,6 +3229,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "0.2.11" @@ -3272,7 +3576,7 @@ dependencies = [ "pin-project 1.0.10", "rand 0.7.3", "salsa20", - "sha3", + "sha3 0.9.1", ] [[package]] @@ -3476,6 +3780,24 @@ dependencies = [ "serde_test", ] +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.3.4" @@ -3886,18 +4208,21 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", "itertools 0.10.3", "loupe", "namada_proof_of_stake", + "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", @@ -3912,9 +4237,9 @@ 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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -3947,12 +4272,14 @@ dependencies = [ "byteorder", "cargo-watch", "clap 3.0.0-beta.2", + "clarity", "color-eyre", "config", "curl", "derivative", "directories", "ed25519-consensus", + "ethabi", "eyre", "ferveo", "ferveo-common", @@ -3970,6 +4297,7 @@ dependencies = [ "namada", "num-derive", "num-traits 0.2.15", + "num256", "num_cpus", "once_cell", "orion", @@ -3995,13 +4323,13 @@ 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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "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?branch=murisi/jsfix)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -4011,11 +4339,12 @@ 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?branch=murisi/jsfix)", "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", + "web30", "websocket", "winapi 0.3.9", ] @@ -4075,10 +4404,8 @@ 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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "test-log", "toml", "tracing 0.1.35", @@ -4244,17 +4571,42 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits 0.2.15", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.4.3", + "num-complex 0.4.1", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", + "num-traits 0.2.15", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.1.0", + "num-integer", "num-traits 0.2.15", ] @@ -4267,6 +4619,17 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", + "serde 1.0.137", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.1.0", + "num-traits 0.2.15", ] [[package]] @@ -4312,12 +4675,24 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg 1.1.0", - "num-bigint", + "num-bigint 0.2.6", + "num-integer", + "num-traits 0.2.15", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg 1.1.0", + "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", ] @@ -4340,6 +4715,20 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static 1.4.0", + "num 0.4.0", + "num-derive", + "num-traits 0.2.15", + "serde 1.0.137", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -4490,6 +4879,32 @@ dependencies = [ "url 2.2.2", ] +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.137", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-send-wrapper" version = "0.1.0" @@ -4813,6 +5228,19 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -5060,6 +5488,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -5487,6 +5921,16 @@ dependencies = [ "libc", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes 1.1.0", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.18.0" @@ -5536,6 +5980,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -5726,6 +6176,24 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.3.1" @@ -5815,6 +6283,18 @@ dependencies = [ "serde 0.8.23", ] +[[package]] +name = "serde-rlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" +dependencies = [ + "byteorder", + "error", + "num 0.2.1", + "serde 1.0.137", +] + [[package]] name = "serde_bytes" version = "0.11.6" @@ -5985,6 +6465,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -6273,6 +6763,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.38" @@ -6307,7 +6803,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6327,35 +6823,7 @@ dependencies = [ "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)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.9", "zeroize", ] @@ -6391,12 +6859,12 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" 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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "toml", "url 2.2.2", ] @@ -6417,13 +6885,13 @@ 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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" 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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "time 0.3.9", ] @@ -6443,24 +6911,7 @@ dependencies = [ [[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" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes 1.1.0", "flex-error", @@ -6494,7 +6945,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "async-tungstenite", @@ -6512,9 +6963,9 @@ dependencies = [ "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)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "thiserror", "time 0.3.9", "tokio", @@ -6560,7 +7011,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -6568,7 +7019,7 @@ dependencies = [ "serde_json", "simple-error", "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?branch=murisi/jsfix)", "time 0.3.9", ] @@ -6722,6 +7173,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny_http" version = "0.11.0" @@ -6833,6 +7293,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -7026,13 +7498,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing#73e43bf79fb21b4969cc09f79a0a40ce4cc7bb52" +source = "git+https://github.com/heliaxdev/tower-abci?branch=murisi/jsfix#09de960c0e67491a65094608d0679b2ecc29e7c9" 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)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=murisi/jsfix)", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7990,6 +8462,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a1cd88f65c3315ffda8722ee2de20ae5e9c607ebd009377fbfd2ea68375af3" +dependencies = [ + "awc", + "clarity", + "futures 0.3.21", + "lazy_static 1.4.0", + "log 0.4.17", + "num 0.4.0", + "num256", + "serde 1.0.137", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -8233,6 +8724,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "1.2.0" @@ -8297,6 +8797,25 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zstd" +version = "0.10.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.6+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +dependencies = [ + "libc", + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "1.6.3+zstd.1.5.2" diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index aa87f82b77..03e88665e9 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -44,17 +44,17 @@ pub mod eth_events { use std::fmt::Debug; use std::str::FromStr; - use anoma::types::address::Address; - use anoma::types::ethereum_events::{ - EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, - TransferToEthereum, TransferToNamada, Uint, - }; - use anoma::types::token::Amount; use ethabi::decode; #[cfg(test)] use ethabi::encode; use ethabi::param_type::ParamType; use ethabi::token::Token; + use namada::types::address::Address; + use namada::types::ethereum_events::{ + EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, + TransferToEthereum, TransferToNamada, Uint, + }; + use namada::types::token::Amount; use num256::Uint256; use thiserror::Error; diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index fd4c6c8569..bbd28e7adb 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -2,8 +2,8 @@ pub mod oracle_process { use std::ops::Deref; - use anoma::types::ethereum_events::{EthAddress, EthereumEvent}; use clarity::Address; + use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; @@ -211,7 +211,7 @@ pub mod oracle_process { #[cfg(test)] mod test_oracle { - use anoma::types::ethereum_events::TransferToEthereum; + use namada::types::ethereum_events::TransferToEthereum; use tokio::sync::oneshot::{channel, Receiver}; use super::*; diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index f646bc1d87..ca7c61a300 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -27,7 +27,7 @@ pub mod mock_eth_fullnode { #[cfg(not(feature = "eth-fullnode"))] pub mod mock_oracle { - use anoma::types::ethereum_events::EthereumEvent; + use namada::types::ethereum_events::EthereumEvent; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 6d4db3059f..48dd43552a 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -75,7 +75,7 @@ where } impl<'a, D, H> From<&'a mut Shell> - for ShellParams<'a, D, H, anoma::vm::WasmCacheRwAccess> + for ShellParams<'a, D, H, namada::vm::WasmCacheRwAccess> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index dbcd63a112..dcff531038 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -33,8 +33,8 @@ use namada::ledger::storage::{ use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; -use namada::types::key::*; use namada::types::ethereum_events::EthereumEvent; +use namada::types::key::*; use namada::types::storage::{BlockHeight, Key}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::transaction::{ diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index a3530461b3..21f83fb36e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,6 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use anoma::types::transaction::protocol::ProtocolTxType; +use namada::types::transaction::protocol::ProtocolTxType; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] diff --git a/documentation/dev/.gitignore b/documentation/dev/.gitignore deleted file mode 100644 index 7585238efe..0000000000 --- a/documentation/dev/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/documentation/dev/Makefile b/documentation/dev/Makefile deleted file mode 100644 index 804a2f00d8..0000000000 --- a/documentation/dev/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -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/book.toml b/documentation/dev/book.toml deleted file mode 100644 index f7b5f340c6..0000000000 --- a/documentation/dev/book.toml +++ /dev/null @@ -1,6 +0,0 @@ -[book] -authors = ["Raymond E. Pasco"] -language = "en" -multilingual = false -src = "src" -title = "Namada Developer Docs" diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md deleted file mode 100644 index 7390c82896..0000000000 --- a/documentation/dev/src/SUMMARY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Summary - -- [Chapter 1](./chapter_1.md) diff --git a/documentation/dev/src/chapter_1.md b/documentation/dev/src/chapter_1.md deleted file mode 100644 index b743fda354..0000000000 --- a/documentation/dev/src/chapter_1.md +++ /dev/null @@ -1 +0,0 @@ -# Chapter 1 diff --git a/documentation/docs/src/chapter_1.md b/documentation/docs/src/chapter_1.md deleted file mode 100644 index b743fda354..0000000000 --- a/documentation/docs/src/chapter_1.md +++ /dev/null @@ -1 +0,0 @@ -# Chapter 1 diff --git a/documentation/spec/.gitignore b/documentation/spec/.gitignore deleted file mode 100644 index 7585238efe..0000000000 --- a/documentation/spec/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/documentation/spec/Makefile b/documentation/spec/Makefile deleted file mode 100644 index 804a2f00d8..0000000000 --- a/documentation/spec/Makefile +++ /dev/null @@ -1,16 +0,0 @@ -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/spec/book.toml b/documentation/spec/book.toml deleted file mode 100644 index 04d0dab657..0000000000 --- a/documentation/spec/book.toml +++ /dev/null @@ -1,6 +0,0 @@ -[book] -authors = ["Raymond E. Pasco"] -language = "en" -multilingual = false -src = "src" -title = "Namada Specifications" diff --git a/documentation/spec/src/SUMMARY.md b/documentation/spec/src/SUMMARY.md deleted file mode 100644 index 7390c82896..0000000000 --- a/documentation/spec/src/SUMMARY.md +++ /dev/null @@ -1,3 +0,0 @@ -# Summary - -- [Chapter 1](./chapter_1.md) diff --git a/documentation/spec/src/chapter_1.md b/documentation/spec/src/chapter_1.md deleted file mode 100644 index b743fda354..0000000000 --- a/documentation/spec/src/chapter_1.md +++ /dev/null @@ -1 +0,0 @@ -# Chapter 1 diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 86fc32a17e..9c9f207f0c 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -126,10 +126,7 @@ impl VoteExtensionDigest { self, last_height: BlockHeight, ) -> Vec> { - let VoteExtensionDigest { - signatures, - events, - } = self; + let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -278,10 +275,7 @@ mod tests { }, ]; - let digest = VoteExtensionDigest { - events, - signatures, - }; + let digest = VoteExtensionDigest { events, signatures }; // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index fcda82a6a5..c4b8e4b65c 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -238,6 +238,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -293,7 +305,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -324,6 +336,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -531,6 +549,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" @@ -753,11 +777,55 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[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", @@ -791,6 +859,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +903,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -901,10 +987,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 +1153,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1180,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1118,7 +1202,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1139,6 +1223,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1363,6 +1485,8 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -1371,6 +1495,7 @@ dependencies = [ "itertools", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1492,6 +1617,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1553,6 +1690,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1655,6 +1818,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1664,6 +1840,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1821,6 +2007,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2003,6 +2195,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2026,6 +2228,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2232,6 +2440,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2298,6 +2516,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2348,6 +2572,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2371,7 +2601,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2399,7 +2629,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2412,7 +2642,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2425,7 +2655,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2442,7 +2672,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2466,7 +2696,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2546,6 +2776,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2809,6 +3048,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3272,6 +3523,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3c82e7293e..2bbac52319 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -238,6 +238,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -293,7 +305,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -324,6 +336,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -531,6 +549,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" @@ -753,11 +777,55 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[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", @@ -791,6 +859,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +903,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -901,10 +987,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 +1153,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1180,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1118,7 +1202,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1139,6 +1223,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1363,6 +1485,8 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -1371,6 +1495,7 @@ dependencies = [ "itertools", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1492,6 +1617,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1553,6 +1690,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1655,6 +1818,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1664,6 +1840,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1821,6 +2007,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2003,6 +2195,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2026,6 +2228,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2232,6 +2440,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2298,6 +2516,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2348,6 +2572,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2371,7 +2601,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2399,7 +2629,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2412,7 +2642,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2425,7 +2655,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2442,7 +2672,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2466,7 +2696,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2546,6 +2776,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2798,6 +3037,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3272,6 +3523,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 6b59401588..d6e7e77bf7 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -238,6 +238,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.0" @@ -293,7 +305,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -324,6 +336,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -531,6 +549,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" @@ -753,11 +777,55 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[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", @@ -791,6 +859,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -823,6 +903,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -901,10 +987,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 +1153,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1180,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1118,7 +1202,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1139,6 +1223,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1363,6 +1485,8 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -1371,6 +1495,7 @@ dependencies = [ "itertools", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1518,6 +1643,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1579,6 +1716,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1681,6 +1844,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1690,6 +1866,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1847,6 +2033,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2029,6 +2221,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.22.0" @@ -2052,6 +2254,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2258,6 +2466,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2324,6 +2542,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2374,6 +2598,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.2" @@ -2397,7 +2627,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2425,7 +2655,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2438,7 +2668,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2451,7 +2681,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2468,7 +2698,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2492,7 +2722,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2572,6 +2802,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2824,6 +3063,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3287,6 +3538,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.3" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2507c11460..2b641df776 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -238,6 +238,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.1" @@ -293,7 +305,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -324,6 +336,12 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "bytecheck" version = "0.6.7" @@ -532,6 +550,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" @@ -754,11 +778,55 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.1", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[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", @@ -792,6 +860,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -824,6 +904,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.21" @@ -902,10 +988,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 +1163,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "derive_more", @@ -1106,7 +1190,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?branch=murisi/jsfix#7d7570f2db008fc8476e4f0cacfc48d281139aa5" dependencies = [ "bytes", "prost", @@ -1128,7 +1212,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1149,6 +1233,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1374,6 +1496,8 @@ dependencies = [ "clru", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", "hex", "ibc", @@ -1382,6 +1506,7 @@ dependencies = [ "itertools", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1524,6 +1649,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1585,6 +1722,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1687,6 +1850,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1696,6 +1872,16 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1853,6 +2039,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -2035,6 +2227,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.23.1" @@ -2058,6 +2260,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2264,6 +2472,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2330,6 +2548,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -2380,6 +2604,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.3" @@ -2403,7 +2633,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "async-trait", "bytes", @@ -2431,7 +2661,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "flex-error", "serde", @@ -2444,7 +2674,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "derive_more", "flex-error", @@ -2457,7 +2687,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2474,7 +2704,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "bytes", "flex-error", @@ -2498,7 +2728,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?branch=murisi/jsfix#d2615b9c8133c61b70d28ce7a34b51cbe623f1a4" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2578,6 +2808,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2830,6 +3069,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -3299,6 +3550,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.4" From 3b7bbdddfd618042c20dd6920cd5e55f24326359 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 18 Jul 2022 14:26:44 +0100 Subject: [PATCH 0099/1995] WIP: Prepare proposal Working on compressing a set of signed vote extensions into a single vote extension digest, reflecting all ethereum events seen by validator nodes at the previous block height. --- .../lib/node/ledger/shell/prepare_proposal.rs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 11daebcbe5..37dc5ae862 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,7 +5,9 @@ mod prepare_block { use tendermint_proto::abci::TxRecord; use super::super::*; + use crate::proto::Signed; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + use crate::types::ethereum_events::vote_extensions::VoteExtension; impl Shell where @@ -33,10 +35,39 @@ mod prepare_block { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); + // add ethereum events as protocol txs + let mut txs: Vec = { + let protocol_key = self.mode + .get_protocol_key() + .expect("Validators should always have a protocol key"); + + let ethereum_events: Vec<_> = req + .local_last_commit + .map(|local_last_commit| { + local_last_commit + .votes + .into_iter() + .filter_map(|vote| { + let vote_extension = Signed::try_from_slice( + &vote.vote_extension[..] + ).ok()?; + vote_extension + }) + .flat_map(|vote_extension| { + vote_extension + .ethereum_events + }) + .collect() + }) + .unwrap_or_default(); + + todo!() + }; + // 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 + let mut mempool_txs: Vec = req .txs .into_iter() .take(number_of_new_txs) @@ -51,6 +82,8 @@ mod prepare_block { }) .collect(); + txs.append(&mut mempool_txs); + // decrypt the wrapper txs included in the previous block let mut decrypted_txs = self .storage From e167555ab0fdc0ca6de96e1eeda6741dc1203553 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 10:47:53 +0100 Subject: [PATCH 0100/1995] Get a validator addr from a tendermint addr --- apps/src/lib/node/ledger/shell/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e5867fddb4..42b990ff8f 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -542,6 +542,16 @@ where } } + /// Converts a raw Tendermint address to an [`address::Address`]. + // TODO: use this above in `slash()` + pub fn raw_hash_to_address( + &self, + raw_hash: impl AsRef<[u8]>, + ) -> Option { + let raw_hash = core::str::from_utf8(raw_hash.as_ref()).ok()?; + self.storage.read_validator_address_raw_hash(raw_hash) + } + #[cfg(not(feature = "ABCI"))] /// INVARIANT: This method must be stateless. pub fn extend_vote( From 6bfad3be23b2e817a85c8ccfd4d3a495fecc128a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 11:03:21 +0100 Subject: [PATCH 0101/1995] Fixes and run fmt --- .../lib/node/ledger/shell/prepare_proposal.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 37dc5ae862..332440b195 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,8 +5,8 @@ mod prepare_block { use tendermint_proto::abci::TxRecord; use super::super::*; - use crate::proto::Signed; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + use crate::proto::Signed; use crate::types::ethereum_events::vote_extensions::VoteExtension; impl Shell @@ -37,7 +37,8 @@ mod prepare_block { // add ethereum events as protocol txs let mut txs: Vec = { - let protocol_key = self.mode + let protocol_key = self + .mode .get_protocol_key() .expect("Validators should always have a protocol key"); @@ -48,14 +49,16 @@ mod prepare_block { .votes .into_iter() .filter_map(|vote| { - let vote_extension = Signed::try_from_slice( - &vote.vote_extension[..] - ).ok()?; + let vote_extension = Signed::< + VoteExtension, + >::try_from_slice( + &vote.vote_extension[..], + ) + .ok()?; vote_extension }) .flat_map(|vote_extension| { - vote_extension - .ethereum_events + vote_extension.ethereum_events }) .collect() }) From dd78d08f483dc12b58b974101545946eac935e8a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 11:13:28 +0100 Subject: [PATCH 0102/1995] WIP: Prepare proposal fixes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 332440b195..e48e7518de 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,8 +6,8 @@ mod prepare_block { use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - use crate::proto::Signed; - use crate::types::ethereum_events::vote_extensions::VoteExtension; + use anoma::proto::Signed; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; impl Shell where @@ -55,10 +55,10 @@ mod prepare_block { &vote.vote_extension[..], ) .ok()?; - vote_extension + Some(vote_extension) }) .flat_map(|vote_extension| { - vote_extension.ethereum_events + vote_extension.data.ethereum_events }) .collect() }) From ca2665fd172652eabc41594589c9139b7ae0941f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 11:20:55 +0100 Subject: [PATCH 0103/1995] Aggregate addresses and vote extensions --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e48e7518de..35162f5b40 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -55,10 +55,12 @@ mod prepare_block { &vote.vote_extension[..], ) .ok()?; - Some(vote_extension) - }) - .flat_map(|vote_extension| { - vote_extension.data.ethereum_events + let validator = vote.validator?; + let validator_addr = self + .raw_hash_to_address( + validator.address, + )?; + Some((validator_addr, vote_extension)) }) .collect() }) From 5edd76134e10194f1019c254d1afc72a48176e34 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 16:27:38 +0100 Subject: [PATCH 0104/1995] Add get_validator_from_tm_address() --- apps/src/lib/node/ledger/shell/queries.rs | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cb750ce122..ae9e6c1841 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -342,4 +342,34 @@ where } }) } + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. Returns an error + /// if no matching validator is found in the active validator + /// set. + pub fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + ) -> std::result::Result { + let epoch = self.storage.get_current_epoch().0; + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| ())?; + let address = self.storage + .read_validator_address_raw_hash(&validator_raw_hash) + .ok_or(())?; + if self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .iter() + .find(|validator| &address == &validator.address) + .is_some() + { + Ok(address) + } else { + Err(()) + } + } } From a2d669e585fc242ac969b5f7b205a207a0289d65 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 16:28:11 +0100 Subject: [PATCH 0105/1995] Run make fmt --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- apps/src/lib/node/ledger/shell/queries.rs | 10 ++++++---- shared/src/types/ethereum_events/vote_extensions.rs | 10 ++-------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 35162f5b40..957fbcf1bf 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,12 +2,12 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { + use anoma::proto::Signed; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; use tendermint_proto::abci::TxRecord; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - use anoma::proto::Signed; - use anoma::types::ethereum_events::vote_extensions::VoteExtension; impl Shell where diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index ae9e6c1841..d357cbe085 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -353,12 +353,14 @@ where tm_address: &[u8], ) -> std::result::Result { let epoch = self.storage.get_current_epoch().0; - let validator_raw_hash = core::str::from_utf8(tm_address) - .map_err(|_| ())?; - let address = self.storage + let validator_raw_hash = + core::str::from_utf8(tm_address).map_err(|_| ())?; + let address = self + .storage .read_validator_address_raw_hash(&validator_raw_hash) .ok_or(())?; - if self.storage + if self + .storage .read_validator_set() .get(epoch) .expect("Validators for an epoch should be known") diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 86fc32a17e..9c9f207f0c 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -126,10 +126,7 @@ impl VoteExtensionDigest { self, last_height: BlockHeight, ) -> Vec> { - let VoteExtensionDigest { - signatures, - events, - } = self; + let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -278,10 +275,7 @@ mod tests { }, ]; - let digest = VoteExtensionDigest { - events, - signatures, - }; + let digest = VoteExtensionDigest { events, signatures }; // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` From ce6ceb7bfc5bb2c1f3dc79ea1e5e8b021a57eb31 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 16:31:27 +0100 Subject: [PATCH 0106/1995] Remove raw_hash_to_address --- apps/src/lib/node/ledger/shell/mod.rs | 10 ---------- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 ++++--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 42b990ff8f..e5867fddb4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -542,16 +542,6 @@ where } } - /// Converts a raw Tendermint address to an [`address::Address`]. - // TODO: use this above in `slash()` - pub fn raw_hash_to_address( - &self, - raw_hash: impl AsRef<[u8]>, - ) -> Option { - let raw_hash = core::str::from_utf8(raw_hash.as_ref()).ok()?; - self.storage.read_validator_address_raw_hash(raw_hash) - } - #[cfg(not(feature = "ABCI"))] /// INVARIANT: This method must be stateless. pub fn extend_vote( diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 957fbcf1bf..0270b4a65f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -57,9 +57,10 @@ mod prepare_block { .ok()?; let validator = vote.validator?; let validator_addr = self - .raw_hash_to_address( - validator.address, - )?; + .get_validator_from_tm_address( + &validator.address[..], + ) + .ok()?; Some((validator_addr, vote_extension)) }) .collect() From 7b401cd985b9f188de99fe4a8ffd471ceb25b5f4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 17:00:25 +0100 Subject: [PATCH 0107/1995] WIP: Compress vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 49 ++++++++++--------- .../types/ethereum_events/vote_extensions.rs | 8 +++ 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0270b4a65f..3c595c0cfb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -3,8 +3,10 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { use anoma::proto::Signed; - use anoma::types::ethereum_events::vote_extensions::VoteExtension; - use tendermint_proto::abci::TxRecord; + use anoma::types::ethereum_events::vote_extensions::{ + VoteExtension, VoteExtensionDigest, + }; + use tendermint_proto::abci::{ExtendedVoteInfo, TxRecord}; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -42,30 +44,13 @@ mod prepare_block { .get_protocol_key() .expect("Validators should always have a protocol key"); - let ethereum_events: Vec<_> = req + let vote_extension_digest = req .local_last_commit .map(|local_last_commit| { - local_last_commit - .votes - .into_iter() - .filter_map(|vote| { - let vote_extension = Signed::< - VoteExtension, - >::try_from_slice( - &vote.vote_extension[..], - ) - .ok()?; - let validator = vote.validator?; - let validator_addr = self - .get_validator_from_tm_address( - &validator.address[..], - ) - .ok()?; - Some((validator_addr, vote_extension)) - }) - .collect() + let votes = local_last_commit.votes; + self.compress_vote_extensions(votes) }) - .unwrap_or_default(); + .unwrap_or_else(VoteExtensionDigest::empty); todo!() }; @@ -116,6 +101,24 @@ mod prepare_block { ..Default::default() } } + + fn compress_vote_extensions( + &self, + vote_extensions: Vec, + ) -> VoteExtensionDigest { + let _ = vote_extensions.into_iter().filter_map(|vote| { + let vote_extension = Signed::::try_from_slice( + &vote.vote_extension[..], + ) + .ok()?; + let validator = vote.validator?; + let validator_addr = self + .get_validator_from_tm_address(&validator.address[..]) + .ok()?; + Some((validator_addr, vote_extension)) + }); + todo!() + } } /// Functions for creating the appropriate TxRecord given the diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 9c9f207f0c..65423e6aef 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -121,6 +121,14 @@ pub struct VoteExtensionDigest { } impl VoteExtensionDigest { + /// Creates a [`VoteExtensionDigest`] without any Ethereum events. + pub const fn empty() -> Self { + Self { + signatures: Vec::new(), + events: Vec::new(), + } + } + /// Decompresses a set of signed `VoteExtension` instances. pub fn decompress( self, From 6dafc0a96cb27dbef09e07bd5b906a882a7dc10a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 19 Jul 2022 17:01:42 +0100 Subject: [PATCH 0108/1995] Add a TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3c595c0cfb..a0f1f4930d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -114,6 +114,7 @@ mod prepare_block { let validator = vote.validator?; let validator_addr = self .get_validator_from_tm_address(&validator.address[..]) + // TODO: catch errors here and log them .ok()?; Some((validator_addr, vote_extension)) }); From b7f29b0dea0b0bdbc45cd786bfca012816d99315 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 10:11:35 +0100 Subject: [PATCH 0109/1995] Apply changes from bat/ethbridge/vote-extensions --- apps/src/lib/node/ledger/shell/queries.rs | 131 ++++++++++++++++++++-- 1 file changed, 120 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d357cbe085..6fbfc4a4e1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,11 +2,14 @@ use std::cmp::max; use anoma::ledger::parameters::EpochDuration; +#[cfg(not(feature = "ABCI"))] +use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; use anoma::ledger::pos::PosParams; use anoma::types::address::Address; +use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; use anoma::types::key; use anoma::types::key::dkg_session_keys::DkgPublicKey; -use anoma::types::storage::{Key, PrefixValue}; +use anoma::types::storage::{Epoch, Key, PrefixValue}; use anoma::types::token::{self, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; @@ -26,6 +29,32 @@ use tendermint_proto_abci::types::EvidenceParams; use super::*; use crate::node::ledger::response; +#[derive(Error, Debug)] +#[allow(dead_code)] +pub enum Error { + #[error( + "The address '{:?}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorAddress(Address, Epoch), + #[error( + "The public key '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKey(String, Epoch), + #[error( + "The public key hash '{0}' is not among the active validator set for \ + epoch {1}" + )] + NotValidatorKeyHash(String, Epoch), + #[error("There are currently no staked validators")] + NoStakingValidators, + #[error("This node is not a validator")] + NotValidator, + #[error("Invalid validator tendermint address")] + InvalidTMAddress, +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -299,17 +328,17 @@ where pub fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, - ) -> Option> { + epoch: Option, + ) -> std::result::Result, Error> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); - // get the current epoch - let (current_epoch, _) = self.storage.get_current_epoch(); + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() - .get(current_epoch) - .expect("Validators for the next epoch should be known") + .get(epoch) + .expect("Validators for an epoch should be known") .active .iter() .find(|validator| { @@ -341,6 +370,81 @@ where public_key: dkg_publickey.into(), } }) + .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) + } + + /// Lookup data about a validator from their address + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .storage + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.voting_power, protocol_pk) + }) + .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) + } + + /// Lookup the total voting power for an epoch + #[cfg(not(feature = "ABCI"))] + pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .map(|validators| { + validators + .active + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + }) + .unwrap_or_default() + } + + /// Get the voting power of this node (as a fraction of this epochs total + /// voting power) if it is a validator. Else return None + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_voting_power( + &self, + ) -> std::result::Result { + let power = if let Some(secret_key) = self.mode.get_protocol_key() { + self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) + .map(|validator| validator.power)? + } else { + return Err(Error::NotValidator); + }; + let total = u64::from(self.get_total_voting_power(None)); + if total > 0 { + Ok(FractionalVotingPower::new(power, total).unwrap()) + } else { + Err(Error::NoStakingValidators) + } } /// Given a tendermint validator, the address is the hash @@ -351,14 +455,19 @@ where pub fn get_validator_from_tm_address( &self, tm_address: &[u8], - ) -> std::result::Result { + ) -> std::result::Result { let epoch = self.storage.get_current_epoch().0; - let validator_raw_hash = - core::str::from_utf8(tm_address).map_err(|_| ())?; + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| Error::InvalidTMAddress)?; let address = self .storage .read_validator_address_raw_hash(&validator_raw_hash) - .ok_or(())?; + .ok_or_else(|| { + Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + })?; if self .storage .read_validator_set() @@ -371,7 +480,7 @@ where { Ok(address) } else { - Err(()) + Err(Error::NotValidatorAddress(address, epoch)) } } } From 2bfbc4777576905f37c053b7ca6cff522964a71d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 10:12:33 +0100 Subject: [PATCH 0110/1995] Verify sigs of vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a0f1f4930d..83736f5d65 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -106,18 +106,34 @@ mod prepare_block { &self, vote_extensions: Vec, ) -> VoteExtensionDigest { - let _ = vote_extensions.into_iter().filter_map(|vote| { - let vote_extension = Signed::::try_from_slice( - &vote.vote_extension[..], - ) - .ok()?; - let validator = vote.validator?; - let validator_addr = self - .get_validator_from_tm_address(&validator.address[..]) - // TODO: catch errors here and log them - .ok()?; - Some((validator_addr, vote_extension)) - }); + let _ = vote_extensions + .into_iter() + .filter_map(|vote| { + let vote_extension = + Signed::::try_from_slice( + &vote.vote_extension[..], + ) + .ok()?; + let validator = vote.validator?; + let validator_addr = self + .get_validator_from_tm_address(&validator.address[..]) + // TODO: catch errors here and log them + .ok()?; + Some((validator_addr, vote_extension)) + }) + .filter(|(validator_addr, vote_extension)| { + // TODO: + // - verify 2/3 stake of vote_extension + + let result = + self.get_validator_from_address(&validator_addr, None); + let validator_public_key = match result { + Ok((_, validator_public_key)) => validator_public_key, + Err(_) => return false, + }; + vote_extension.verify(&validator_public_key).is_ok() + }); + todo!() } } From 95c6dc5a6bf429ce882beb2c2a361b92520497f7 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 11:15:50 +0200 Subject: [PATCH 0111/1995] [feat]: Refactored vote extensions with new type defs. Fixed tests, lints, and formatting --- apps/src/lib/node/ledger/shell/mod.rs | 2 - apps/src/lib/node/ledger/shell/queries.rs | 65 +-- .../lib/node/ledger/shell/vote_extensions.rs | 374 +++++++----------- .../types/ethereum_events/vote_extensions.rs | 11 +- 4 files changed, 161 insertions(+), 291 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 52c443fe06..6a5ed84b69 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -33,8 +33,6 @@ use anoma::ledger::storage::{ use anoma::ledger::{ibc, parameters, pos}; use anoma::proto::{self, Tx}; use anoma::types::chain::ChainId; -#[cfg(not(feature = "ABCI"))] -use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; use anoma::types::ethereum_events::EthereumEvent; use anoma::types::key::*; use anoma::types::storage::{BlockHeight, Key}; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 8335713ad8..cb22a3ff80 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -13,16 +13,12 @@ use anoma::types::token::{self, Amount}; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; #[cfg(not(feature = "ABCI"))] -use tendermint_proto::abci::Validator; -#[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::abci::Validator; -#[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::{ProofOp, ProofOps}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf; @@ -43,16 +39,13 @@ pub enum Error { "The public key '{0}' is not among the active validator set for epoch \ {1}" )] + #[allow(dead_code)] NotValidatorKey(String, Epoch), #[error( - "The public key hash '{0}' is not among the active validator set for epoch \ + "The public key hash '{0}' is not among the active validator set for epoch \ {1}" )] NotValidatorKeyHash(String, Epoch), - #[error("There are currently no staked validators")] - NoStakingValidators, - #[error("This node is not a validator")] - NotValidator, #[error("Invalid validator tendermint address")] InvalidTMAddress, } @@ -411,6 +404,7 @@ where /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] + #[allow(dead_code)] pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); @@ -429,56 +423,25 @@ where .unwrap_or_default() } - /// Get the voting power of this node (as a fraction of this epochs total - /// voting power) if it is a validator. Else return None - #[cfg(not(feature = "ABCI"))] - pub fn get_validator_voting_power( - &self, - ) -> std::result::Result { - let power = if let Some(secret_key) = self.mode.get_protocol_key() { - self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) - .map(|validator| validator.power)? - } else { - return Err(Error::NotValidator); - }; - let total = u64::from(self.get_total_voting_power(None)); - if total > 0 { - Ok(FractionalVotingPower::new(power, total).unwrap()) - } else { - Err(Error::NoStakingValidators) - } - } - /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native - /// address from storage using this hash. Returns an error - /// if no matching validator is found in the active validator - /// set. + /// address from storage using this hash. pub fn get_validator_from_tm_address( &self, tm_address: &[u8], + epoch: Option, ) -> std::result::Result { - let epoch = self.storage.get_current_epoch().0; + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); let validator_raw_hash = core::str::from_utf8(tm_address) .map_err(|_| Error::InvalidTMAddress)?; - let address = self.storage + self.storage .read_validator_address_raw_hash(&validator_raw_hash) - .ok_or_else(|| Error::NotValidatorKeyHash( - validator_raw_hash.to_string(), - epoch - ))?; - if self.storage - .read_validator_set() - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .iter() - .find(|validator| address == &validator.address) - .is_some() - { - Ok(address) - } else { - Err(Error::NotValidatorAddress(address, epoch)) - } + .ok_or_else(|| { + Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + }) } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 672781e5a6..abc962d319 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,17 +1,12 @@ #[cfg(not(feature = "ABCI"))] mod extend_votes { - use anoma::types::ethereum_events::vote_extensions::{ - EpochPower, SignedEthEvent, SignedEvent, - }; + use anoma::proto::Signed; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; + use borsh::BorshDeserialize; use super::super::*; - /// The data we include in a vote extension - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] - pub struct VoteExtension { - /// Ethereum events seen since last round - ethereum_events: Vec, - } + type SignedExt = Signed; impl Shell where @@ -23,119 +18,94 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - response::ExtendVote { - vote_extension: VoteExtension { - ethereum_events: self.new_ethereum_events(), - } - .try_to_vec() - .unwrap(), - } + let ext = VoteExtension { + block_height: self.storage.last_height + 1, + ethereum_events: self.new_ethereum_events(), + }; + self.mode + .get_protocol_key() + .map(|signing_key| response::ExtendVote { + vote_extension: ext.sign(signing_key).try_to_vec().unwrap(), + }) + .unwrap_or_default() } - /// At present this checks the signature on all Ethereum headers + /// This checks that the vote extension: + /// * Correctly deserializes + /// * Was correctly signed by an active validator. + /// * The block height signed over is correct (replay protection) /// /// INVARIANT: This method must be stateless. pub fn verify_vote_extension( &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - if let Ok(VoteExtension { ethereum_events }) = - VoteExtension::try_from_slice(&req.vote_extension[..]) + let validator_addr = req.validator_address.as_slice(); + if let Ok(signed) = + SignedExt::try_from_slice(&req.vote_extension[..]) { - return if ethereum_events.iter().all(|event| { - self.validate_ethereum_event( + response::VerifyVoteExtension { + status: if self.validate_vote_extension( + signed, + validator_addr, self.storage.last_height + 1, - event, - ) - }) { - response::VerifyVoteExtension { - status: VerifyStatus::Accept.into(), - } - } else { - response::VerifyVoteExtension { - status: VerifyStatus::Reject.into(), - } - }; + ) { + VerifyStatus::Accept.into() + } else { + VerifyStatus::Reject.into() + }, + } + } else { + response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + } } - Default::default() + } + + /// Validates a vote extension issued at the provided block height + /// Checks that at epoch of the provided height + /// * The tendermint address corresponds to an active validator + /// * The validator correctly signed the extension + /// * The validator signed over the correct height inside of the + /// extension + pub fn validate_vote_extension( + &self, + ext: SignedExt, + tm_address: &[u8], + height: BlockHeight, + ) -> bool { + let epoch = self.storage.block.pred_epochs.get_epoch(height); + // get the validator that issued this vote extension + // this should not fail + let validator = + match self.get_validator_from_tm_address(tm_address, epoch) { + Ok(address) => address, + _ => return false, + }; + // get the public key associated with this validator + let pk = match self.get_validator_from_address(&validator, epoch) { + Ok((_, pk)) => pk, + _ => return false, + }; + ext.verify(&pk).is_ok() + && ext.data.block_height == height } /// Checks the channel from the Ethereum oracle monitoring - /// the fullnode and retrieves all messages sent. These are - /// signed and prepared for inclusion in a vote extension. - pub fn new_ethereum_events(&mut self) -> Vec { + /// the fullnode and retrieves all VoteExtensionmessages sent. + pub fn new_ethereum_events(&mut self) -> Vec { let mut events = vec![]; - let voting_power = self.get_validator_voting_power(); - let address = self.mode.get_validator_address().cloned(); if let ShellMode::Validator { ref mut ethereum_recv, - data: - ValidatorData { - keys: - ValidatorKeys { - protocol_keypair, .. - }, - .. - }, .. } = &mut self.mode { - let (voting_power, address) = - voting_power.zip(address).unwrap(); while let Ok(eth_event) = ethereum_recv.try_recv() { - events.push(SignedEthEvent::new( - eth_event, - address.clone(), - voting_power.clone(), - self.storage.last_height + 1, - protocol_keypair, - )); + events.push(eth_event); } } - events - } - - /// Verify that each ethereum header in a vote extension was signed by - /// a validator in the correct epoch, the stated voting power is - /// correct, and the signature is correct. - pub fn validate_ethereum_event( - &self, - height: BlockHeight, - event: &impl SignedEvent, - ) -> bool { - let epoch = self.storage.block.pred_epochs.get_epoch(height); - let total_voting_power = self.get_total_voting_power(epoch); - if u64::from(total_voting_power) == 0 { - return false; - } - // Get the public keys of each validator. Filter out those that - // inaccurately stated their voting power at a given block height - let mut public_keys = vec![]; - for EpochPower { - validator, - voting_power, - block_height, - } in event.get_voting_powers().into_iter() - { - if block_height != height { - continue; - } - if let Some((power, pk)) = - self.get_validator_from_address(&validator, epoch) - { - let power = - FractionalVotingPower::new(power, total_voting_power) - .unwrap(); - if power == voting_power { - public_keys.push(pk); - } - } - } - // check that we found all the public keys and - // check that the signatures are valid - public_keys.len() == event.number_of_signers() - && event.verify_signatures(&public_keys).is_ok() + events } } @@ -145,9 +115,7 @@ mod extend_votes { use anoma::ledger::pos; use anoma::ledger::pos::anoma_proof_of_stake::PosBase; - use anoma::types::ethereum_events::vote_extensions::{ - FractionalVotingPower, MultiSignedEthEvent, SignedEthEvent, - }; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; use anoma::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; @@ -157,8 +125,8 @@ mod extend_votes { use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; + use super::SignedExt; use crate::node::ledger::shell::test_utils::*; - use crate::node::ledger::shell::vote_extensions::VoteExtension; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; /// Test that we successfully receive ethereum events @@ -183,13 +151,8 @@ mod extend_votes { }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); - let [event_first, event_second]: [EthereumEvent; 2] = shell - .new_ethereum_events() - .into_iter() - .map(|signed| signed.event.data.0) - .collect::>() - .try_into() - .expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = + shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_2); @@ -200,6 +163,11 @@ mod extend_votes { #[test] fn test_eth_events_vote_extension() { let (mut shell, _, oracle) = setup(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); let event_1 = EthereumEvent::NewContract { name: "Test".to_string(), address: EthAddress([0; 20]), @@ -214,19 +182,17 @@ mod extend_votes { }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); - let vote_extension: VoteExtension = - BorshDeserialize::try_from_slice( + let vote_extension = + ::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], ) .expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = vote_extension + .data .ethereum_events .clone() - .into_iter() - .map(|signed| signed.event.data.0) - .collect::>() .try_into() .expect("Test failed"); @@ -234,7 +200,11 @@ mod extend_votes { assert_eq!(event_second, event_2); let req = request::VerifyVoteExtension { hash: vec![], - validator_address: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), height: 0, vote_extension: vote_extension .try_to_vec() @@ -254,30 +224,34 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let voting_power = - shell.get_validator_voting_power().expect("Test failed"); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height + 1, - &signing_key, + }], + block_height: shell.storage.last_height + 1, + } + .sign(&signing_key) + .try_to_vec() + .expect("Test failed"); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); } /// Test that validation of vote extensions cast during the @@ -294,24 +268,20 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let voting_power = - shell.get_validator_voting_power().expect("Test failed"); - let height = shell.storage.last_height + 1; - - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let signed_height = shell.storage.last_height + 1; + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height + 1, - &signing_key, - ); + }], + block_height: signed_height, + } + .sign(shell.mode.get_protocol_key().expect("Test failed")); + assert_eq!(shell.storage.get_current_epoch().0.0, 0); // We make a change so that there are no // validators in the next epoch @@ -334,7 +304,7 @@ mod extend_votes { assert!( shell .get_validator_from_protocol_pk(&signing_key.ref_to(), None) - .is_none() + .is_err() ); let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); assert!( @@ -344,49 +314,15 @@ mod extend_votes { &signing_key.ref_to(), Some(prev_epoch) ) - .is_some() + .is_ok() ); - assert!(shell.validate_ethereum_event(height, &signed_event)); - assert!(shell.validate_ethereum_event( - height, - &MultiSignedEthEvent::from(signed_event) - )); - } - /// Test that if the declared voting power is not correct, - /// the signed event is rejected - #[test] - fn reject_incorrect_voting_power() { - let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); - let address = shell.mode.get_validator_address().unwrap().clone(); - let voting_power = 99u64; - let total_voting_power = - u64::from(shell.get_total_voting_power(None)); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }, - address, - FractionalVotingPower::new(voting_power, total_voting_power) - .expect("Test failed"), - shell.storage.last_height + 1, - signing_key, - ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); + let tm_address = address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(); + assert!(shell.validate_vote_extension(vote_ext, &tm_address, signed_height)); } /// Test that that an event that incorrectly labels what block it was @@ -394,64 +330,32 @@ mod extend_votes { #[test] fn reject_incorrect_block_number() { let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); let address = shell.mode.get_validator_address().unwrap().clone(); - let voting_power = shell.get_validator_voting_power().unwrap(); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height, - signing_key, - ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); - } + }], + block_height: shell.storage.last_height, + } + .sign(shell.mode.get_protocol_key().expect("Test failed")) + .try_to_vec() + .expect("Test failed"); - /// Test that that an event with an incorrect address - /// included in a vote extension is rejected - #[test] - fn reject_incorrect_address() { - let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); - let voting_power = shell.get_validator_voting_power().unwrap(); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }, - crate::wallet::defaults::bertha_address(), - voting_power, - shell.storage.last_height, - signing_key, + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address.try_to_vec().expect("Test failed"), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); } } } diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 9c9f207f0c..c76a659f6f 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -10,7 +10,7 @@ use num_rational::Ratio; use super::EthereumEvent; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::key::common::Signature; +use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; /// This struct will be created and signed over by each @@ -32,6 +32,11 @@ impl VoteExtension { ethereum_events: Vec::new(), } } + + /// Sign a vote extension and return the data with signature + pub fn sign(self, signing_key: &common::SecretKey) -> Signed { + Signed::new(signing_key, self) + } } /// A fraction of the total voting power. This should always be a reduced @@ -266,11 +271,11 @@ mod tests { }; let events = vec![ MultiSignedEthEvent { - event: ev_1.clone(), + event: ev_1, signers: signers.clone(), }, MultiSignedEthEvent { - event: ev_2.clone(), + event: ev_2, signers, }, ]; From 8d756947409d1addc32d619ff63f1c288e6b8247 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 10:47:06 +0100 Subject: [PATCH 0112/1995] Remove VoteExtensionDigest::empty --- shared/src/types/ethereum_events/vote_extensions.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 65423e6aef..9c9f207f0c 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -121,14 +121,6 @@ pub struct VoteExtensionDigest { } impl VoteExtensionDigest { - /// Creates a [`VoteExtensionDigest`] without any Ethereum events. - pub const fn empty() -> Self { - Self { - signatures: Vec::new(), - events: Vec::new(), - } - } - /// Decompresses a set of signed `VoteExtension` instances. pub fn decompress( self, From 5326ed7699a4a5fe3a17c4a1451f59e2f0a43c77 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 10:51:45 +0100 Subject: [PATCH 0113/1995] Refactor transaction batching --- .../lib/node/ledger/shell/prepare_proposal.rs | 129 +++++++++++------- 1 file changed, 77 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 83736f5d65..074e766a12 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,7 +6,9 @@ mod prepare_block { use anoma::types::ethereum_events::vote_extensions::{ VoteExtension, VoteExtensionDigest, }; - use tendermint_proto::abci::{ExtendedVoteInfo, TxRecord}; + use tendermint_proto::abci::{ + ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, + }; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -34,63 +36,18 @@ mod prepare_block { // 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(); - // add ethereum events as protocol txs - let mut txs: Vec = { - let protocol_key = self - .mode - .get_protocol_key() - .expect("Validators should always have a protocol key"); - - let vote_extension_digest = req - .local_last_commit - .map(|local_last_commit| { - let votes = local_last_commit.votes; - self.compress_vote_extensions(votes) - }) - .unwrap_or_else(VoteExtensionDigest::empty); - - todo!() - }; - - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + req.txs.len() / 2; - let mut mempool_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(); + let mut txs: Vec = + self.build_vote_extensions_txs(req.local_last_commit); + // add mempool txs + let mut mempool_txs = self.build_mempool_txs(req.txs); txs.append(&mut mempool_txs); // 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() - }) - .map(record::add) - .collect(); - + let mut decrypted_txs = self.build_decrypted_txs(); txs.append(&mut decrypted_txs); + txs } else { vec![] @@ -102,6 +59,74 @@ mod prepare_block { } } + /// Builds a batch of vote extension transactions, comprised of Ethereum + /// events + fn build_vote_extensions_txs( + &mut self, + local_last_commit: Option, + ) -> Vec { + let protocol_key = self + .mode + .get_protocol_key() + .expect("Validators should always have a protocol key"); + + let vote_extension_digest = + local_last_commit.map(|local_last_commit| { + let votes = local_last_commit.votes; + self.compress_vote_extensions(votes) + }); + let vote_extension_digest = match vote_extension_digest { + Some(d) => d, + // if no vote extensions were found, we return an empty + // `Vec` of protocol + // transactions + _ => return vec![], + }; + + todo!() + } + + /// Builds a batch of mempool transactions + fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { + // filter in half of the new txs from Tendermint, only keeping + // wrappers + let number_of_new_txs = 1 + txs.len() / 2; + 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() + } + + /// Builds a batch of DKG decrypted transactions + fn build_decrypted_txs(&mut self) -> Vec { + // TODO: This should not be hardcoded + let privkey = ::G2Affine::prime_subgroup_generator(); + + self.storage + .tx_queue + .iter() + .map(|tx| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted(tx), + _ => DecryptedTx::Undecryptable(tx.clone()), + }) + .to_bytes() + }) + .map(record::add) + .collect() + } + + /// Compresses a set of vote extensions into a single + /// [`VoteExtensionDigest`], whilst filtering invalid + /// `Signed` instances in the process fn compress_vote_extensions( &self, vote_extensions: Vec, From 0e8dd679f889538c11838b9e1dee4a1b70293663 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 11:21:13 +0100 Subject: [PATCH 0114/1995] Compress vote extensions into a digest --- .../lib/node/ledger/shell/prepare_proposal.rs | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 074e766a12..8544daef6b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,9 +2,11 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { + use std::collections::{BTreeMap, HashSet}; + use anoma::proto::Signed; use anoma::types::ethereum_events::vote_extensions::{ - VoteExtension, VoteExtensionDigest, + MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -131,7 +133,7 @@ mod prepare_block { &self, vote_extensions: Vec, ) -> VoteExtensionDigest { - let _ = vote_extensions + let all_vote_extensions = vote_extensions .into_iter() .filter_map(|vote| { let vote_extension = @@ -149,7 +151,9 @@ mod prepare_block { .filter(|(validator_addr, vote_extension)| { // TODO: // - verify 2/3 stake of vote_extension + // - verify block height of vote_extension + // verify signature of the vote extension let result = self.get_validator_from_address(&validator_addr, None); let validator_public_key = match result { @@ -159,7 +163,35 @@ mod prepare_block { vote_extension.verify(&validator_public_key).is_ok() }); - todo!() + let mut event_observers = BTreeMap::new(); + let mut signatures = Vec::new(); + + for (validator_addr, vote_extension) in all_vote_extensions { + // register all ethereum events seen by `validator_addr` + for ev in vote_extension.data.ethereum_events { + let signers = event_observers + .entry(ev) + .or_insert_with(HashSet::new); + + signers.insert(validator_addr.clone()); + } + + // register the signature of `validator_addr` + let addr = validator_addr; + let sig = vote_extension.sig; + + signatures.push((sig, addr)); + } + + let events = event_observers + .into_iter() + .map(|(event, signers)| MultiSignedEthEvent { event, signers }) + .collect(); + + VoteExtensionDigest { + events, + signatures, + } } } From e72755f00e0fb21d0e2a01e347ad6276c1b1e510 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 11:41:58 +0100 Subject: [PATCH 0115/1995] Log errors --- .../lib/node/ledger/shell/prepare_proposal.rs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 8544daef6b..2cb87e347b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -144,7 +144,14 @@ mod prepare_block { let validator = vote.validator?; let validator_addr = self .get_validator_from_tm_address(&validator.address[..]) - // TODO: catch errors here and log them + .map_err(|err| { + tracing::error!( + "Failed to get an address from Tendermint \ + {:?}: {}", + validator, + err + ); + }) .ok()?; Some((validator_addr, vote_extension)) }) @@ -158,7 +165,14 @@ mod prepare_block { self.get_validator_from_address(&validator_addr, None); let validator_public_key = match result { Ok((_, validator_public_key)) => validator_public_key, - Err(_) => return false, + Err(_) => { + tracing::error!( + "Could not get public key from Storage for \ + validator {}", + validator_addr + ); + return false; + } }; vote_extension.verify(&validator_public_key).is_ok() }); @@ -169,9 +183,8 @@ mod prepare_block { for (validator_addr, vote_extension) in all_vote_extensions { // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { - let signers = event_observers - .entry(ev) - .or_insert_with(HashSet::new); + let signers = + event_observers.entry(ev).or_insert_with(HashSet::new); signers.insert(validator_addr.clone()); } @@ -188,10 +201,7 @@ mod prepare_block { .map(|(event, signers)| MultiSignedEthEvent { event, signers }) .collect(); - VoteExtensionDigest { - events, - signatures, - } + VoteExtensionDigest { events, signatures } } } From 4f37ebcf0b934179375e58c727d22f4e0eef7c03 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 11:44:21 +0100 Subject: [PATCH 0116/1995] Remove type signature from assignment --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 2cb87e347b..34dc7bc1c9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -39,7 +39,7 @@ mod prepare_block { self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { // add ethereum events as protocol txs - let mut txs: Vec = + let mut txs = self.build_vote_extensions_txs(req.local_last_commit); // add mempool txs From ba08ca3964171dbe2fd3d5d1153e4a409dcef820 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 11:47:44 +0100 Subject: [PATCH 0117/1995] More logging --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 34dc7bc1c9..5c3dd203bf 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -140,6 +140,13 @@ mod prepare_block { Signed::::try_from_slice( &vote.vote_extension[..], ) + .map_err(|err| { + tracing::error!( + "Failed to deserialize signed vote extension: \ + {}", + err + ); + }) .ok()?; let validator = vote.validator?; let validator_addr = self From aa4b8d88dc7e2aff914aaba798362d510064e051 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 13:10:04 +0200 Subject: [PATCH 0118/1995] [feat]: Added an internal queue of Ethereum events. This is so that validators can monitor if they need to resubmit events because their previous vote extensions didn't make it into a block proposal --- apps/src/lib/node/ledger/shell/mod.rs | 53 ++++++++++++- apps/src/lib/node/ledger/shell/queries.rs | 4 +- .../lib/node/ledger/shell/vote_extensions.rs | 75 ++++++++++++------- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6a5ed84b69..84a0677ab7 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -169,12 +169,55 @@ pub(super) enum ShellMode { Validator { data: ValidatorData, broadcast_sender: UnboundedSender>, - ethereum_recv: UnboundedReceiver, + ethereum_recv: EthereumReceiver, }, Full, Seed, } +/// A channel for pulling events from the Ethereum oracle +/// and queueing them up for inclusion in vote extensions +#[derive(Debug)] +pub(super) struct EthereumReceiver { + channel: UnboundedReceiver, + queue: Vec, +} + +impl EthereumReceiver { + /// Create a new [`EthereumReceiver`] from a channel connected + /// to an Ethereum oracle + pub fn new(channel: UnboundedReceiver) -> Self { + Self { + channel, + queue: vec![], + } + } + + /// Pull messages from the channel and add to queue + /// Since vote extensions require ordering of ethereum + /// events, we do that here. We also de-duplicate events + pub fn fill_queue(&mut self) { + while let Ok(eth_event) = self.channel.try_recv() { + self.queue.push(eth_event); + } + self.queue.sort(); + self.queue.dedup(); + } + + /// Get a copy of the queue + pub fn get_events(&self) -> Vec { + self.queue.clone() + } + + /// Given a list of events, remove them from the queue if present + /// Note that this method preserves the sorting and de-duplication + /// of events in the queue. + #[allow(dead_code)] + pub fn remove(&mut self, events: &[EthereumEvent]) { + self.queue.retain(|event| !events.contains(event)); + } +} + #[allow(dead_code)] impl ShellMode { /// Get the validator address if ledger is in validator mode @@ -308,7 +351,9 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, - ethereum_recv: eth_receiver.unwrap(), + ethereum_recv: EthereumReciever::new( + eth_receiver.unwrap(), + ), }) .expect( "Validator data should have been stored in the \ @@ -327,7 +372,9 @@ where }, }, broadcast_sender, - ethereum_recv: eth_receiver.unwrap(), + ethereum_recv: EthereumReceiver::new( + eth_receiver.unwrap(), + ), } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cb22a3ff80..8e6903017b 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -42,8 +42,8 @@ pub enum Error { #[allow(dead_code)] NotValidatorKey(String, Epoch), #[error( - "The public key hash '{0}' is not among the active validator set for epoch \ - {1}" + "The public key hash '{0}' is not among the active validator set for \ + epoch {1}" )] NotValidatorKeyHash(String, Epoch), #[error("Invalid validator tendermint address")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index abc962d319..1d3c4a3363 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -87,25 +87,22 @@ mod extend_votes { Ok((_, pk)) => pk, _ => return false, }; - ext.verify(&pk).is_ok() - && ext.data.block_height == height + ext.verify(&pk).is_ok() && ext.data.block_height == height } /// Checks the channel from the Ethereum oracle monitoring /// the fullnode and retrieves all VoteExtensionmessages sent. pub fn new_ethereum_events(&mut self) -> Vec { - let mut events = vec![]; - if let ShellMode::Validator { - ref mut ethereum_recv, - .. - } = &mut self.mode - { - while let Ok(eth_event) = ethereum_recv.try_recv() { - events.push(eth_event); + match &mut self.mode { + ShellMode::Validator { + ref mut ethereum_recv, + .. + } => { + ethereum_recv.fill_queue(); + ethereum_recv.get_events() } + _ => vec![], } - - events } } @@ -133,29 +130,48 @@ mod extend_votes { /// from the channel to fullnode process /// /// We further check that ledger side buffering is done if multiple - /// events are in the channel + /// events are in the channel and that queueing and de-duplicating is + /// done #[test] fn test_get_eth_events() { let (mut shell, _, oracle) = setup(); - let event_1 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), + let event_1 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], }; let event_2 = EthereumEvent::TransfersToEthereum { - nonce: 1.into(), + nonce: 2.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], }; + let event_3 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_3); + // check that we queue and de-duplicate events + oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); + let [event_first, event_second, event_third]: [EthereumEvent; 3] = + shell.new_ethereum_events().try_into().expect("Test failed"); + assert_eq!(event_first, event_1); assert_eq!(event_second, event_2); + assert_eq!(event_third, event_3); } /// Test that ethereum events are added to vote extensions. @@ -168,11 +184,7 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let event_1 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - }; - let event_2 = EthereumEvent::TransfersToEthereum { + let event_1 = EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), @@ -180,6 +192,10 @@ mod extend_votes { receiver: EthAddress([2; 20]), }], }; + let event_2 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); let vote_extension = @@ -317,12 +333,13 @@ mod extend_votes { .is_ok() ); - let tm_address = address - .raw_hash() - .expect("Test failed") - .as_bytes() - .to_vec(); - assert!(shell.validate_vote_extension(vote_ext, &tm_address, signed_height)); + let tm_address = + address.raw_hash().expect("Test failed").as_bytes().to_vec(); + assert!(shell.validate_vote_extension( + vote_ext, + &tm_address, + signed_height + )); } /// Test that that an event that incorrectly labels what block it was From 75ed9fdfcd8d5adcb975c9c6c73db7762ba4d779 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 13:07:22 +0100 Subject: [PATCH 0119/1995] Logging --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5c3dd203bf..9b1b59a4e9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -38,6 +38,8 @@ mod prepare_block { // proposal is accepted self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { + // TODO: add some info logging + // add ethereum events as protocol txs let mut txs = self.build_vote_extensions_txs(req.local_last_commit); @@ -148,7 +150,10 @@ mod prepare_block { ); }) .ok()?; - let validator = vote.validator?; + let validator = vote.validator.or_else(|| { + tracing::error!("Vote extension has no validator data"); + None + })?; let validator_addr = self .get_validator_from_tm_address(&validator.address[..]) .map_err(|err| { From d925a363ab597f7ca72140bd31d0ceb007588d45 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 13:39:33 +0100 Subject: [PATCH 0120/1995] Refactor ProtocolTxType::sign --- shared/src/types/transaction/protocol.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index 05b8bec010..a956935a52 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -83,20 +83,14 @@ mod protocol_txs { impl ProtocolTxType { /// Sign a ProtocolTxType and wrap it up in a normal Tx - pub fn sign( - self, - pk: &common::PublicKey, - signing_key: &common::SecretKey, - ) -> Tx { + pub fn sign(self, signing_key: &common::SecretKey) -> Tx { + let pk = signing_key.ref_to(); Tx::new( vec![], Some( - TxType::Protocol(ProtocolTx { - pk: pk.clone(), - tx: self, - }) - .try_to_vec() - .expect("Could not serialize ProtocolTx"), + TxType::Protocol(ProtocolTx { pk, tx: self }) + .try_to_vec() + .expect("Could not serialize ProtocolTx"), ), ) .sign(signing_key) From 9195fd806d63b7c882ba60abffcc6e0f43498c40 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 13:54:42 +0100 Subject: [PATCH 0121/1995] Add ethereum events as a protocol tx --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9b1b59a4e9..5359186bc9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -8,6 +8,7 @@ mod prepare_block { use anoma::types::ethereum_events::vote_extensions::{ MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; + use anoma::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; @@ -87,7 +88,12 @@ mod prepare_block { _ => return vec![], }; - todo!() + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let tx_record = record::add(tx); + + vec![tx_record] } /// Builds a batch of mempool transactions From 1e489313817b465b45102b70223f3f2650ca8071 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 15:32:56 +0100 Subject: [PATCH 0122/1995] Two thirds of the voting power --- shared/src/types/ethereum_events/vote_extensions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 9c9f207f0c..9ad155982e 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -40,6 +40,10 @@ impl VoteExtension { pub struct FractionalVotingPower(Ratio); impl FractionalVotingPower { + /// Two thirds of the voting power. + pub const TWO_THIRDS: FractionalVotingPower = + FractionalVotingPower(Ratio::new_raw(2, 3)); + /// Create a new FractionalVotingPower. It must be between zero and one /// inclusive. pub fn new(numer: u64, denom: u64) -> Result { From 722b26ca217419c7ded340d8bf47a77664c432ef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 15:33:33 +0100 Subject: [PATCH 0123/1995] Allow dead code on get_validator_voting_power() --- apps/src/lib/node/ledger/shell/queries.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 6fbfc4a4e1..cea16fd4da 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -430,6 +430,7 @@ where /// Get the voting power of this node (as a fraction of this epochs total /// voting power) if it is a validator. Else return None #[cfg(not(feature = "ABCI"))] + #[allow(dead_code)] pub fn get_validator_voting_power( &self, ) -> std::result::Result { From aa840d54dc0535a66fb30c8669ea05a61f62e7aa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 15:34:04 +0100 Subject: [PATCH 0124/1995] Check that we have 2/3 voting power on vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 81 ++++++++++++++----- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5359186bc9..4a0ba6f613 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,8 +6,10 @@ mod prepare_block { use anoma::proto::Signed; use anoma::types::ethereum_events::vote_extensions::{ - MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, + FractionalVotingPower, MultiSignedEthEvent, VoteExtension, + VoteExtensionDigest, }; + use anoma::types::storage::BlockHeight; use anoma::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -42,8 +44,11 @@ mod prepare_block { // TODO: add some info logging // add ethereum events as protocol txs - let mut txs = - self.build_vote_extensions_txs(req.local_last_commit); + let last_height = BlockHeight((req.height - 1) as u64); + let mut txs = self.build_vote_extensions_txs( + last_height, + req.local_last_commit, + ); // add mempool txs let mut mempool_txs = self.build_mempool_txs(req.txs); @@ -68,6 +73,7 @@ mod prepare_block { /// events fn build_vote_extensions_txs( &mut self, + last_height: BlockHeight, local_last_commit: Option, ) -> Vec { let protocol_key = self @@ -76,9 +82,9 @@ mod prepare_block { .expect("Validators should always have a protocol key"); let vote_extension_digest = - local_last_commit.map(|local_last_commit| { + local_last_commit.and_then(|local_last_commit| { let votes = local_last_commit.votes; - self.compress_vote_extensions(votes) + self.compress_vote_extensions(last_height, votes) }); let vote_extension_digest = match vote_extension_digest { Some(d) => d, @@ -139,11 +145,11 @@ mod prepare_block { /// `Signed` instances in the process fn compress_vote_extensions( &self, + last_height: BlockHeight, vote_extensions: Vec, - ) -> VoteExtensionDigest { - let all_vote_extensions = vote_extensions - .into_iter() - .filter_map(|vote| { + ) -> Option { + let all_vote_extensions = + vote_extensions.into_iter().filter_map(|vote| { let vote_extension = Signed::::try_from_slice( &vote.vote_extension[..], @@ -171,34 +177,61 @@ mod prepare_block { ); }) .ok()?; - Some((validator_addr, vote_extension)) - }) - .filter(|(validator_addr, vote_extension)| { - // TODO: - // - verify 2/3 stake of vote_extension - // - verify block height of vote_extension // verify signature of the vote extension let result = self.get_validator_from_address(&validator_addr, None); let validator_public_key = match result { Ok((_, validator_public_key)) => validator_public_key, + // TODO: improve this code Err(_) => { tracing::error!( "Could not get public key from Storage for \ validator {}", validator_addr ); - return false; + return None; } }; - vote_extension.verify(&validator_public_key).is_ok() + + // TODO: log invalid signature + vote_extension.verify(&validator_public_key).ok()?; + + Some((validator_addr, vote_extension)) }); + // TODO: + // [x] verify 2/3 stake of vote_extension + // [ ] verify block height of vote_extension + let mut event_observers = BTreeMap::new(); let mut signatures = Vec::new(); + let events_epoch = self + .storage + .block + .pred_epochs + .get_epoch(last_height) + // TODO: is this `unwrap()` fine? + .unwrap(); + let total_voting_power = + self.get_total_voting_power(Some(events_epoch)).into(); + let mut voting_power = 0u64; + for (validator_addr, vote_extension) in all_vote_extensions { + let (validator_voting_power, _) = self + .get_validator_from_address( + &validator_addr, + Some(events_epoch), + ) + .expect( + "We already checked that we have a valid Tendermint \ + address", + ); + + // update voting power + voting_power += u64::from(validator_voting_power); + // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { let signers = @@ -214,12 +247,24 @@ mod prepare_block { signatures.push((sig, addr)); } + let voting_power = + FractionalVotingPower::new(voting_power, total_voting_power) + .unwrap(); + + if voting_power <= FractionalVotingPower::TWO_THIRDS { + tracing::error!( + "Tendermint has decided on a block including vote \ + extensions reflecting less than 2/3 of the total stake" + ); + return None; + } + let events = event_observers .into_iter() .map(|(event, signers)| MultiSignedEthEvent { event, signers }) .collect(); - VoteExtensionDigest { events, signatures } + Some(VoteExtensionDigest { events, signatures }) } } From 48e5c386d73f92ef4120ad33225d2f95e0e688e1 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 17:15:06 +0200 Subject: [PATCH 0125/1995] [feat]: Added processing vote extensions to ProcessProposal. Need to add tests --- apps/src/lib/node/ledger/shell/mod.rs | 1 + .../lib/node/ledger/shell/process_proposal.rs | 242 +++++++++++------- .../lib/node/ledger/shell/vote_extensions.rs | 28 +- .../types/ethereum_events/vote_extensions.rs | 31 ++- 4 files changed, 195 insertions(+), 107 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 84a0677ab7..302518530e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -134,6 +134,7 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, + InvalidVoteExntension = 7, } impl From for u32 { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1902301ba4..f24085237e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use anoma::types::transaction::protocol::ProtocolTxType; +use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; +use anoma::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -34,16 +35,51 @@ where &mut self, req: RequestProcessProposal, ) -> ResponseProcessProposal { + // the number of vote extension digests included in the block proposal + let mut vote_ext_digest_num = 0; let tx_results: Vec = req .txs .iter() .map(|tx_bytes| { - ExecTxResult::from(self.process_single_tx(tx_bytes)) + ExecTxResult::from(match Tx::try_from(tx_bytes.as_slice()) { + Ok(tx) => match process_tx(tx) { + // This occurs if the wrapper / protocol tx signature is invalid + Err(err) => TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + }, + Ok(tx) => { + if let TxType::Protocol(ProtocolTx{tx: ProtocolTxType::EthereumEvents(_), ..}) = &tx { + vote_ext_digest_num += 1; + // genesis block should not have vote extensions + if self.storage.last_height.0 == 0 { + TxResult { + code: ErrorCodes::InvalidVoteExntension.into(), + info: "No vote extensions should be included in block height 0".into() + } + } else { + self.process_single_tx(tx) + } + } else { + self.process_single_tx(tx) + } + } + } + Err(_) => { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The submitted transaction was not deserializable" + .into(), + } + } + }) }) .collect(); ResponseProcessProposal { - status: if tx_results.iter().any(|res| res.code > 3) { + status: if vote_ext_digest_num <= 1 + && tx_results.iter().any(|res| res.code > 3) + { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 @@ -71,112 +107,144 @@ 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 { - let tx = match Tx::try_from(tx_bytes) { - Ok(tx) => tx, - Err(_) => { - return TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The submitted transaction was not deserializable" - .into(), - }; - } - }; + pub(crate) fn process_single_tx(&mut self, tx: TxType) -> TxResult { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - - match process_tx(tx) { - // This occurs if the wrapper / protocol tx signature is invalid - Err(err) => TxResult { - code: ErrorCodes::InvalidSig.into(), - info: err.to_string(), + let hash = hash_tx(&Tx::from(tx.clone()).to_bytes()); + match tx { + // If it is a raw transaction, we do no further validation + TxType::Raw(_) => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Transaction rejected: Non-encrypted transactions are \ + not supported" + .into(), }, - Ok(result) => match result { - // If it is a raw transaction, we do no further validation - TxType::Raw(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Transaction rejected: Non-encrypted transactions \ - are not supported" - .into(), - }, - TxType::Protocol(protocol_tx) => match protocol_tx.tx { - ProtocolTxType::EthereumEvents(_) => TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this transaction" - .into(), - }, - _ => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Unsupported protocol transaction type".into(), - }, - }, - TxType::Decrypted(tx) => match self.next_wrapper() { - Some(wrapper) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { + TxType::Protocol(protocol_tx) => match protocol_tx.tx { + ProtocolTxType::EthereumEvents(digest) => { + let extensions = + digest.decompress(self.storage.last_height); + let mut voting_power = FractionalVotingPower::default(); + // the subtraction underflow check is handled at a higher + // scope + let epoch = + self.storage.block.pred_epochs.get_epoch(BlockHeight( + self.storage.last_height.0 - 1, + )); + let total_power = + u64::from(self.get_total_voting_power(epoch)); + if extensions.into_iter().all(|(ext, validator)| match self + .get_validator_from_address(&validator, epoch) + { + Ok((power, _)) => { + voting_power += FractionalVotingPower::new( + u64::from(power), + total_power, + ) + .unwrap_or_default(); + self.validate_vote_extension( + ext, + validator, + self.storage.last_height, + ) + } + _ => false, + }) { + if voting_power + > FractionalVotingPower::new(2, 3).unwrap() + { TxResult { code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ + info: "Process proposal accepted this \ transaction" .into(), } } else { TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" + code: ErrorCodes::InvalidVoteExntension.into(), + info: "Process proposal rejected this \ + proposal because the backing stake of \ + the vote extensions published in the \ + proposal was insufficient" .into(), } } + } else { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal rejected this proposal \ + because the at least on of the vote \ + extensions included was invalid." + .into(), + } } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected" - .into(), - }, + } + _ => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Unsupported protocol transaction type".into(), }, - TxType::Wrapper(tx) => { - // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + }, + TxType::Decrypted(tx) => match self.next_wrapper() { + Some(wrapper) => { + if wrapper.tx_hash != tx.hash_commitment() { TxResult { - code: ErrorCodes::InvalidTx.into(), - info: format!( - "The ciphertext of the wrapped tx {} is \ - invalid", - hash_tx(tx_bytes) - ), + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), } } else { - // check that the fee payer has sufficient balance - let balance = self - .get_balance(&tx.fee.token, &tx.fee_payer()) - .unwrap_or_default(); - - if tx.fee.amount <= balance { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The address given does not have \ - sufficient balance to pay fee" - .into(), - } + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The encrypted payload of tx was \ + incorrectly marked as un-decryptable" + .into(), } } } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected".into(), + }, }, + TxType::Wrapper(wrapper) => { + // validate the ciphertext via Ferveo + if !wrapper.validate_ciphertext() { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!( + "The ciphertext of the wrapped tx {} is invalid", + hash + ), + } + } else { + // check that the fee payer has sufficient balance + let balance = self + .get_balance(&wrapper.fee.token, &wrapper.fee_payer()) + .unwrap_or_default(); + + if wrapper.fee.amount <= balance { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this transaction" + .into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The address given does not have sufficient \ + balance to pay fee" + .into(), + } + } + } + } } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 1d3c4a3363..10e20c0f9d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,12 +1,12 @@ #[cfg(not(feature = "ABCI"))] mod extend_votes { use anoma::proto::Signed; + use anoma::types::address::Address; use anoma::types::ethereum_events::vote_extensions::VoteExtension; use borsh::BorshDeserialize; use super::super::*; - - type SignedExt = Signed; + pub type SignedExt = Signed; impl Shell where @@ -40,14 +40,17 @@ mod extend_votes { &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - let validator_addr = req.validator_address.as_slice(); - if let Ok(signed) = - SignedExt::try_from_slice(&req.vote_extension[..]) + let addr = self.get_validator_from_tm_address( + req.validator_address.as_slice(), + None, + ); + if let (Ok(signed), Ok(validator)) = + (SignedExt::try_from_slice(&req.vote_extension[..]), addr) { response::VerifyVoteExtension { status: if self.validate_vote_extension( signed, - validator_addr, + validator, self.storage.last_height + 1, ) { VerifyStatus::Accept.into() @@ -71,17 +74,10 @@ mod extend_votes { pub fn validate_vote_extension( &self, ext: SignedExt, - tm_address: &[u8], + validator: Address, height: BlockHeight, ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); - // get the validator that issued this vote extension - // this should not fail - let validator = - match self.get_validator_from_tm_address(tm_address, epoch) { - Ok(address) => address, - _ => return false, - }; // get the public key associated with this validator let pk = match self.get_validator_from_address(&validator, epoch) { Ok((_, pk)) => pk, @@ -333,11 +329,9 @@ mod extend_votes { .is_ok() ); - let tm_address = - address.raw_hash().expect("Test failed").as_bytes().to_vec(); assert!(shell.validate_vote_extension( vote_ext, - &tm_address, + address, signed_height )); } diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index c76a659f6f..cb11c35c9a 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -2,6 +2,7 @@ //! in vote extensions. use std::collections::HashSet; +use std::ops::{Add, AddAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -61,12 +62,32 @@ impl FractionalVotingPower { } } +impl Default for FractionalVotingPower { + fn default() -> Self { + Self::new(0, 1).unwrap() + } +} + impl From<&FractionalVotingPower> for (u64, u64) { fn from(ratio: &FractionalVotingPower) -> Self { (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) } } +impl Add for FractionalVotingPower { + type Output = Self; + + fn add(self, rhs: FractionalVotingPower) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FractionalVotingPower { + fn add_assign(&mut self, rhs: FractionalVotingPower) { + *self = Self(self.0 + rhs.0) + } +} + impl BorshSerialize for FractionalVotingPower { fn serialize( &self, @@ -130,7 +151,7 @@ impl VoteExtensionDigest { pub fn decompress( self, last_height: BlockHeight, - ) -> Vec> { + ) -> Vec<(Signed, Address)> { let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -151,7 +172,7 @@ impl VoteExtensionDigest { ext.ethereum_events.sort(); let signed = Signed { data: ext, sig }; - extensions.push(signed); + extensions.push((signed, addr)); } extensions } @@ -284,7 +305,11 @@ mod tests { // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` - let decompressed = digest.decompress(last_block_height); + let decompressed = digest + .decompress(last_block_height) + .into_iter() + .map(|event| event.0) + .collect::>>(); assert_eq!(ext, decompressed); assert!(decompressed[0].verify(&sk_1.ref_to()).is_ok()); From 3c12711e254c646cb260e642138cb293682fd5ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 16:54:46 +0100 Subject: [PATCH 0126/1995] Add a TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4a0ba6f613..7e24fe41c5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -303,6 +303,7 @@ mod prepare_block { } #[cfg(test)] + // TODO: write tests for ethereum events on prepare proposal mod test_prepare_proposal { use anoma::types::address::xan; use anoma::types::storage::Epoch; From d68a88b9ae037ce14699b7fbff69c8128c907eea Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 20 Jul 2022 22:39:52 +0100 Subject: [PATCH 0127/1995] Last block height logic --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7e24fe41c5..c21502c810 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -46,6 +46,10 @@ mod prepare_block { // add ethereum events as protocol txs let last_height = BlockHeight((req.height - 1) as u64); let mut txs = self.build_vote_extensions_txs( + // TODO: maybe get last block height + // from `self.storage.last_height`, + // in which case we don't even need + // to pass a parameter here last_height, req.local_last_commit, ); @@ -87,6 +91,12 @@ mod prepare_block { self.compress_vote_extensions(last_height, votes) }); let vote_extension_digest = match vote_extension_digest { + Some(_) if last_height.0 == 0 => { + tracing::error!( + "The genesis block should not contain vote extensions" + ); + return vec![]; + } Some(d) => d, // if no vote extensions were found, we return an empty // `Vec` of protocol From 2f6b788d4b398c052d6a5133361962fec121358f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 09:28:20 +0100 Subject: [PATCH 0128/1995] Get last block height from storage --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c21502c810..61432456a2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,7 +9,6 @@ mod prepare_block { FractionalVotingPower, MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; - use anoma::types::storage::BlockHeight; use anoma::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -44,15 +43,8 @@ mod prepare_block { // TODO: add some info logging // add ethereum events as protocol txs - let last_height = BlockHeight((req.height - 1) as u64); - let mut txs = self.build_vote_extensions_txs( - // TODO: maybe get last block height - // from `self.storage.last_height`, - // in which case we don't even need - // to pass a parameter here - last_height, - req.local_last_commit, - ); + let mut txs = + self.build_vote_extensions_txs(req.local_last_commit); // add mempool txs let mut mempool_txs = self.build_mempool_txs(req.txs); @@ -77,7 +69,6 @@ mod prepare_block { /// events fn build_vote_extensions_txs( &mut self, - last_height: BlockHeight, local_last_commit: Option, ) -> Vec { let protocol_key = self @@ -88,10 +79,10 @@ mod prepare_block { let vote_extension_digest = local_last_commit.and_then(|local_last_commit| { let votes = local_last_commit.votes; - self.compress_vote_extensions(last_height, votes) + self.compress_vote_extensions(votes) }); let vote_extension_digest = match vote_extension_digest { - Some(_) if last_height.0 == 0 => { + Some(_) if self.storage.last_height.0 == 0 => { tracing::error!( "The genesis block should not contain vote extensions" ); @@ -155,7 +146,6 @@ mod prepare_block { /// `Signed` instances in the process fn compress_vote_extensions( &self, - last_height: BlockHeight, vote_extensions: Vec, ) -> Option { let all_vote_extensions = @@ -221,7 +211,7 @@ mod prepare_block { .storage .block .pred_epochs - .get_epoch(last_height) + .get_epoch(self.storage.last_height) // TODO: is this `unwrap()` fine? .unwrap(); let total_voting_power = From 2bccd47af458ca5d8ce15c7cefc08803f35adbc4 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:47:06 +0200 Subject: [PATCH 0129/1995] Update apps/src/lib/node/ledger/shell/mod.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 84a0677ab7..1b2764c5b6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -351,7 +351,7 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, - ethereum_recv: EthereumReciever::new( + ethereum_recv: EthereumReceiver::new( eth_receiver.unwrap(), ), }) From 7489140830da13f5f5e69e5205cf960d95120a66 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:49:16 +0200 Subject: [PATCH 0130/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 1d3c4a3363..78a1ba674d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -52,6 +52,12 @@ mod extend_votes { ) { VerifyStatus::Accept.into() } else { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "received vote extension that didn't validate" + ); VerifyStatus::Reject.into() }, } From 73142ea42db2f7e79a9614ecc2a538d34eb9cc1c Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:54:58 +0200 Subject: [PATCH 0131/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 78a1ba674d..19f32b3d84 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -62,9 +62,15 @@ mod extend_votes { }, } } else { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "received undeserializable vote extension" + ); response::VerifyVoteExtension { status: VerifyStatus::Reject.into(), - } + } } } From 593c48d15db6be22c6c7dbb141de02ebd74d8073 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 09:55:26 +0100 Subject: [PATCH 0132/1995] Check block height of vote extension --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 61432456a2..cc29161dbb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -161,7 +161,23 @@ mod prepare_block { err ); }) - .ok()?; + .ok() + .and_then(|ext| { + let ext_height = ext.data.block_height; + let last_height = self.storage.last_height; + + if ext_height == last_height { + Some(ext) + } else { + tracing::error!( + "Vote extension issued for a block height \ + {ext_height} different from the last \ + height {last_height}" + ); + None + } + })?; + let validator = vote.validator.or_else(|| { tracing::error!("Vote extension has no validator data"); None @@ -200,10 +216,6 @@ mod prepare_block { Some((validator_addr, vote_extension)) }); - // TODO: - // [x] verify 2/3 stake of vote_extension - // [ ] verify block height of vote_extension - let mut event_observers = BTreeMap::new(); let mut signatures = Vec::new(); From 5795fcfc383aea2a770357ceac795a58c2bd0966 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 21 Jul 2022 10:55:56 +0200 Subject: [PATCH 0133/1995] [fix]: Replaced ethereum events queue with btreeset. Added logging --- apps/src/lib/node/ledger/shell/mod.rs | 16 +++++++++------- apps/src/lib/node/ledger/shell/queries.rs | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1b2764c5b6..bc10a0a1e6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -13,7 +13,7 @@ mod process_proposal; mod queries; mod vote_extensions; -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use std::convert::{TryFrom, TryInto}; use std::mem; use std::path::{Path, PathBuf}; @@ -180,7 +180,7 @@ pub(super) enum ShellMode { #[derive(Debug)] pub(super) struct EthereumReceiver { channel: UnboundedReceiver, - queue: Vec, + queue: BTreeSet, } impl EthereumReceiver { @@ -189,7 +189,7 @@ impl EthereumReceiver { pub fn new(channel: UnboundedReceiver) -> Self { Self { channel, - queue: vec![], + queue: BTreeSet::new(), } } @@ -197,16 +197,18 @@ impl EthereumReceiver { /// Since vote extensions require ordering of ethereum /// events, we do that here. We also de-duplicate events pub fn fill_queue(&mut self) { + let mut new_events = 0; while let Ok(eth_event) = self.channel.try_recv() { - self.queue.push(eth_event); + if self.queue.insert(eth_event) { + new_events += 1; + }; } - self.queue.sort(); - self.queue.dedup(); + tracing::debug!(n = new_events, "received Ethereum events"); } /// Get a copy of the queue pub fn get_events(&self) -> Vec { - self.queue.clone() + self.queue.iter().cloned().collect() } /// Given a list of events, remove them from the queue if present diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 8e6903017b..c903824951 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -426,6 +426,8 @@ where /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native /// address from storage using this hash. + /// TODO: We may change how this lookup is done, see + /// https://github.com/anoma/namada/issues/200 pub fn get_validator_from_tm_address( &self, tm_address: &[u8], From 7ed8e4f30b59d7050e491fe2e6769cf185a49463 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 10:06:11 +0100 Subject: [PATCH 0134/1995] Apply changes from bat/ethbridge/vote-extensions --- apps/src/lib/node/ledger/shell/queries.rs | 56 ++++------------------- 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cea16fd4da..8e6903017b 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -6,7 +6,6 @@ use anoma::ledger::parameters::EpochDuration; use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; use anoma::ledger::pos::PosParams; use anoma::types::address::Address; -use anoma::types::ethereum_events::vote_extensions::FractionalVotingPower; use anoma::types::key; use anoma::types::key::dkg_session_keys::DkgPublicKey; use anoma::types::storage::{Epoch, Key, PrefixValue}; @@ -30,7 +29,6 @@ use super::*; use crate::node::ledger::response; #[derive(Error, Debug)] -#[allow(dead_code)] pub enum Error { #[error( "The address '{:?}' is not among the active validator set for epoch \ @@ -41,16 +39,13 @@ pub enum Error { "The public key '{0}' is not among the active validator set for epoch \ {1}" )] + #[allow(dead_code)] NotValidatorKey(String, Epoch), #[error( "The public key hash '{0}' is not among the active validator set for \ epoch {1}" )] NotValidatorKeyHash(String, Epoch), - #[error("There are currently no staked validators")] - NoStakingValidators, - #[error("This node is not a validator")] - NotValidator, #[error("Invalid validator tendermint address")] InvalidTMAddress, } @@ -409,6 +404,7 @@ where /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] + #[allow(dead_code)] pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); @@ -427,61 +423,25 @@ where .unwrap_or_default() } - /// Get the voting power of this node (as a fraction of this epochs total - /// voting power) if it is a validator. Else return None - #[cfg(not(feature = "ABCI"))] - #[allow(dead_code)] - pub fn get_validator_voting_power( - &self, - ) -> std::result::Result { - let power = if let Some(secret_key) = self.mode.get_protocol_key() { - self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) - .map(|validator| validator.power)? - } else { - return Err(Error::NotValidator); - }; - let total = u64::from(self.get_total_voting_power(None)); - if total > 0 { - Ok(FractionalVotingPower::new(power, total).unwrap()) - } else { - Err(Error::NoStakingValidators) - } - } - /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native - /// address from storage using this hash. Returns an error - /// if no matching validator is found in the active validator - /// set. + /// address from storage using this hash. pub fn get_validator_from_tm_address( &self, tm_address: &[u8], + epoch: Option, ) -> std::result::Result { - let epoch = self.storage.get_current_epoch().0; + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); let validator_raw_hash = core::str::from_utf8(tm_address) .map_err(|_| Error::InvalidTMAddress)?; - let address = self - .storage + self.storage .read_validator_address_raw_hash(&validator_raw_hash) .ok_or_else(|| { Error::NotValidatorKeyHash( validator_raw_hash.to_string(), epoch, ) - })?; - if self - .storage - .read_validator_set() - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .iter() - .find(|validator| &address == &validator.address) - .is_some() - { - Ok(address) - } else { - Err(Error::NotValidatorAddress(address, epoch)) - } + }) } } From 325789016700ac0b7bae7f4caf742cfdfccff500 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 10:07:10 +0100 Subject: [PATCH 0135/1995] Fetch the epoch of the prev block height when checking validator addrs --- .../lib/node/ledger/shell/prepare_proposal.rs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index cc29161dbb..1e4a202b2f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -148,6 +148,14 @@ mod prepare_block { &self, vote_extensions: Vec, ) -> Option { + let events_epoch = self + .storage + .block + .pred_epochs + .get_epoch(self.storage.last_height) + // TODO: is this `unwrap()` fine? + .unwrap(); + let all_vote_extensions = vote_extensions.into_iter().filter_map(|vote| { let vote_extension = @@ -183,7 +191,10 @@ mod prepare_block { None })?; let validator_addr = self - .get_validator_from_tm_address(&validator.address[..]) + .get_validator_from_tm_address( + &validator.address[..], + Some(events_epoch), + ) .map_err(|err| { tracing::error!( "Failed to get an address from Tendermint \ @@ -195,8 +206,10 @@ mod prepare_block { .ok()?; // verify signature of the vote extension - let result = - self.get_validator_from_address(&validator_addr, None); + let result = self.get_validator_from_address( + &validator_addr, + Some(events_epoch), + ); let validator_public_key = match result { Ok((_, validator_public_key)) => validator_public_key, // TODO: improve this code @@ -219,13 +232,6 @@ mod prepare_block { let mut event_observers = BTreeMap::new(); let mut signatures = Vec::new(); - let events_epoch = self - .storage - .block - .pred_epochs - .get_epoch(self.storage.last_height) - // TODO: is this `unwrap()` fine? - .unwrap(); let total_voting_power = self.get_total_voting_power(Some(events_epoch)).into(); let mut voting_power = 0u64; From 7eb063263c04efe4917673e60c1a5df735201801 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 10:12:03 +0100 Subject: [PATCH 0136/1995] Log invalid vote extension signatures --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1e4a202b2f..31666b3f79 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -223,8 +223,15 @@ mod prepare_block { } }; - // TODO: log invalid signature - vote_extension.verify(&validator_public_key).ok()?; + vote_extension + .verify(&validator_public_key) + .map_err(|_| { + tracing::error!( + "Failed to verify the signature of a vote \ + extension issued by {validator_addr}" + ); + }) + .ok()?; Some((validator_addr, vote_extension)) }); From 7ca0155ef7e081634935d9d07403b916e451335e Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 00:48:24 +0200 Subject: [PATCH 0137/1995] [feat]: Added in vote extensions that include ethereum events. --- apps/src/lib/node/ledger/shell/mod.rs | 23 +- apps/src/lib/node/ledger/shell/queries.rs | 86 +++- .../lib/node/ledger/shell/vote_extensions.rs | 469 ++++++++++++++++++ 3 files changed, 555 insertions(+), 23 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/vote_extensions.rs diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index dcff531038..26b171dc92 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 vote_extensions; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; @@ -33,6 +34,8 @@ use namada::ledger::storage::{ use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; +#[cfg(not(feature="ABCI"))] +use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key}; @@ -542,26 +545,6 @@ where } } - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn extend_vote( - &self, - _req: request::ExtendVote, - ) -> response::ExtendVote { - Default::default() - } - - #[cfg(not(feature = "ABCI"))] - /// INVARIANT: This method must be stateless. - pub fn verify_vote_extension( - &self, - _req: request::VerifyVoteExtension, - ) -> response::VerifyVoteExtension { - response::VerifyVoteExtension { - status: VerifyStatus::Accept as i32, - } - } - /// Commit a block. Persist the application state and return the Merkle root /// hash. pub fn commit(&mut self) -> response::Commit { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 71ad018a2f..d6f63c76df 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -4,11 +4,13 @@ use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::parameters::EpochDuration; +#[cfg(not(feature = "ABCI"))] +use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; 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::storage::{Epoch, Key, PrefixValue}; use namada::types::token::{self, Amount}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::{ProofOp, ProofOps}; @@ -299,16 +301,17 @@ where pub fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, + epoch: Option, ) -> Option> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); // get the current epoch - let (current_epoch, _) = self.storage.get_current_epoch(); + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() - .get(current_epoch) + .get(epoch) .expect("Validators for the next epoch should be known") .active .iter() @@ -342,4 +345,81 @@ where } }) } + + /// Lookup data about a validator from their address + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Option<(VotingPower, common::PublicKey)> { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for the next epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .storage + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.voting_power, protocol_pk) + }) + } + + /// Lookup the total voting power for an epoch + #[cfg(not(feature = "ABCI"))] + pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + // get the active validator set + self.storage + .read_validator_set() + .get(epoch) + .map(|validators| { + validators + .active + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + }) + .unwrap_or_default() + } + + /// Get the voting power of this node (as a fraction of this epochs total + /// voting power) if it is a validator. Else return None + #[cfg(not(feature = "ABCI"))] + pub fn get_validator_voting_power(&self) -> Option { + let power = if let Some(secret_key) = self.mode.get_protocol_key() { + match self + .get_validator_from_protocol_pk(&secret_key.ref_to(), None) + { + Some(validator) => Some(validator.power), + _ => None, + } + } else { + None + }; + let total = u64::from(self.get_total_voting_power(None)); + match power { + Some(power) if total > 0 => { + FractionalVotingPower::new(power, total).ok() + } + _ => None, + } + } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs new file mode 100644 index 0000000000..e0236052eb --- /dev/null +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -0,0 +1,469 @@ +#[cfg(not(feature = "ABCI"))] +mod extend_votes { + use anoma::types::ethereum_events::vote_extensions::{ + EpochPower, SignedEthEvent, SignedEvent, + }; + + use super::super::*; + + /// The data we include in a vote extension + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] + pub struct VoteExtension { + /// Ethereum events seen since last round + ethereum_events: Vec, + } + + impl Shell + where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, + { + /// INVARIANT: This method must be stateless. + pub fn extend_vote( + &mut self, + _req: request::ExtendVote, + ) -> response::ExtendVote { + response::ExtendVote { + vote_extension: VoteExtension { + ethereum_events: self.new_ethereum_events(), + } + .try_to_vec() + .unwrap(), + } + } + + /// At present this checks the signature on all Ethereum headers + /// + /// INVARIANT: This method must be stateless. + pub fn verify_vote_extension( + &self, + req: request::VerifyVoteExtension, + ) -> response::VerifyVoteExtension { + if let Ok(VoteExtension { ethereum_events }) = + VoteExtension::try_from_slice(&req.vote_extension[..]) + { + return if ethereum_events.iter().all(|event| { + self.validate_ethereum_event( + self.storage.last_height + 1, + event, + ) + }) { + response::VerifyVoteExtension { + status: VerifyStatus::Accept.into(), + } + } else { + response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + } + }; + } + Default::default() + } + + /// Checks the channel from the Ethereum oracle monitoring + /// the fullnode and retrieves all messages sent. These are + /// signed and prepared for inclusion in a vote extension. + pub fn new_ethereum_events(&mut self) -> Vec { + let mut events = vec![]; + let voting_power = self.get_validator_voting_power(); + let address = self.mode.get_validator_address().cloned(); + if let ShellMode::Validator { + ref mut ethereum_recv, + data: + ValidatorData { + keys: + ValidatorKeys { + protocol_keypair, .. + }, + .. + }, + .. + } = &mut self.mode + { + let (voting_power, address) = + voting_power.zip(address).unwrap(); + while let Ok(eth_event) = ethereum_recv.try_recv() { + events.push(SignedEthEvent::new( + eth_event, + address.clone(), + voting_power.clone(), + self.storage.last_height + 1, + protocol_keypair, + )); + } + } + events + } + + /// Verify that each ethereum header in a vote extension was signed by + /// a validator in the correct epoch, the stated voting power is + /// correct, and the signature is correct. + pub fn validate_ethereum_event( + &self, + height: BlockHeight, + event: &impl SignedEvent, + ) -> bool { + let epoch = self.storage.block.pred_epochs.get_epoch(height); + let total_voting_power = self.get_total_voting_power(epoch); + + // Get the public keys of each validator. Filter out those that + // inaccurately stated their voting power at a given block height + let public_keys: Vec = event + .get_voting_powers() + .into_iter() + .filter_map( + |EpochPower { + validator, + voting_power, + block_height, + }| { + if block_height != height { + return None; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + FractionalVotingPower::new( + power, + total_voting_power, + ) + .ok() + .and_then(|power| { + if power == voting_power { + Some(pk) + } else { + None + } + }) + } else { + None + } + }, + ) + .collect(); + // check that we found all the public keys and + // check that the signatures are valid + public_keys.len() == event.number_of_signers() + && event.verify_signatures(&public_keys).is_ok() + } + } + + #[cfg(test)] + mod test_vote_extensions { + use std::convert::TryInto; + + use anoma::ledger::pos; + use anoma::ledger::pos::anoma_proof_of_stake::PosBase; + use anoma::types::ethereum_events::vote_extensions::{ + FractionalVotingPower, MultiSignedEthEvent, SignedEthEvent, + }; + use anoma::types::ethereum_events::{ + EthAddress, EthereumEvent, TransferToEthereum, + }; + use anoma::types::key::*; + use anoma::types::storage::{BlockHeight, Epoch}; + use borsh::{BorshDeserialize, BorshSerialize}; + use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + use tower_abci::request; + + use crate::node::ledger::shell::test_utils::*; + use crate::node::ledger::shell::vote_extensions::VoteExtension; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + + /// Test that we successfully receive ethereum events + /// from the channel to fullnode process + /// + /// We further check that ledger side buffering is done if multiple + /// events are in the channel + #[test] + fn test_get_eth_events() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = shell + .new_ethereum_events() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + } + + /// Test that ethereum events are added to vote extensions. + /// Check that vote extensions pass verification. + #[test] + fn test_eth_events_vote_extension() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let vote_extension: VoteExtension = + BorshDeserialize::try_from_slice( + &shell.extend_vote(Default::default()).vote_extension[..], + ) + .expect("Test failed"); + + let [event_first, event_second]: [EthereumEvent; 2] = + vote_extension + .ethereum_events + .clone() + .into_iter() + .map(|signed| signed.event.data.0) + .collect::>() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: vec![], + height: 0, + vote_extension: vote_extension + .try_to_vec() + .expect("Test failed"), + }; + let res = shell.verify_vote_extension(req); + assert_eq!(res.status, i32::from(VerifyStatus::Accept)); + } + + /// Test that Ethereum headers signed by a non-validator is rejected + #[test] + fn test_eth_events_must_be_signed_by_validator() { + let (shell, _, _) = setup(); + let signing_key = gen_keypair(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that validation of vote extensions cast during the + /// previous block are accepted for the current block. This + /// should pass even if the epoch changed resulting in a + /// change to the validator set. + #[test] + fn test_validate_vote_extensions() { + let (mut shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let voting_power = + shell.get_validator_voting_power().expect("Test failed"); + let height = shell.storage.last_height + 1; + + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height + 1, + &signing_key, + ); + assert_eq!(shell.storage.get_current_epoch().0.0, 0); + // We make a change so that there are no + // validators in the next epoch + let mut current_validators = shell.storage.read_validator_set(); + current_validators.data.insert( + 1, + Some(pos::types::ValidatorSet { + active: Default::default(), + inactive: Default::default(), + }), + ); + shell.storage.write_validator_set(¤t_validators); + // we advance forward to the next epoch + let mut req = FinalizeBlock::default(); + req.header.time = anoma::types::time::DateTimeUtc::now(); + shell.storage.last_height = BlockHeight(11); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + assert_eq!(shell.storage.get_current_epoch().0.0, 1); + assert!( + shell + .get_validator_from_protocol_pk(&signing_key.ref_to(), None) + .is_none() + ); + let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); + assert!( + shell + .shell + .get_validator_from_protocol_pk( + &signing_key.ref_to(), + Some(prev_epoch) + ) + .is_some() + ); + assert!(shell.validate_ethereum_event(height, &signed_event)); + assert!(shell.validate_ethereum_event( + height, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that if the declared voting power is not correct, + /// the signed event is rejected + #[test] + fn reject_incorrect_voting_power() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = 99u64; + let total_voting_power = + u64::from(shell.get_total_voting_power(None)); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + FractionalVotingPower::new(voting_power, total_voting_power) + .expect("Test failed"), + shell.storage.last_height + 1, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event that incorrectly labels what block it was + /// included in a vote extension on is rejected + #[test] + fn reject_incorrect_block_number() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let address = shell.mode.get_validator_address().unwrap().clone(); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + address, + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + + /// Test that that an event with an incorrect address + /// included in a vote extension is rejected + #[test] + fn reject_incorrect_address() { + let (shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed"); + let voting_power = shell.get_validator_voting_power().unwrap(); + let signed_event = SignedEthEvent::new( + EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }, + crate::wallet::defaults::bertha_address(), + voting_power, + shell.storage.last_height, + signing_key, + ); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &signed_event + )); + assert!(!shell.validate_ethereum_event( + shell.storage.last_height + 1, + &MultiSignedEthEvent::from(signed_event) + )); + } + } +} + +#[cfg(not(feature = "ABCI"))] +pub use extend_votes::*; From d7737c46000c2a7d496f8f04a17175fda34364aa Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 12 Jul 2022 11:40:54 +0200 Subject: [PATCH 0138/1995] [cleanup]: Refactored some ugly functional code that was bugging me --- .../lib/node/ledger/shell/vote_extensions.rs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e0236052eb..672781e5a6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -105,42 +105,33 @@ mod extend_votes { ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); let total_voting_power = self.get_total_voting_power(epoch); + if u64::from(total_voting_power) == 0 { + return false; + } // Get the public keys of each validator. Filter out those that // inaccurately stated their voting power at a given block height - let public_keys: Vec = event - .get_voting_powers() - .into_iter() - .filter_map( - |EpochPower { - validator, - voting_power, - block_height, - }| { - if block_height != height { - return None; - } - if let Some((power, pk)) = - self.get_validator_from_address(&validator, epoch) - { - FractionalVotingPower::new( - power, - total_voting_power, - ) - .ok() - .and_then(|power| { - if power == voting_power { - Some(pk) - } else { - None - } - }) - } else { - None - } - }, - ) - .collect(); + let mut public_keys = vec![]; + for EpochPower { + validator, + voting_power, + block_height, + } in event.get_voting_powers().into_iter() + { + if block_height != height { + continue; + } + if let Some((power, pk)) = + self.get_validator_from_address(&validator, epoch) + { + let power = + FractionalVotingPower::new(power, total_voting_power) + .unwrap(); + if power == voting_power { + public_keys.push(pk); + } + } + } // check that we found all the public keys and // check that the signatures are valid public_keys.len() == event.number_of_signers() From 801b0c60cd441efd405a613a4d313ef1b03a4160 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:28 +0200 Subject: [PATCH 0139/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d6f63c76df..0a65b691fa 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -353,7 +353,6 @@ where address: &Address, epoch: Option, ) -> Option<(VotingPower, common::PublicKey)> { - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From 4483aec81f1095161afefe6d5d058d6a4ebc0e85 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 12 Jul 2022 15:02:35 +0200 Subject: [PATCH 0140/1995] Update apps/src/lib/node/ledger/shell/queries.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0a65b691fa..1271848939 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -306,7 +306,6 @@ where let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage From ff83695bc4bc406e74e5b7c1f44bd2f0dc2aacf4 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 19 Jul 2022 15:26:32 +0200 Subject: [PATCH 0141/1995] [feat]: Added query method to get anoma native validator address from tendermint address --- apps/src/lib/node/ledger/shell/queries.rs | 95 +++++++++++++++++++---- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1271848939..cdb27be66e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -13,12 +13,16 @@ use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, Key, PrefixValue}; use namada::types::token::{self, Amount}; #[cfg(not(feature = "ABCI"))] +use tendermint_proto::abci::Validator; +#[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::abci::Validator; +#[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::{ProofOp, ProofOps}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf; @@ -28,6 +32,31 @@ use tendermint_proto_abci::types::EvidenceParams; use super::*; use crate::node::ledger::response; +#[derive(Error, Debug)] +pub enum Error { + #[error( + "The address '{:?}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorAddress(Address, Epoch), + #[error( + "The public key '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKey(String, Epoch), + #[error( + "The public key hash '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKeyHash(String, Epoch), + #[error("There are currently no staked validators")] + NoStakingValidators, + #[error("This node is not a validator")] + NotValidator, + #[error("Invalid validator tendermint address")] + InvalidTMAddress, +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -302,7 +331,7 @@ where &self, pk: &key::common::PublicKey, epoch: Option, - ) -> Option> { + ) -> std::result::Result, Error> { let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); @@ -311,7 +340,7 @@ where self.storage .read_validator_set() .get(epoch) - .expect("Validators for the next epoch should be known") + .expect("Validators for an epoch should be known") .active .iter() .find(|validator| { @@ -343,6 +372,7 @@ where public_key: dkg_publickey.into(), } }) + .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) } /// Lookup data about a validator from their address @@ -351,13 +381,13 @@ where &self, address: &Address, epoch: Option, - ) -> Option<(VotingPower, common::PublicKey)> { + ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); // get the active validator set self.storage .read_validator_set() .get(epoch) - .expect("Validators for the next epoch should be known") + .expect("Validators for an epoch should be known") .active .iter() .find(|validator| address == &validator.address) @@ -376,6 +406,7 @@ where ); (validator.voting_power, protocol_pk) }) + .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } /// Lookup the total voting power for an epoch @@ -401,23 +432,53 @@ where /// Get the voting power of this node (as a fraction of this epochs total /// voting power) if it is a validator. Else return None #[cfg(not(feature = "ABCI"))] - pub fn get_validator_voting_power(&self) -> Option { + pub fn get_validator_voting_power( + &self, + ) -> std::result::Result { let power = if let Some(secret_key) = self.mode.get_protocol_key() { - match self - .get_validator_from_protocol_pk(&secret_key.ref_to(), None) - { - Some(validator) => Some(validator.power), - _ => None, - } + self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) + .map(|validator| validator.power)? } else { - None + return Err(Error::NotValidator); }; let total = u64::from(self.get_total_voting_power(None)); - match power { - Some(power) if total > 0 => { - FractionalVotingPower::new(power, total).ok() - } - _ => None, + if total > 0 { + Ok(FractionalVotingPower::new(power, total).unwrap()) + } else { + Err(Error::NoStakingValidators) + } + } + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. Returns an error + /// if no matching validator is found in the active validator + /// set. + pub fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + ) -> std::result::Result { + let epoch = self.storage.get_current_epoch().0; + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| Error::InvalidTMAddress)?; + let address = self.storage + .read_validator_address_raw_hash(&validator_raw_hash) + .ok_or_else(|| Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch + ))?; + if self.storage + .read_validator_set() + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .iter() + .find(|validator| address == &validator.address) + .is_some() + { + Ok(address) + } else { + Err(Error::NotValidatorAddress(address, epoch)) } } } From 9de164eda48bfb8ce2518f4730d1c210fc8e6a0e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 10:18:40 +0100 Subject: [PATCH 0142/1995] Refactor fetching of a validator's pubkey --- .../lib/node/ledger/shell/prepare_proposal.rs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 31666b3f79..8febf6f21b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -206,22 +206,19 @@ mod prepare_block { .ok()?; // verify signature of the vote extension - let result = self.get_validator_from_address( - &validator_addr, - Some(events_epoch), - ); - let validator_public_key = match result { - Ok((_, validator_public_key)) => validator_public_key, - // TODO: improve this code - Err(_) => { + let validator_public_key = self + .get_validator_from_address( + &validator_addr, + Some(events_epoch), + ) + .map(|(_, validator_public_key)| validator_public_key) + .map_err(|_| { tracing::error!( "Could not get public key from Storage for \ - validator {}", - validator_addr + validator {validator_addr}" ); - return None; - } - }; + }) + .ok()?; vote_extension .verify(&validator_public_key) From 963cd348f172a9c73ba5b9a4752ec7fd5eafe5f8 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 11:15:50 +0200 Subject: [PATCH 0143/1995] [feat]: Refactored vote extensions with new type defs. Fixed tests, lints, and formatting --- apps/src/lib/node/ledger/shell/mod.rs | 2 - apps/src/lib/node/ledger/shell/queries.rs | 65 +-- .../lib/node/ledger/shell/vote_extensions.rs | 374 +++++++----------- .../types/ethereum_events/vote_extensions.rs | 11 +- 4 files changed, 161 insertions(+), 291 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 26b171dc92..86a684bfed 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -34,8 +34,6 @@ use namada::ledger::storage::{ use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; -#[cfg(not(feature="ABCI"))] -use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key}; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cdb27be66e..a61e4c9c6f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -13,16 +13,12 @@ use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, Key, PrefixValue}; use namada::types::token::{self, Amount}; #[cfg(not(feature = "ABCI"))] -use tendermint_proto::abci::Validator; -#[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::abci::Validator; -#[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::{ProofOp, ProofOps}; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf; @@ -43,16 +39,13 @@ pub enum Error { "The public key '{0}' is not among the active validator set for epoch \ {1}" )] + #[allow(dead_code)] NotValidatorKey(String, Epoch), #[error( - "The public key hash '{0}' is not among the active validator set for epoch \ + "The public key hash '{0}' is not among the active validator set for epoch \ {1}" )] NotValidatorKeyHash(String, Epoch), - #[error("There are currently no staked validators")] - NoStakingValidators, - #[error("This node is not a validator")] - NotValidator, #[error("Invalid validator tendermint address")] InvalidTMAddress, } @@ -411,6 +404,7 @@ where /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] + #[allow(dead_code)] pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); @@ -429,56 +423,25 @@ where .unwrap_or_default() } - /// Get the voting power of this node (as a fraction of this epochs total - /// voting power) if it is a validator. Else return None - #[cfg(not(feature = "ABCI"))] - pub fn get_validator_voting_power( - &self, - ) -> std::result::Result { - let power = if let Some(secret_key) = self.mode.get_protocol_key() { - self.get_validator_from_protocol_pk(&secret_key.ref_to(), None) - .map(|validator| validator.power)? - } else { - return Err(Error::NotValidator); - }; - let total = u64::from(self.get_total_voting_power(None)); - if total > 0 { - Ok(FractionalVotingPower::new(power, total).unwrap()) - } else { - Err(Error::NoStakingValidators) - } - } - /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native - /// address from storage using this hash. Returns an error - /// if no matching validator is found in the active validator - /// set. + /// address from storage using this hash. pub fn get_validator_from_tm_address( &self, tm_address: &[u8], + epoch: Option, ) -> std::result::Result { - let epoch = self.storage.get_current_epoch().0; + // get the current epoch + let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); let validator_raw_hash = core::str::from_utf8(tm_address) .map_err(|_| Error::InvalidTMAddress)?; - let address = self.storage + self.storage .read_validator_address_raw_hash(&validator_raw_hash) - .ok_or_else(|| Error::NotValidatorKeyHash( - validator_raw_hash.to_string(), - epoch - ))?; - if self.storage - .read_validator_set() - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .iter() - .find(|validator| address == &validator.address) - .is_some() - { - Ok(address) - } else { - Err(Error::NotValidatorAddress(address, epoch)) - } + .ok_or_else(|| { + Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + }) } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 672781e5a6..abc962d319 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,17 +1,12 @@ #[cfg(not(feature = "ABCI"))] mod extend_votes { - use anoma::types::ethereum_events::vote_extensions::{ - EpochPower, SignedEthEvent, SignedEvent, - }; + use anoma::proto::Signed; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; + use borsh::BorshDeserialize; use super::super::*; - /// The data we include in a vote extension - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] - pub struct VoteExtension { - /// Ethereum events seen since last round - ethereum_events: Vec, - } + type SignedExt = Signed; impl Shell where @@ -23,119 +18,94 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - response::ExtendVote { - vote_extension: VoteExtension { - ethereum_events: self.new_ethereum_events(), - } - .try_to_vec() - .unwrap(), - } + let ext = VoteExtension { + block_height: self.storage.last_height + 1, + ethereum_events: self.new_ethereum_events(), + }; + self.mode + .get_protocol_key() + .map(|signing_key| response::ExtendVote { + vote_extension: ext.sign(signing_key).try_to_vec().unwrap(), + }) + .unwrap_or_default() } - /// At present this checks the signature on all Ethereum headers + /// This checks that the vote extension: + /// * Correctly deserializes + /// * Was correctly signed by an active validator. + /// * The block height signed over is correct (replay protection) /// /// INVARIANT: This method must be stateless. pub fn verify_vote_extension( &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - if let Ok(VoteExtension { ethereum_events }) = - VoteExtension::try_from_slice(&req.vote_extension[..]) + let validator_addr = req.validator_address.as_slice(); + if let Ok(signed) = + SignedExt::try_from_slice(&req.vote_extension[..]) { - return if ethereum_events.iter().all(|event| { - self.validate_ethereum_event( + response::VerifyVoteExtension { + status: if self.validate_vote_extension( + signed, + validator_addr, self.storage.last_height + 1, - event, - ) - }) { - response::VerifyVoteExtension { - status: VerifyStatus::Accept.into(), - } - } else { - response::VerifyVoteExtension { - status: VerifyStatus::Reject.into(), - } - }; + ) { + VerifyStatus::Accept.into() + } else { + VerifyStatus::Reject.into() + }, + } + } else { + response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + } } - Default::default() + } + + /// Validates a vote extension issued at the provided block height + /// Checks that at epoch of the provided height + /// * The tendermint address corresponds to an active validator + /// * The validator correctly signed the extension + /// * The validator signed over the correct height inside of the + /// extension + pub fn validate_vote_extension( + &self, + ext: SignedExt, + tm_address: &[u8], + height: BlockHeight, + ) -> bool { + let epoch = self.storage.block.pred_epochs.get_epoch(height); + // get the validator that issued this vote extension + // this should not fail + let validator = + match self.get_validator_from_tm_address(tm_address, epoch) { + Ok(address) => address, + _ => return false, + }; + // get the public key associated with this validator + let pk = match self.get_validator_from_address(&validator, epoch) { + Ok((_, pk)) => pk, + _ => return false, + }; + ext.verify(&pk).is_ok() + && ext.data.block_height == height } /// Checks the channel from the Ethereum oracle monitoring - /// the fullnode and retrieves all messages sent. These are - /// signed and prepared for inclusion in a vote extension. - pub fn new_ethereum_events(&mut self) -> Vec { + /// the fullnode and retrieves all VoteExtensionmessages sent. + pub fn new_ethereum_events(&mut self) -> Vec { let mut events = vec![]; - let voting_power = self.get_validator_voting_power(); - let address = self.mode.get_validator_address().cloned(); if let ShellMode::Validator { ref mut ethereum_recv, - data: - ValidatorData { - keys: - ValidatorKeys { - protocol_keypair, .. - }, - .. - }, .. } = &mut self.mode { - let (voting_power, address) = - voting_power.zip(address).unwrap(); while let Ok(eth_event) = ethereum_recv.try_recv() { - events.push(SignedEthEvent::new( - eth_event, - address.clone(), - voting_power.clone(), - self.storage.last_height + 1, - protocol_keypair, - )); + events.push(eth_event); } } - events - } - - /// Verify that each ethereum header in a vote extension was signed by - /// a validator in the correct epoch, the stated voting power is - /// correct, and the signature is correct. - pub fn validate_ethereum_event( - &self, - height: BlockHeight, - event: &impl SignedEvent, - ) -> bool { - let epoch = self.storage.block.pred_epochs.get_epoch(height); - let total_voting_power = self.get_total_voting_power(epoch); - if u64::from(total_voting_power) == 0 { - return false; - } - // Get the public keys of each validator. Filter out those that - // inaccurately stated their voting power at a given block height - let mut public_keys = vec![]; - for EpochPower { - validator, - voting_power, - block_height, - } in event.get_voting_powers().into_iter() - { - if block_height != height { - continue; - } - if let Some((power, pk)) = - self.get_validator_from_address(&validator, epoch) - { - let power = - FractionalVotingPower::new(power, total_voting_power) - .unwrap(); - if power == voting_power { - public_keys.push(pk); - } - } - } - // check that we found all the public keys and - // check that the signatures are valid - public_keys.len() == event.number_of_signers() - && event.verify_signatures(&public_keys).is_ok() + events } } @@ -145,9 +115,7 @@ mod extend_votes { use anoma::ledger::pos; use anoma::ledger::pos::anoma_proof_of_stake::PosBase; - use anoma::types::ethereum_events::vote_extensions::{ - FractionalVotingPower, MultiSignedEthEvent, SignedEthEvent, - }; + use anoma::types::ethereum_events::vote_extensions::VoteExtension; use anoma::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; @@ -157,8 +125,8 @@ mod extend_votes { use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; + use super::SignedExt; use crate::node::ledger::shell::test_utils::*; - use crate::node::ledger::shell::vote_extensions::VoteExtension; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; /// Test that we successfully receive ethereum events @@ -183,13 +151,8 @@ mod extend_votes { }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); - let [event_first, event_second]: [EthereumEvent; 2] = shell - .new_ethereum_events() - .into_iter() - .map(|signed| signed.event.data.0) - .collect::>() - .try_into() - .expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = + shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_2); @@ -200,6 +163,11 @@ mod extend_votes { #[test] fn test_eth_events_vote_extension() { let (mut shell, _, oracle) = setup(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); let event_1 = EthereumEvent::NewContract { name: "Test".to_string(), address: EthAddress([0; 20]), @@ -214,19 +182,17 @@ mod extend_votes { }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); - let vote_extension: VoteExtension = - BorshDeserialize::try_from_slice( + let vote_extension = + ::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], ) .expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = vote_extension + .data .ethereum_events .clone() - .into_iter() - .map(|signed| signed.event.data.0) - .collect::>() .try_into() .expect("Test failed"); @@ -234,7 +200,11 @@ mod extend_votes { assert_eq!(event_second, event_2); let req = request::VerifyVoteExtension { hash: vec![], - validator_address: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), height: 0, vote_extension: vote_extension .try_to_vec() @@ -254,30 +224,34 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let voting_power = - shell.get_validator_voting_power().expect("Test failed"); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height + 1, - &signing_key, + }], + block_height: shell.storage.last_height + 1, + } + .sign(&signing_key) + .try_to_vec() + .expect("Test failed"); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); } /// Test that validation of vote extensions cast during the @@ -294,24 +268,20 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let voting_power = - shell.get_validator_voting_power().expect("Test failed"); - let height = shell.storage.last_height + 1; - - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let signed_height = shell.storage.last_height + 1; + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height + 1, - &signing_key, - ); + }], + block_height: signed_height, + } + .sign(shell.mode.get_protocol_key().expect("Test failed")); + assert_eq!(shell.storage.get_current_epoch().0.0, 0); // We make a change so that there are no // validators in the next epoch @@ -334,7 +304,7 @@ mod extend_votes { assert!( shell .get_validator_from_protocol_pk(&signing_key.ref_to(), None) - .is_none() + .is_err() ); let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); assert!( @@ -344,49 +314,15 @@ mod extend_votes { &signing_key.ref_to(), Some(prev_epoch) ) - .is_some() + .is_ok() ); - assert!(shell.validate_ethereum_event(height, &signed_event)); - assert!(shell.validate_ethereum_event( - height, - &MultiSignedEthEvent::from(signed_event) - )); - } - /// Test that if the declared voting power is not correct, - /// the signed event is rejected - #[test] - fn reject_incorrect_voting_power() { - let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); - let address = shell.mode.get_validator_address().unwrap().clone(); - let voting_power = 99u64; - let total_voting_power = - u64::from(shell.get_total_voting_power(None)); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }, - address, - FractionalVotingPower::new(voting_power, total_voting_power) - .expect("Test failed"), - shell.storage.last_height + 1, - signing_key, - ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); + let tm_address = address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(); + assert!(shell.validate_vote_extension(vote_ext, &tm_address, signed_height)); } /// Test that that an event that incorrectly labels what block it was @@ -394,64 +330,32 @@ mod extend_votes { #[test] fn reject_incorrect_block_number() { let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); let address = shell.mode.get_validator_address().unwrap().clone(); - let voting_power = shell.get_validator_voting_power().unwrap(); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { + let vote_ext = VoteExtension { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], - }, - address, - voting_power, - shell.storage.last_height, - signing_key, - ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); - } + }], + block_height: shell.storage.last_height, + } + .sign(shell.mode.get_protocol_key().expect("Test failed")) + .try_to_vec() + .expect("Test failed"); - /// Test that that an event with an incorrect address - /// included in a vote extension is rejected - #[test] - fn reject_incorrect_address() { - let (shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed"); - let voting_power = shell.get_validator_voting_power().unwrap(); - let signed_event = SignedEthEvent::new( - EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }, - crate::wallet::defaults::bertha_address(), - voting_power, - shell.storage.last_height, - signing_key, + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address.try_to_vec().expect("Test failed"), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) ); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &signed_event - )); - assert!(!shell.validate_ethereum_event( - shell.storage.last_height + 1, - &MultiSignedEthEvent::from(signed_event) - )); } } } diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 9c9f207f0c..c76a659f6f 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -10,7 +10,7 @@ use num_rational::Ratio; use super::EthereumEvent; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::key::common::Signature; +use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; /// This struct will be created and signed over by each @@ -32,6 +32,11 @@ impl VoteExtension { ethereum_events: Vec::new(), } } + + /// Sign a vote extension and return the data with signature + pub fn sign(self, signing_key: &common::SecretKey) -> Signed { + Signed::new(signing_key, self) + } } /// A fraction of the total voting power. This should always be a reduced @@ -266,11 +271,11 @@ mod tests { }; let events = vec![ MultiSignedEthEvent { - event: ev_1.clone(), + event: ev_1, signers: signers.clone(), }, MultiSignedEthEvent { - event: ev_2.clone(), + event: ev_2, signers, }, ]; From 86e679c0a7363e4413e9c3e3612243910ecbe416 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 13:10:04 +0200 Subject: [PATCH 0144/1995] [feat]: Added an internal queue of Ethereum events. This is so that validators can monitor if they need to resubmit events because their previous vote extensions didn't make it into a block proposal --- apps/src/lib/node/ledger/shell/mod.rs | 53 ++++++++++++- apps/src/lib/node/ledger/shell/queries.rs | 4 +- .../lib/node/ledger/shell/vote_extensions.rs | 75 ++++++++++++------- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 86a684bfed..b824a0a526 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -169,12 +169,55 @@ pub(super) enum ShellMode { Validator { data: ValidatorData, broadcast_sender: UnboundedSender>, - ethereum_recv: UnboundedReceiver, + ethereum_recv: EthereumReceiver, }, Full, Seed, } +/// A channel for pulling events from the Ethereum oracle +/// and queueing them up for inclusion in vote extensions +#[derive(Debug)] +pub(super) struct EthereumReceiver { + channel: UnboundedReceiver, + queue: Vec, +} + +impl EthereumReceiver { + /// Create a new [`EthereumReceiver`] from a channel connected + /// to an Ethereum oracle + pub fn new(channel: UnboundedReceiver) -> Self { + Self { + channel, + queue: vec![], + } + } + + /// Pull messages from the channel and add to queue + /// Since vote extensions require ordering of ethereum + /// events, we do that here. We also de-duplicate events + pub fn fill_queue(&mut self) { + while let Ok(eth_event) = self.channel.try_recv() { + self.queue.push(eth_event); + } + self.queue.sort(); + self.queue.dedup(); + } + + /// Get a copy of the queue + pub fn get_events(&self) -> Vec { + self.queue.clone() + } + + /// Given a list of events, remove them from the queue if present + /// Note that this method preserves the sorting and de-duplication + /// of events in the queue. + #[allow(dead_code)] + pub fn remove(&mut self, events: &[EthereumEvent]) { + self.queue.retain(|event| !events.contains(event)); + } +} + #[allow(dead_code)] impl ShellMode { /// Get the validator address if ledger is in validator mode @@ -308,7 +351,9 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, - ethereum_recv: eth_receiver.unwrap(), + ethereum_recv: EthereumReciever::new( + eth_receiver.unwrap(), + ), }) .expect( "Validator data should have been stored in the \ @@ -327,7 +372,9 @@ where }, }, broadcast_sender, - ethereum_recv: eth_receiver.unwrap(), + ethereum_recv: EthereumReceiver::new( + eth_receiver.unwrap(), + ), } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a61e4c9c6f..b3ef5d6ba7 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -42,8 +42,8 @@ pub enum Error { #[allow(dead_code)] NotValidatorKey(String, Epoch), #[error( - "The public key hash '{0}' is not among the active validator set for epoch \ - {1}" + "The public key hash '{0}' is not among the active validator set for \ + epoch {1}" )] NotValidatorKeyHash(String, Epoch), #[error("Invalid validator tendermint address")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index abc962d319..1d3c4a3363 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -87,25 +87,22 @@ mod extend_votes { Ok((_, pk)) => pk, _ => return false, }; - ext.verify(&pk).is_ok() - && ext.data.block_height == height + ext.verify(&pk).is_ok() && ext.data.block_height == height } /// Checks the channel from the Ethereum oracle monitoring /// the fullnode and retrieves all VoteExtensionmessages sent. pub fn new_ethereum_events(&mut self) -> Vec { - let mut events = vec![]; - if let ShellMode::Validator { - ref mut ethereum_recv, - .. - } = &mut self.mode - { - while let Ok(eth_event) = ethereum_recv.try_recv() { - events.push(eth_event); + match &mut self.mode { + ShellMode::Validator { + ref mut ethereum_recv, + .. + } => { + ethereum_recv.fill_queue(); + ethereum_recv.get_events() } + _ => vec![], } - - events } } @@ -133,29 +130,48 @@ mod extend_votes { /// from the channel to fullnode process /// /// We further check that ledger side buffering is done if multiple - /// events are in the channel + /// events are in the channel and that queueing and de-duplicating is + /// done #[test] fn test_get_eth_events() { let (mut shell, _, oracle) = setup(); - let event_1 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), + let event_1 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], }; let event_2 = EthereumEvent::TransfersToEthereum { - nonce: 1.into(), + nonce: 2.into(), transfers: vec![TransferToEthereum { amount: 100.into(), asset: EthAddress([1; 20]), receiver: EthAddress([2; 20]), }], }; + let event_3 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_3); + // check that we queue and de-duplicate events + oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); + let [event_first, event_second, event_third]: [EthereumEvent; 3] = + shell.new_ethereum_events().try_into().expect("Test failed"); + assert_eq!(event_first, event_1); assert_eq!(event_second, event_2); + assert_eq!(event_third, event_3); } /// Test that ethereum events are added to vote extensions. @@ -168,11 +184,7 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let event_1 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - }; - let event_2 = EthereumEvent::TransfersToEthereum { + let event_1 = EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { amount: 100.into(), @@ -180,6 +192,10 @@ mod extend_votes { receiver: EthAddress([2; 20]), }], }; + let event_2 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); let vote_extension = @@ -317,12 +333,13 @@ mod extend_votes { .is_ok() ); - let tm_address = address - .raw_hash() - .expect("Test failed") - .as_bytes() - .to_vec(); - assert!(shell.validate_vote_extension(vote_ext, &tm_address, signed_height)); + let tm_address = + address.raw_hash().expect("Test failed").as_bytes().to_vec(); + assert!(shell.validate_vote_extension( + vote_ext, + &tm_address, + signed_height + )); } /// Test that that an event that incorrectly labels what block it was From d39d7a6c08d500b6eeb4c72fc3b6ee83702368ed Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:47:06 +0200 Subject: [PATCH 0145/1995] Update apps/src/lib/node/ledger/shell/mod.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index b824a0a526..e0ead6edcb 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -351,7 +351,7 @@ where .map(|data| ShellMode::Validator { data, broadcast_sender, - ethereum_recv: EthereumReciever::new( + ethereum_recv: EthereumReceiver::new( eth_receiver.unwrap(), ), }) From 7cef839b8201e0e32e95ec4d389631dd7c8d9b7f Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:49:16 +0200 Subject: [PATCH 0146/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 1d3c4a3363..78a1ba674d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -52,6 +52,12 @@ mod extend_votes { ) { VerifyStatus::Accept.into() } else { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "received vote extension that didn't validate" + ); VerifyStatus::Reject.into() }, } From dfa1a59725b448c1b5aa85e2bbc4f215f77de76e Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 21 Jul 2022 10:54:58 +0200 Subject: [PATCH 0147/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 78a1ba674d..19f32b3d84 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -62,9 +62,15 @@ mod extend_votes { }, } } else { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "received undeserializable vote extension" + ); response::VerifyVoteExtension { status: VerifyStatus::Reject.into(), - } + } } } From 38032bd60f22fbc641fa52bb4e1ff41d2ad5b73a Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 21 Jul 2022 10:55:56 +0200 Subject: [PATCH 0148/1995] [fix]: Replaced ethereum events queue with btreeset. Added logging --- apps/src/lib/node/ledger/shell/mod.rs | 16 +++++++++------- apps/src/lib/node/ledger/shell/queries.rs | 2 ++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index e0ead6edcb..a2f8c06f80 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -13,7 +13,7 @@ mod process_proposal; mod queries; mod vote_extensions; -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use std::convert::{TryFrom, TryInto}; use std::mem; use std::path::{Path, PathBuf}; @@ -180,7 +180,7 @@ pub(super) enum ShellMode { #[derive(Debug)] pub(super) struct EthereumReceiver { channel: UnboundedReceiver, - queue: Vec, + queue: BTreeSet, } impl EthereumReceiver { @@ -189,7 +189,7 @@ impl EthereumReceiver { pub fn new(channel: UnboundedReceiver) -> Self { Self { channel, - queue: vec![], + queue: BTreeSet::new(), } } @@ -197,16 +197,18 @@ impl EthereumReceiver { /// Since vote extensions require ordering of ethereum /// events, we do that here. We also de-duplicate events pub fn fill_queue(&mut self) { + let mut new_events = 0; while let Ok(eth_event) = self.channel.try_recv() { - self.queue.push(eth_event); + if self.queue.insert(eth_event) { + new_events += 1; + }; } - self.queue.sort(); - self.queue.dedup(); + tracing::debug!(n = new_events, "received Ethereum events"); } /// Get a copy of the queue pub fn get_events(&self) -> Vec { - self.queue.clone() + self.queue.iter().cloned().collect() } /// Given a list of events, remove them from the queue if present diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b3ef5d6ba7..a2c62d60a5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -426,6 +426,8 @@ where /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native /// address from storage using this hash. + /// TODO: We may change how this lookup is done, see + /// https://github.com/anoma/namada/issues/200 pub fn get_validator_from_tm_address( &self, tm_address: &[u8], From 822c0da31b366284f22d7216af0e715b65553e31 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 21 Jul 2022 11:37:06 +0200 Subject: [PATCH 0149/1995] [chore]: rebasing onto eth-bridge-integration branch --- apps/src/lib/node/ledger/shell/queries.rs | 2 +- .../lib/node/ledger/shell/vote_extensions.rs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a2c62d60a5..5376c242c7 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,7 +5,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::parameters::EpochDuration; #[cfg(not(feature = "ABCI"))] -use anoma::ledger::pos::anoma_proof_of_stake::types::VotingPower; +use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::PosParams; use namada::types::address::Address; use namada::types::key; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 19f32b3d84..5c7eccf3d4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,8 +1,8 @@ #[cfg(not(feature = "ABCI"))] mod extend_votes { - use anoma::proto::Signed; - use anoma::types::ethereum_events::vote_extensions::VoteExtension; use borsh::BorshDeserialize; + use namada::proto::Signed; + use namada::types::ethereum_events::vote_extensions::VoteExtension; use super::super::*; @@ -70,7 +70,7 @@ mod extend_votes { ); response::VerifyVoteExtension { status: VerifyStatus::Reject.into(), - } + } } } @@ -122,15 +122,15 @@ mod extend_votes { mod test_vote_extensions { use std::convert::TryInto; - use anoma::ledger::pos; - use anoma::ledger::pos::anoma_proof_of_stake::PosBase; - use anoma::types::ethereum_events::vote_extensions::VoteExtension; - use anoma::types::ethereum_events::{ + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::ledger::pos; + use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; - use anoma::types::key::*; - use anoma::types::storage::{BlockHeight, Epoch}; - use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::key::*; + use namada::types::storage::{BlockHeight, Epoch}; use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; @@ -324,7 +324,7 @@ mod extend_votes { shell.storage.write_validator_set(¤t_validators); // we advance forward to the next epoch let mut req = FinalizeBlock::default(); - req.header.time = anoma::types::time::DateTimeUtc::now(); + req.header.time = namada::types::time::DateTimeUtc::now(); shell.storage.last_height = BlockHeight(11); shell.finalize_block(req).expect("Test failed"); shell.commit(); From 52bb28643072275233e12573b41810dbaefa2796 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 20 Jul 2022 17:15:06 +0200 Subject: [PATCH 0150/1995] [feat]: Added processing vote extensions to ProcessProposal. Need to add tests --- apps/src/lib/node/ledger/shell/mod.rs | 1 + .../lib/node/ledger/shell/process_proposal.rs | 243 +++++++++++------- .../lib/node/ledger/shell/vote_extensions.rs | 28 +- .../types/ethereum_events/vote_extensions.rs | 31 ++- 4 files changed, 196 insertions(+), 107 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a2f8c06f80..68cdb03ee7 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -134,6 +134,7 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, + InvalidVoteExntension = 7, } impl From for u32 { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 21f83fb36e..322f9cebc6 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,8 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use namada::types::transaction::protocol::ProtocolTxType; +use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; +use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; + #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -34,16 +36,51 @@ where &mut self, req: RequestProcessProposal, ) -> ResponseProcessProposal { + // the number of vote extension digests included in the block proposal + let mut vote_ext_digest_num = 0; let tx_results: Vec = req .txs .iter() .map(|tx_bytes| { - ExecTxResult::from(self.process_single_tx(tx_bytes)) + ExecTxResult::from(match Tx::try_from(tx_bytes.as_slice()) { + Ok(tx) => match process_tx(tx) { + // This occurs if the wrapper / protocol tx signature is invalid + Err(err) => TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + }, + Ok(tx) => { + if let TxType::Protocol(ProtocolTx{tx: ProtocolTxType::EthereumEvents(_), ..}) = &tx { + vote_ext_digest_num += 1; + // genesis block should not have vote extensions + if self.storage.last_height.0 == 0 { + TxResult { + code: ErrorCodes::InvalidVoteExntension.into(), + info: "No vote extensions should be included in block height 0".into() + } + } else { + self.process_single_tx(tx) + } + } else { + self.process_single_tx(tx) + } + } + } + Err(_) => { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The submitted transaction was not deserializable" + .into(), + } + } + }) }) .collect(); ResponseProcessProposal { - status: if tx_results.iter().any(|res| res.code > 3) { + status: if vote_ext_digest_num <= 1 + && tx_results.iter().any(|res| res.code > 3) + { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 @@ -71,112 +108,144 @@ 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 { - let tx = match Tx::try_from(tx_bytes) { - Ok(tx) => tx, - Err(_) => { - return TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The submitted transaction was not deserializable" - .into(), - }; - } - }; + pub(crate) fn process_single_tx(&mut self, tx: TxType) -> TxResult { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - - match process_tx(tx) { - // This occurs if the wrapper / protocol tx signature is invalid - Err(err) => TxResult { - code: ErrorCodes::InvalidSig.into(), - info: err.to_string(), + let hash = hash_tx(&Tx::from(tx.clone()).to_bytes()); + match tx { + // If it is a raw transaction, we do no further validation + TxType::Raw(_) => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Transaction rejected: Non-encrypted transactions are \ + not supported" + .into(), }, - Ok(result) => match result { - // If it is a raw transaction, we do no further validation - TxType::Raw(_) => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Transaction rejected: Non-encrypted transactions \ - are not supported" - .into(), - }, - TxType::Protocol(protocol_tx) => match protocol_tx.tx { - ProtocolTxType::EthereumEvents(_) => TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this transaction" - .into(), - }, - _ => TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Unsupported protocol transaction type".into(), - }, - }, - TxType::Decrypted(tx) => match self.next_wrapper() { - Some(wrapper) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { + TxType::Protocol(protocol_tx) => match protocol_tx.tx { + ProtocolTxType::EthereumEvents(digest) => { + let extensions = + digest.decompress(self.storage.last_height); + let mut voting_power = FractionalVotingPower::default(); + // the subtraction underflow check is handled at a higher + // scope + let epoch = + self.storage.block.pred_epochs.get_epoch(BlockHeight( + self.storage.last_height.0 - 1, + )); + let total_power = + u64::from(self.get_total_voting_power(epoch)); + if extensions.into_iter().all(|(ext, validator)| match self + .get_validator_from_address(&validator, epoch) + { + Ok((power, _)) => { + voting_power += FractionalVotingPower::new( + u64::from(power), + total_power, + ) + .unwrap_or_default(); + self.validate_vote_extension( + ext, + validator, + self.storage.last_height, + ) + } + _ => false, + }) { + if voting_power + > FractionalVotingPower::new(2, 3).unwrap() + { TxResult { code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ + info: "Process proposal accepted this \ transaction" .into(), } } else { TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" + code: ErrorCodes::InvalidVoteExntension.into(), + info: "Process proposal rejected this \ + proposal because the backing stake of \ + the vote extensions published in the \ + proposal was insufficient" .into(), } } + } else { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal rejected this proposal \ + because the at least on of the vote \ + extensions included was invalid." + .into(), + } } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected" - .into(), - }, + } + _ => TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "Unsupported protocol transaction type".into(), }, - TxType::Wrapper(tx) => { - // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + }, + TxType::Decrypted(tx) => match self.next_wrapper() { + Some(wrapper) => { + if wrapper.tx_hash != tx.hash_commitment() { TxResult { - code: ErrorCodes::InvalidTx.into(), - info: format!( - "The ciphertext of the wrapped tx {} is \ - invalid", - hash_tx(tx_bytes) - ), + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), + } + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), } } else { - // check that the fee payer has sufficient balance - let balance = self - .get_balance(&tx.fee.token, &tx.fee_payer()) - .unwrap_or_default(); - - if tx.fee.amount <= balance { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The address given does not have \ - sufficient balance to pay fee" - .into(), - } + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The encrypted payload of tx was \ + incorrectly marked as un-decryptable" + .into(), } } } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected".into(), + }, }, + TxType::Wrapper(wrapper) => { + // validate the ciphertext via Ferveo + if !wrapper.validate_ciphertext() { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!( + "The ciphertext of the wrapped tx {} is invalid", + hash + ), + } + } else { + // check that the fee payer has sufficient balance + let balance = self + .get_balance(&wrapper.fee.token, &wrapper.fee_payer()) + .unwrap_or_default(); + + if wrapper.fee.amount <= balance { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this transaction" + .into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The address given does not have sufficient \ + balance to pay fee" + .into(), + } + } + } + } } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5c7eccf3d4..a950f8c6fc 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -2,11 +2,11 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; + use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::VoteExtension; use super::super::*; - - type SignedExt = Signed; + pub type SignedExt = Signed; impl Shell where @@ -40,14 +40,17 @@ mod extend_votes { &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - let validator_addr = req.validator_address.as_slice(); - if let Ok(signed) = - SignedExt::try_from_slice(&req.vote_extension[..]) + let addr = self.get_validator_from_tm_address( + req.validator_address.as_slice(), + None, + ); + if let (Ok(signed), Ok(validator)) = + (SignedExt::try_from_slice(&req.vote_extension[..]), addr) { response::VerifyVoteExtension { status: if self.validate_vote_extension( signed, - validator_addr, + validator, self.storage.last_height + 1, ) { VerifyStatus::Accept.into() @@ -83,17 +86,10 @@ mod extend_votes { pub fn validate_vote_extension( &self, ext: SignedExt, - tm_address: &[u8], + validator: Address, height: BlockHeight, ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); - // get the validator that issued this vote extension - // this should not fail - let validator = - match self.get_validator_from_tm_address(tm_address, epoch) { - Ok(address) => address, - _ => return false, - }; // get the public key associated with this validator let pk = match self.get_validator_from_address(&validator, epoch) { Ok((_, pk)) => pk, @@ -345,11 +341,9 @@ mod extend_votes { .is_ok() ); - let tm_address = - address.raw_hash().expect("Test failed").as_bytes().to_vec(); assert!(shell.validate_vote_extension( vote_ext, - &tm_address, + address, signed_height )); } diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index c76a659f6f..cb11c35c9a 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -2,6 +2,7 @@ //! in vote extensions. use std::collections::HashSet; +use std::ops::{Add, AddAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -61,12 +62,32 @@ impl FractionalVotingPower { } } +impl Default for FractionalVotingPower { + fn default() -> Self { + Self::new(0, 1).unwrap() + } +} + impl From<&FractionalVotingPower> for (u64, u64) { fn from(ratio: &FractionalVotingPower) -> Self { (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) } } +impl Add for FractionalVotingPower { + type Output = Self; + + fn add(self, rhs: FractionalVotingPower) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FractionalVotingPower { + fn add_assign(&mut self, rhs: FractionalVotingPower) { + *self = Self(self.0 + rhs.0) + } +} + impl BorshSerialize for FractionalVotingPower { fn serialize( &self, @@ -130,7 +151,7 @@ impl VoteExtensionDigest { pub fn decompress( self, last_height: BlockHeight, - ) -> Vec> { + ) -> Vec<(Signed, Address)> { let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -151,7 +172,7 @@ impl VoteExtensionDigest { ext.ethereum_events.sort(); let signed = Signed { data: ext, sig }; - extensions.push(signed); + extensions.push((signed, addr)); } extensions } @@ -284,7 +305,11 @@ mod tests { // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` - let decompressed = digest.decompress(last_block_height); + let decompressed = digest + .decompress(last_block_height) + .into_iter() + .map(|event| event.0) + .collect::>>(); assert_eq!(ext, decompressed); assert!(decompressed[0].verify(&sk_1.ref_to()).is_ok()); From ca951b8817dbe63243618244895cf3eefacef49d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 11:00:41 +0100 Subject: [PATCH 0151/1995] Merge fixes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- apps/src/lib/node/ledger/shell/queries.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5b2ade7ca3..6421524281 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,12 +4,12 @@ mod prepare_block { use std::collections::{BTreeMap, HashSet}; - use anoma::proto::Signed; - use anoma::types::ethereum_events::vote_extensions::{ + use namada::proto::Signed; + use namada::types::ethereum_events::vote_extensions::{ FractionalVotingPower, MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; - use anoma::types::transaction::protocol::ProtocolTxType; + use namada::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 759e526647..5521a8bde9 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -4,7 +4,7 @@ use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; #[cfg(not(feature = "ABCI"))] -use namada::ledger::pos::namada_proof_of_stake::types::VotingPower +use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::PosParams; use namada::types::address::Address; From 60a97cda511088a5b55b35ec7639c760c41f79d1 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 21 Jul 2022 12:06:45 +0200 Subject: [PATCH 0152/1995] [docs]: Fixed doc string about error codes in process proposal --- apps/src/lib/node/ledger/shell/process_proposal.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 322f9cebc6..054c218199 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,7 +2,6 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -104,6 +103,8 @@ where /// 3: Wasm runtime error /// 4: Invalid order of decrypted txs /// 5. More decrypted txs than expected + /// 6. A transaciton could not be decrypted + /// 7. An error in the vote extensions included in the proposal /// /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the @@ -125,12 +126,11 @@ where let extensions = digest.decompress(self.storage.last_height); let mut voting_power = FractionalVotingPower::default(); - // the subtraction underflow check is handled at a higher - // scope - let epoch = - self.storage.block.pred_epochs.get_epoch(BlockHeight( - self.storage.last_height.0 - 1, - )); + let epoch = self + .storage + .block + .pred_epochs + .get_epoch(BlockHeight(self.storage.last_height.0)); let total_power = u64::from(self.get_total_voting_power(epoch)); if extensions.into_iter().all(|(ext, validator)| match self From 849a78b8cd6e60551d858053ba06b561d6604a9a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 11:08:00 +0100 Subject: [PATCH 0153/1995] Fix merge conflicts --- apps/src/lib/node/ledger/shell/queries.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d94939e122..995f34dd88 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -3,8 +3,6 @@ use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; -#[cfg(not(feature = "ABCI"))] -use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::parameters::EpochDuration; #[cfg(not(feature = "ABCI"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; From 720094034f6a5c3033f4318fd800ee2f4b9bce62 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 11:24:53 +0100 Subject: [PATCH 0154/1995] Improve error handling of get_epoch() for the last block height --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6421524281..d142463f07 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -153,8 +153,9 @@ mod prepare_block { .block .pred_epochs .get_epoch(self.storage.last_height) - // TODO: is this `unwrap()` fine? - .unwrap(); + .expect( + "The epoch of the last block height should always be known", + ); let all_vote_extensions = vote_extensions.into_iter().filter_map(|vote| { From 27094390eb39fb36e72ddd796eba30b9913e3f5d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 11:47:07 +0100 Subject: [PATCH 0155/1995] Remove #[allow(dead_code)] from get_total_voting_power() --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 995f34dd88..58ca6b4e58 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -404,7 +404,6 @@ where /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] - #[allow(dead_code)] pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); From 40974fb180d2d32f31c0ec67299145f66a601c82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 13:32:38 +0100 Subject: [PATCH 0156/1995] Refactor compress_vote_extensions and validate_vote_extension --- .../lib/node/ledger/shell/prepare_proposal.rs | 80 ++++--------------- .../lib/node/ledger/shell/vote_extensions.rs | 65 ++++++++++++--- 2 files changed, 70 insertions(+), 75 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d142463f07..3fa001d91a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -148,15 +148,6 @@ mod prepare_block { &self, vote_extensions: Vec, ) -> Option { - let events_epoch = self - .storage - .block - .pred_epochs - .get_epoch(self.storage.last_height) - .expect( - "The epoch of the last block height should always be known", - ); - let all_vote_extensions = vote_extensions.into_iter().filter_map(|vote| { let vote_extension = @@ -170,70 +161,29 @@ mod prepare_block { err ); }) - .ok() - .and_then(|ext| { - let ext_height = ext.data.block_height; - let last_height = self.storage.last_height; - - if ext_height == last_height { - Some(ext) - } else { - tracing::error!( - "Vote extension issued for a block height \ - {ext_height} different from the last \ - height {last_height}" - ); - None - } - })?; + .ok()?; let validator = vote.validator.or_else(|| { tracing::error!("Vote extension has no validator data"); None })?; - let validator_addr = self - .get_validator_from_tm_address( - &validator.address[..], - Some(events_epoch), - ) - .map_err(|err| { - tracing::error!( - "Failed to get an address from Tendermint \ - {:?}: {}", - validator, - err - ); - }) - .ok()?; - - // verify signature of the vote extension - let validator_public_key = self - .get_validator_from_address( - &validator_addr, - Some(events_epoch), - ) - .map(|(_, validator_public_key)| validator_public_key) - .map_err(|_| { - tracing::error!( - "Could not get public key from Storage for \ - validator {validator_addr}" - ); - }) - .ok()?; - - vote_extension - .verify(&validator_public_key) - .map_err(|_| { - tracing::error!( - "Failed to verify the signature of a vote \ - extension issued by {validator_addr}" - ); - }) - .ok()?; - Some((validator_addr, vote_extension)) + self.validate_vote_ext_and_get_nam_addr( + vote_extension, + &validator.address[..], + self.storage.last_height, + ) }); + let events_epoch = self + .storage + .block + .pred_epochs + .get_epoch(self.storage.last_height) + .expect( + "The epoch of the last block height should always be known", + ); + let mut event_observers = BTreeMap::new(); let mut signatures = Vec::new(); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5c7eccf3d4..c2e1a8750c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -2,6 +2,7 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; + use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::VoteExtension; use super::super::*; @@ -80,26 +81,70 @@ mod extend_votes { /// * The validator correctly signed the extension /// * The validator signed over the correct height inside of the /// extension + #[inline] pub fn validate_vote_extension( &self, ext: SignedExt, tm_address: &[u8], height: BlockHeight, ) -> bool { + self.validate_vote_ext_and_get_nam_addr(ext, tm_address, height) + .is_some() + } + + /// This method behaves exactly like [`Self::validate_vote_extension`], + /// with the added bonus of returning the Namada [`Address`] + /// corresponding to `tm_address`, and the respective + /// [`SignedExt`] to be validated. + pub fn validate_vote_ext_and_get_nam_addr( + &self, + ext: SignedExt, + tm_address: &[u8], + height: BlockHeight, + ) -> Option<(Address, SignedExt)> { + if ext.data.block_height != height { + let ext_height = ext.data.block_height; + tracing::error!( + "Vote extension issued for a block height {ext_height} \ + different from the expected height {height}" + ); + return None; + } let epoch = self.storage.block.pred_epochs.get_epoch(height); // get the validator that issued this vote extension // this should not fail - let validator = - match self.get_validator_from_tm_address(tm_address, epoch) { - Ok(address) => address, - _ => return false, - }; + let validator = self + .get_validator_from_tm_address(tm_address, epoch) + .map_err(|err| { + tracing::error!( + "Failed to get an address from Tendermint validator \ + {:?}: {}", + tm_address, + err + ); + }) + .ok()?; // get the public key associated with this validator - let pk = match self.get_validator_from_address(&validator, epoch) { - Ok((_, pk)) => pk, - _ => return false, - }; - ext.verify(&pk).is_ok() && ext.data.block_height == height + let pk = self + .get_validator_from_address(&validator, epoch) + .map(|(_, pk)| pk) + .map_err(|_| { + tracing::error!( + "Could not get public key from Storage for validator \ + {validator}" + ); + }) + .ok()?; + // verify the signature of the vote extension + ext.verify(&pk) + .map_err(|_| { + tracing::error!( + "Failed to verify the signature of a vote extension \ + issued by {validator}" + ); + }) + .ok() + .map(|_| (validator, ext)) } /// Checks the channel from the Ethereum oracle monitoring From 2dfd25c4ab82305a432747d551ee44ae67b8c9e7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 13:43:54 +0100 Subject: [PATCH 0157/1995] Make SignedExt public --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c2e1a8750c..43e2af3c6b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -7,7 +7,8 @@ mod extend_votes { use super::super::*; - type SignedExt = Signed; + /// A [`VoteExtension`] signed by a Namada validator. + pub type SignedExt = Signed; impl Shell where From 77bee7e0b82f25eb92d1c04e2dc539f5c9e79b40 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 13:44:02 +0100 Subject: [PATCH 0158/1995] Factor out filter_invalid_vote_extensions() --- .../lib/node/ledger/shell/prepare_proposal.rs | 66 +++++++++++-------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3fa001d91a..205b6dae2a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,6 +5,7 @@ mod prepare_block { use std::collections::{BTreeMap, HashSet}; use namada::proto::Signed; + use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::{ FractionalVotingPower, MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, @@ -14,6 +15,7 @@ mod prepare_block { ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; + use super::super::vote_extensions::SignedExt; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -143,38 +145,11 @@ mod prepare_block { /// Compresses a set of vote extensions into a single /// [`VoteExtensionDigest`], whilst filtering invalid - /// `Signed` instances in the process + /// [`SignedExt`] instances in the process fn compress_vote_extensions( &self, vote_extensions: Vec, ) -> Option { - let all_vote_extensions = - vote_extensions.into_iter().filter_map(|vote| { - let vote_extension = - Signed::::try_from_slice( - &vote.vote_extension[..], - ) - .map_err(|err| { - tracing::error!( - "Failed to deserialize signed vote extension: \ - {}", - err - ); - }) - .ok()?; - - let validator = vote.validator.or_else(|| { - tracing::error!("Vote extension has no validator data"); - None - })?; - - self.validate_vote_ext_and_get_nam_addr( - vote_extension, - &validator.address[..], - self.storage.last_height, - ) - }); - let events_epoch = self .storage .block @@ -191,7 +166,9 @@ mod prepare_block { self.get_total_voting_power(Some(events_epoch)).into(); let mut voting_power = 0u64; - for (validator_addr, vote_extension) in all_vote_extensions { + for (validator_addr, vote_extension) in + self.filter_invalid_vote_extensions(vote_extensions) + { let (validator_voting_power, _) = self .get_validator_from_address( &validator_addr, @@ -239,6 +216,37 @@ mod prepare_block { Some(VoteExtensionDigest { events, signatures }) } + + /// Takes a list of signed vote extensions, + /// and filters out invalid instances. + fn filter_invalid_vote_extensions( + &self, + vote_extensions: Vec, + ) -> impl Iterator + '_ { + vote_extensions.into_iter().filter_map(|vote| { + let vote_extension = Signed::::try_from_slice( + &vote.vote_extension[..], + ) + .map_err(|err| { + tracing::error!( + "Failed to deserialize signed vote extension: {}", + err + ); + }) + .ok()?; + + let validator = vote.validator.or_else(|| { + tracing::error!("Vote extension has no validator data"); + None + })?; + + self.validate_vote_ext_and_get_nam_addr( + vote_extension, + &validator.address[..], + self.storage.last_height, + ) + }) + } } /// Functions for creating the appropriate TxRecord given the From 24a33432340624a000d54672eb37116b7c9384c9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 14:00:11 +0100 Subject: [PATCH 0159/1995] Handle scenarios where we might not have any vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 205b6dae2a..f81d291f5e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -83,19 +83,29 @@ mod prepare_block { let votes = local_last_commit.votes; self.compress_vote_extensions(votes) }); - let vote_extension_digest = match vote_extension_digest { - Some(_) if self.storage.last_height.0 == 0 => { - tracing::error!( - "The genesis block should not contain vote extensions" - ); - return vec![]; - } - Some(d) => d, - // if no vote extensions were found, we return an empty - // `Vec` of protocol - // transactions - _ => return vec![], - }; + let vote_extension_digest = + match (vote_extension_digest, self.storage.last_height) { + // handle genesis block + (None, BlockHeight(0)) => return vec![], + (Some(_), BlockHeight(0)) => { + tracing::error!( + "The genesis block should not contain vote \ + extensions" + ); + return vec![]; + } + // handle block heights > 0 + (Some(digest), _) => digest, + _ => unreachable!( + "Honest Namada validators will always sign a \ + VoteExtension, even if no Ethereum events were \ + observed at a given block height. In fact, signing \ + an empty VoteExtension commits the fact no events \ + were observed by a majority of validators. This \ + scenario is virtually impossible, unless every \ + validator is Byzantine." + ), + }; let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) .sign(&protocol_key) From 2cf85792323fc104e5d6a4cc752def25fc406acf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 14:03:06 +0100 Subject: [PATCH 0160/1995] Add a TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f81d291f5e..ad86aa0943 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -92,6 +92,8 @@ mod prepare_block { "The genesis block should not contain vote \ extensions" ); + // TODO: maybe slash validators who claim to have + // seen vote extensions at H=0 return vec![]; } // handle block heights > 0 From 23343672cf7d875b34ff0179dbe9932972077474 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 16:26:08 +0100 Subject: [PATCH 0161/1995] WIP: Writing test_prepare_proposal_filter_out_bad_signatures() --- .../lib/node/ledger/shell/prepare_proposal.rs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ad86aa0943..66192cf609 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -299,10 +299,15 @@ mod prepare_block { // TODO: write tests for ethereum events on prepare proposal mod test_prepare_proposal { use namada::types::address::xan; - use namada::types::storage::Epoch; + use namada::types::ethereum_events::vote_extensions::VoteExtension; + // use namada::types::ethereum_events::EthereumEvent; + use namada::types::key::{self, common, ed25519::Signature}; + use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; use tendermint_proto::abci::tx_record::TxAction; + use tendermint_proto::abci::{ExtendedCommitInfo, ExtendedVoteInfo}; + use super::super::super::vote_extensions::SignedExt; use super::*; use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; @@ -327,6 +332,57 @@ mod prepare_block { ); } + /// Test if we are filtering out vote extensinos with bad + /// signatures in a prepare proposal. + #[test] + fn test_prepare_proposal_filter_out_bad_signatures() { + const LAST_HEIGHT: BlockHeight = BlockHeight(2); + + let (mut shell, _, _) = TestShell::new(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let signed_vote_extensions = vec![ + { + let sk = key::testing::keypair_1(); + VoteExtension { + block_height: LAST_HEIGHT, + ethereum_events: vec![], + } + .sign(&sk) + }, + { + // create a fake signature + let sig = + common::Signature::Ed25519(Signature([0u8; 64].into())); + + let data = VoteExtension { + block_height: LAST_HEIGHT, + ethereum_events: vec![], + }; + + SignedExt { sig, data } + }, + ]; + let votes = signed_vote_extensions + .into_iter() + .map(|ext| ExtendedVoteInfo { + vote_extension: ext.try_to_vec().unwrap(), + validator: Some(todo!()), + ..Default::default() + }) + .collect(); + + let req = RequestPrepareProposal { + local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), + ..Default::default() + }; + } + + // TODO: test if we filter out valid signatures, + // but for invalid block heights + /// Test that if an error is encountered while /// trying to process a tx from the mempool, /// we simply exclude it from the proposal From 2618121e51ffc3c241ef80023947e410c08b954f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 16:41:23 +0100 Subject: [PATCH 0162/1995] Add a TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 66192cf609..45443f0f24 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -369,6 +369,10 @@ mod prepare_block { .into_iter() .map(|ext| ExtendedVoteInfo { vote_extension: ext.try_to_vec().unwrap(), + // TODO: make sure the Tendermint validator we insert here + // has an equivalent Namada validator addr in the `shell` + // + // https://github.com/anoma/namada/blob/tiago/vote-extensions-types/apps/src/lib/config/genesis.rs#L733= validator: Some(todo!()), ..Default::default() }) From 88d50c927c1e5b525e86382bb67a56eeb7a9f942 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 17:00:19 +0100 Subject: [PATCH 0163/1995] Make unreachable!() message more generic --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 45443f0f24..68ad531e4c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -103,9 +103,11 @@ mod prepare_block { VoteExtension, even if no Ethereum events were \ observed at a given block height. In fact, signing \ an empty VoteExtension commits the fact no events \ - were observed by a majority of validators. This \ - scenario is virtually impossible, unless every \ - validator is Byzantine." + were observed by a majority of validators. Likewise, \ + a Tendermint quorum should never decide on a block \ + including vote extensions reflecting less than 2/3 \ + of the total stake. These scenarios are virtually \ + impossible, so we will panic here." ), }; From a12a69ff84b9d44eff30e6ad9afb6e333b2d5e76 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 17:07:34 +0100 Subject: [PATCH 0164/1995] Fix mistake in the unreachable!() message --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 68ad531e4c..8883ab2072 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -105,9 +105,9 @@ mod prepare_block { an empty VoteExtension commits the fact no events \ were observed by a majority of validators. Likewise, \ a Tendermint quorum should never decide on a block \ - including vote extensions reflecting less than 2/3 \ - of the total stake. These scenarios are virtually \ - impossible, so we will panic here." + including vote extensions reflecting less than or \ + equal to 2/3 of the total stake. These scenarios are \ + virtually impossible, so we will panic here." ), }; From 0b7c2bdf45347cc56261ad239a707c5ce3b60a05 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 21 Jul 2022 20:29:56 +0100 Subject: [PATCH 0165/1995] TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 8883ab2072..eaeec338b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -347,6 +347,8 @@ mod prepare_block { let signed_vote_extensions = vec![ { + // TODO: might need to change this to the sk + // of the validator in `genesis::genesis()` let sk = key::testing::keypair_1(); VoteExtension { block_height: LAST_HEIGHT, From e460b069b4919e554e4b12f38d642ddd7836e557 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 10:02:40 +0100 Subject: [PATCH 0166/1995] Test filtering out bad signatures in vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 106 ++++++++++-------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index eaeec338b3..0b60cd3601 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -183,6 +183,9 @@ mod prepare_block { for (validator_addr, vote_extension) in self.filter_invalid_vote_extensions(vote_extensions) { + // TODO: we can return the voting power from + // `filter_invalid_vote_extensions`, therefore + // optimizing this loop a bit let (validator_voting_power, _) = self .get_validator_from_address( &validator_addr, @@ -218,7 +221,7 @@ mod prepare_block { if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( "Tendermint has decided on a block including vote \ - extensions reflecting less than 2/3 of the total stake" + extensions reflecting <= 2/3 of the total stake" ); return None; } @@ -302,16 +305,22 @@ mod prepare_block { mod test_prepare_proposal { use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::hash::Hash; // use namada::types::ethereum_events::EthereumEvent; - use namada::types::key::{self, common, ed25519::Signature}; + use namada::types::key::{common, ed25519}; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; use tendermint_proto::abci::tx_record::TxAction; - use tendermint_proto::abci::{ExtendedCommitInfo, ExtendedVoteInfo}; + use tendermint_proto::abci::{ + ExtendedCommitInfo, ExtendedVoteInfo, Validator, + }; use super::super::super::vote_extensions::SignedExt; use super::*; - use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; + use crate::node::ledger::shell::test_utils::{ + self, gen_keypair, TestShell, + }; + use crate::wallet; /// Test that if a tx from the mempool is not a /// WrapperTx type, it is not included in the @@ -337,60 +346,69 @@ mod prepare_block { /// Test if we are filtering out vote extensinos with bad /// signatures in a prepare proposal. #[test] - fn test_prepare_proposal_filter_out_bad_signatures() { + fn test_prepare_proposal_filter_out_bad_vext_signatures() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let signed_vote_extensions = vec![ - { - // TODO: might need to change this to the sk - // of the validator in `genesis::genesis()` - let sk = key::testing::keypair_1(); - VoteExtension { - block_height: LAST_HEIGHT, - ethereum_events: vec![], - } - .sign(&sk) - }, - { - // create a fake signature - let sig = - common::Signature::Ed25519(Signature([0u8; 64].into())); - - let data = VoteExtension { - block_height: LAST_HEIGHT, - ethereum_events: vec![], - }; - - SignedExt { sig, data } - }, - ]; - let votes = signed_vote_extensions - .into_iter() - .map(|ext| ExtendedVoteInfo { - vote_extension: ext.try_to_vec().unwrap(), - // TODO: make sure the Tendermint validator we insert here - // has an equivalent Namada validator addr in the `shell` - // - // https://github.com/anoma/namada/blob/tiago/vote-extensions-types/apps/src/lib/config/genesis.rs#L733= - validator: Some(todo!()), - ..Default::default() - }) - .collect(); + // tendermint address + let validator_tm_addr = { + let consensus_key = wallet::defaults::validator_keypair(); + let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = + consensus_key.ref_to(); + let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); + (&raw_hash[..20]).to_vec() + }; - let req = RequestPrepareProposal { - local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), + let signed_vote_extension = { + // create a fake signature + let sig = common::Signature::Ed25519(ed25519::Signature( + [0u8; 64].into(), + )); + + let data = VoteExtension { + block_height: LAST_HEIGHT, + ethereum_events: vec![], + }; + + SignedExt { sig, data } + }; + let vote = ExtendedVoteInfo { + vote_extension: signed_vote_extension.try_to_vec().unwrap(), + validator: Some(Validator { + address: validator_tm_addr, + ..Default::default() + }), ..Default::default() }; + + let votes = vec![vote]; + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + + assert_eq!(filtered_votes, vec![]); } // TODO: test if we filter out valid signatures, // but for invalid block heights + /// Test if vote extension validation and inclusion in a block + /// behaves as expected, considering honest validators. + // TODO: finish this + #[test] + fn test_prepare_proposal_vext_normal_op() { + //let shell: TestShell = todo!(); + //let votes = todo!(); + //let req = RequestPrepareProposal { + // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), + // ..Default::default() + //}; + //let rsp = shell.prepare_proposal(req); + } + /// Test that if an error is encountered while /// trying to process a tx from the mempool, /// we simply exclude it from the proposal From 848cd0779eeb0a0b9c4869dc479989ddc278a4ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 10:19:06 +0100 Subject: [PATCH 0167/1995] Test filtering out bad block heights in vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0b60cd3601..1a173dd1db 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -392,8 +392,50 @@ mod prepare_block { assert_eq!(filtered_votes, vec![]); } - // TODO: test if we filter out valid signatures, - // but for invalid block heights + /// Test if we are filtering out vote extensinos for + /// block heights different than the last height. + #[test] + fn test_prepare_proposal_filter_out_bad_vext_bheights() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + // tendermint address + let validator_tm_addr = { + let consensus_key = wallet::defaults::validator_keypair(); + let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = + consensus_key.ref_to(); + let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); + (&raw_hash[..20]).to_vec() + }; + + let signed_vote_extension = { + let (protocol_key, _) = wallet::defaults::validator_keys(); + VoteExtension { + block_height: PRED_LAST_HEIGHT, + ethereum_events: vec![], + } + .sign(&protocol_key) + }; + let vote = ExtendedVoteInfo { + vote_extension: signed_vote_extension.try_to_vec().unwrap(), + validator: Some(Validator { + address: validator_tm_addr, + ..Default::default() + }), + ..Default::default() + }; + + let votes = vec![vote]; + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + + assert_eq!(filtered_votes, vec![]); + } /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering honest validators. From c2bd5a099c2affad7a6454aa13d3e5db1d290744 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 10:30:54 +0100 Subject: [PATCH 0168/1995] Refactor tests a bit --- .../lib/node/ledger/shell/prepare_proposal.rs | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1a173dd1db..e6cc57b944 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -343,6 +343,35 @@ mod prepare_block { ); } + /// Returns a tuple with the Tendermint validator address and the + /// Namada protocol key. + fn get_validator_keys() -> (Vec, common::SecretKey) { + let validator_tm_addr = { + let consensus_key = wallet::defaults::validator_keypair(); + let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = + consensus_key.ref_to(); + let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); + (&raw_hash[..20]).to_vec() + }; + let (protocol_key, _) = wallet::defaults::validator_keys(); + (validator_tm_addr, protocol_key) + } + + /// Serialize a [`SignedExt`] to an [`ExtendedVoteInfo`] + fn vote_extension_serialize( + tm_addr: Vec, + vext: SignedExt, + ) -> ExtendedVoteInfo { + ExtendedVoteInfo { + vote_extension: vext.try_to_vec().unwrap(), + validator: Some(Validator { + address: tm_addr, + ..Default::default() + }), + ..Default::default() + } + } + /// Test if we are filtering out vote extensinos with bad /// signatures in a prepare proposal. #[test] @@ -354,14 +383,7 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - // tendermint address - let validator_tm_addr = { - let consensus_key = wallet::defaults::validator_keypair(); - let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = - consensus_key.ref_to(); - let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); - (&raw_hash[..20]).to_vec() - }; + let (validator_tm_addr, _) = get_validator_keys(); let signed_vote_extension = { // create a fake signature @@ -376,16 +398,10 @@ mod prepare_block { SignedExt { sig, data } }; - let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension.try_to_vec().unwrap(), - validator: Some(Validator { - address: validator_tm_addr, - ..Default::default() - }), - ..Default::default() - }; - - let votes = vec![vote]; + let votes = vec![vote_extension_serialize( + validator_tm_addr, + signed_vote_extension, + )]; let filtered_votes: Vec<_> = shell.filter_invalid_vote_extensions(votes).collect(); @@ -397,40 +413,27 @@ mod prepare_block { #[test] fn test_prepare_proposal_filter_out_bad_vext_bheights() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + const PRED_LAST_HEIGHT: BlockHeight = + BlockHeight(LAST_HEIGHT.0 - 1); let (mut shell, _, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - // tendermint address - let validator_tm_addr = { - let consensus_key = wallet::defaults::validator_keypair(); - let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = - consensus_key.ref_to(); - let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); - (&raw_hash[..20]).to_vec() - }; + let (validator_tm_addr, protocol_key) = get_validator_keys(); let signed_vote_extension = { - let (protocol_key, _) = wallet::defaults::validator_keys(); VoteExtension { block_height: PRED_LAST_HEIGHT, ethereum_events: vec![], } .sign(&protocol_key) }; - let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension.try_to_vec().unwrap(), - validator: Some(Validator { - address: validator_tm_addr, - ..Default::default() - }), - ..Default::default() - }; - - let votes = vec![vote]; + let votes = vec![vote_extension_serialize( + validator_tm_addr, + signed_vote_extension, + )]; let filtered_votes: Vec<_> = shell.filter_invalid_vote_extensions(votes).collect(); @@ -442,13 +445,13 @@ mod prepare_block { // TODO: finish this #[test] fn test_prepare_proposal_vext_normal_op() { - //let shell: TestShell = todo!(); - //let votes = todo!(); - //let req = RequestPrepareProposal { - // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), - // ..Default::default() + // let shell: TestShell = todo!(); + // let votes = todo!(); + // let req = RequestPrepareProposal { + // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes + // }), ..Default::default() //}; - //let rsp = shell.prepare_proposal(req); + // let rsp = shell.prepare_proposal(req); } /// Test that if an error is encountered while From f7e2b95ce678ef9c0812d982b26fd7a5a995f9e3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 10:55:27 +0100 Subject: [PATCH 0169/1995] Test filtering out bad validators in vote extensions --- .../lib/node/ledger/shell/prepare_proposal.rs | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e6cc57b944..88198d1307 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -343,15 +343,20 @@ mod prepare_block { ); } + /// Given a secret key `sk`, return a Tendermint compliant address. + fn get_tm_address(sk: &common::SecretKey) -> Vec { + let common::PublicKey::Ed25519(ed25519::PublicKey(pk)) = + sk.ref_to(); + let Hash(raw_hash) = Hash::sha256(pk.as_bytes()); + (&raw_hash[..20]).to_vec() + } + /// Returns a tuple with the Tendermint validator address and the /// Namada protocol key. fn get_validator_keys() -> (Vec, common::SecretKey) { let validator_tm_addr = { let consensus_key = wallet::defaults::validator_keypair(); - let common::PublicKey::Ed25519(ed25519::PublicKey(public_key)) = - consensus_key.ref_to(); - let Hash(raw_hash) = Hash::sha256(public_key.as_bytes()); - (&raw_hash[..20]).to_vec() + get_tm_address(&consensus_key) }; let (protocol_key, _) = wallet::defaults::validator_keys(); (validator_tm_addr, protocol_key) @@ -440,6 +445,40 @@ mod prepare_block { assert_eq!(filtered_votes, vec![]); } + /// Test if we are filtering out vote extensinos for + /// non-validator nodes. + #[test] + fn test_prepare_proposal_filter_out_bad_vext_validators() { + const LAST_HEIGHT: BlockHeight = BlockHeight(2); + + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let (validator_tm_addr, protocol_key) = { + let bertha = wallet::defaults::bertha_keypair(); + let bertha_tm_addr = get_tm_address(&bertha); + (bertha_tm_addr, bertha) + }; + + let signed_vote_extension = { + VoteExtension { + block_height: LAST_HEIGHT, + ethereum_events: vec![], + } + .sign(&protocol_key) + }; + let votes = vec![vote_extension_serialize( + validator_tm_addr, + signed_vote_extension, + )]; + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + + assert_eq!(filtered_votes, vec![]); + } + /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering honest validators. // TODO: finish this From 0eebb7260132d6fc867c3e32c7cf75dc132fb72e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 11:00:29 +0100 Subject: [PATCH 0170/1995] Refactor tests a bit --- .../lib/node/ledger/shell/prepare_proposal.rs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 88198d1307..b27f53a2a9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -377,6 +377,19 @@ mod prepare_block { } } + /// Check if we are filtering out an invalid vote extension `vext` + fn check_vote_extension_filtering( + shell: &mut TestShell, + tm_addr: Vec, + vext: SignedExt, + ) { + let votes = vec![vote_extension_serialize(tm_addr, vext)]; + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + + assert_eq!(filtered_votes, vec![]); + } + /// Test if we are filtering out vote extensinos with bad /// signatures in a prepare proposal. #[test] @@ -403,14 +416,12 @@ mod prepare_block { SignedExt { sig, data } }; - let votes = vec![vote_extension_serialize( + + check_vote_extension_filtering( + &mut shell, validator_tm_addr, signed_vote_extension, - )]; - let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); - - assert_eq!(filtered_votes, vec![]); + ); } /// Test if we are filtering out vote extensinos for @@ -435,14 +446,12 @@ mod prepare_block { } .sign(&protocol_key) }; - let votes = vec![vote_extension_serialize( + + check_vote_extension_filtering( + &mut shell, validator_tm_addr, signed_vote_extension, - )]; - let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); - - assert_eq!(filtered_votes, vec![]); + ); } /// Test if we are filtering out vote extensinos for @@ -469,14 +478,12 @@ mod prepare_block { } .sign(&protocol_key) }; - let votes = vec![vote_extension_serialize( + + check_vote_extension_filtering( + &mut shell, validator_tm_addr, signed_vote_extension, - )]; - let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); - - assert_eq!(filtered_votes, vec![]); + ); } /// Test if vote extension validation and inclusion in a block From a9c149b6306ec3ba760fa1be1e5c792942bc455a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 11:08:12 +0100 Subject: [PATCH 0171/1995] Verify signatures in tests --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b27f53a2a9..931ed8286a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -440,11 +440,13 @@ mod prepare_block { let (validator_tm_addr, protocol_key) = get_validator_keys(); let signed_vote_extension = { - VoteExtension { + let ext = VoteExtension { block_height: PRED_LAST_HEIGHT, ethereum_events: vec![], } - .sign(&protocol_key) + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext }; check_vote_extension_filtering( @@ -472,11 +474,13 @@ mod prepare_block { }; let signed_vote_extension = { - VoteExtension { + let ext = VoteExtension { block_height: LAST_HEIGHT, ethereum_events: vec![], } - .sign(&protocol_key) + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext }; check_vote_extension_filtering( From c3943be719fd93e025b23dcdb2927f65e94b7e58 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 11:24:36 +0100 Subject: [PATCH 0172/1995] Test if we are de-duplicating Ethereum events in prepare proposals --- .../lib/node/ledger/shell/prepare_proposal.rs | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 931ed8286a..e5e6f26385 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -305,8 +305,8 @@ mod prepare_block { mod test_prepare_proposal { use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; - // use namada::types::ethereum_events::EthereumEvent; use namada::types::key::{common, ed25519}; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; @@ -490,6 +490,51 @@ mod prepare_block { ); } + /// Test if we are de-duplicating Ethereum events in + /// prepare proposals. + #[test] + fn test_prepare_proposal_deduplicate_ethereum_events() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let (validator_tm_addr, protocol_key) = get_validator_keys(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let signed_vote_extension = { + let ev = ethereum_event.clone(); + let ext = VoteExtension { + block_height: LAST_HEIGHT, + ethereum_events: vec![ev.clone(), ev.clone(), ev], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + + let digest = { + let votes = vec![vote_extension_serialize( + validator_tm_addr, + signed_vote_extension, + )]; + shell.compress_vote_extensions(votes).unwrap() + }; + let decompressed = digest.decompress(LAST_HEIGHT); + + assert_eq!(decompressed.len(), 1); + assert!(decompressed[0].verify(&protocol_key.ref_to()).is_ok()); + assert_eq!( + decompressed[0].data.ethereum_events, + vec![ethereum_event] + ); + } + /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering honest validators. // TODO: finish this @@ -498,9 +543,9 @@ mod prepare_block { // let shell: TestShell = todo!(); // let votes = todo!(); // let req = RequestPrepareProposal { - // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes - // }), ..Default::default() - //}; + // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), + // ..Default::default() + // }; // let rsp = shell.prepare_proposal(req); } From cdd9d6d053027cb0aa178b6de93c8b6ca39df8ce Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 21 Jul 2022 16:09:21 +0100 Subject: [PATCH 0173/1995] Temporarily add validator_addr to vote extensions Until https://github.com/anoma/namada/issues/200 is fixed --- apps/src/lib/node/ledger/shell/queries.rs | 1 + .../lib/node/ledger/shell/vote_extensions.rs | 36 +++++++++---------- .../types/ethereum_events/vote_extensions.rs | 26 +++++++++----- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 5376c242c7..8101eb484b 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -428,6 +428,7 @@ where /// address from storage using this hash. /// TODO: We may change how this lookup is done, see /// https://github.com/anoma/namada/issues/200 + #[allow(dead_code)] pub fn get_validator_from_tm_address( &self, tm_address: &[u8], diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5c7eccf3d4..ff4a738196 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -18,9 +18,20 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { + let validator_addr = match self.mode.get_validator_address() { + Some(validator_addr) => validator_addr.to_owned(), + None => { + tracing::warn!( + "couldn't get validator address, returning empty vote \ + extension" + ); + return response::ExtendVote::default(); + } + }; let ext = VoteExtension { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), + validator_addr, }; self.mode .get_protocol_key() @@ -40,14 +51,12 @@ mod extend_votes { &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - let validator_addr = req.validator_address.as_slice(); if let Ok(signed) = SignedExt::try_from_slice(&req.vote_extension[..]) { response::VerifyVoteExtension { status: if self.validate_vote_extension( signed, - validator_addr, self.storage.last_height + 1, ) { VerifyStatus::Accept.into() @@ -83,19 +92,13 @@ mod extend_votes { pub fn validate_vote_extension( &self, ext: SignedExt, - tm_address: &[u8], height: BlockHeight, ) -> bool { let epoch = self.storage.block.pred_epochs.get_epoch(height); - // get the validator that issued this vote extension - // this should not fail - let validator = - match self.get_validator_from_tm_address(tm_address, epoch) { - Ok(address) => address, - _ => return false, - }; // get the public key associated with this validator - let pk = match self.get_validator_from_address(&validator, epoch) { + let pk = match self + .get_validator_from_address(&ext.data.validator_addr, epoch) + { Ok((_, pk)) => pk, _ => return false, }; @@ -262,6 +265,7 @@ mod extend_votes { }], }], block_height: shell.storage.last_height + 1, + validator_addr: address.clone(), } .sign(&signing_key) .try_to_vec() @@ -307,6 +311,7 @@ mod extend_votes { }], }], block_height: signed_height, + validator_addr: address.clone(), } .sign(shell.mode.get_protocol_key().expect("Test failed")); @@ -345,13 +350,7 @@ mod extend_votes { .is_ok() ); - let tm_address = - address.raw_hash().expect("Test failed").as_bytes().to_vec(); - assert!(shell.validate_vote_extension( - vote_ext, - &tm_address, - signed_height - )); + assert!(shell.validate_vote_extension(vote_ext, signed_height)); } /// Test that that an event that incorrectly labels what block it was @@ -370,6 +369,7 @@ mod extend_votes { }], }], block_height: shell.storage.last_height, + validator_addr: address.clone(), } .sign(shell.mode.get_protocol_key().expect("Test failed")) .try_to_vec() diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index c76a659f6f..60e14a19bc 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -19,6 +19,10 @@ use crate::types::storage::BlockHeight; pub struct VoteExtension { /// The block height for which this [`VoteExtension`] was made. pub block_height: BlockHeight, + /// The validator's address is temporarily being included + /// until we're able to map a Tendermint address to a validator + /// address (see https://github.com/anoma/namada/issues/200) + pub validator_addr: Address, /// The new ethereum events seen. These should be /// deterministically ordered. pub ethereum_events: Vec, @@ -26,10 +30,11 @@ pub struct VoteExtension { impl VoteExtension { /// Creates a [`VoteExtension`] without any Ethereum events. - pub fn empty(block_height: BlockHeight) -> Self { + pub fn empty(block_height: BlockHeight, validator_addr: Address) -> Self { Self { block_height, ethereum_events: Vec::new(), + validator_addr, } } @@ -136,7 +141,7 @@ impl VoteExtensionDigest { let mut extensions = vec![]; for (sig, addr) in signatures.into_iter() { - let mut ext = VoteExtension::empty(last_height); + let mut ext = VoteExtension::empty(last_height, addr.clone()); for event in events.iter() { if event.signers.contains(&addr) { @@ -164,7 +169,7 @@ mod tests { use super::super::EthereumEvent; use super::*; use crate::proto::Signed; - use crate::types::address::Address; + use crate::types::address::{self, Address}; use crate::types::ethereum_events::Uint; use crate::types::hash::Hash; use crate::types::key; @@ -240,8 +245,11 @@ mod tests { transfers: vec![], }; - let ext = { - let mut ext = VoteExtension::empty(last_block_height); + let validator_1 = address::testing::established_address_2(); + let validator_2 = address::testing::established_address_2(); + + let ext = |validator: Address| -> VoteExtension { + let mut ext = VoteExtension::empty(last_block_height, validator); ext.ethereum_events.push(ev_1.clone()); ext.ethereum_events.push(ev_2.clone()); @@ -252,16 +260,16 @@ mod tests { // assume both v1 and v2 saw the same events, // so each of them signs `ext` with their respective sk - let ext_1 = Signed::new(&sk_1, ext.clone()); - let ext_2 = Signed::new(&sk_2, ext); + let ext_1 = Signed::new(&sk_1, ext(validator_1.clone())); + let ext_2 = Signed::new(&sk_2, ext(validator_2.clone())); let ext = vec![ext_1, ext_2]; // we have the `Signed` instances we need, // let us now compress them into a single `VoteExtensionDigest` let signatures: Vec<(_, Address)> = vec![ - (ext[0].sig.clone(), (&sk_1.ref_to()).into()), - (ext[1].sig.clone(), (&sk_2.ref_to()).into()), + (ext[0].sig.clone(), validator_1), + (ext[1].sig.clone(), validator_2), ]; let signers = { let mut s = HashSet::new(); From d39b32118b38d86fe4e3e2a6be07ce93fba609c0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 22 Jul 2022 12:04:21 +0100 Subject: [PATCH 0174/1995] extend_vote: use expect when getting validator_addr --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ff4a738196..04b450d3a8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -18,16 +18,11 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - let validator_addr = match self.mode.get_validator_address() { - Some(validator_addr) => validator_addr.to_owned(), - None => { - tracing::warn!( - "couldn't get validator address, returning empty vote \ - extension" - ); - return response::ExtendVote::default(); - } - }; + let validator_addr = self + .mode + .get_validator_address() + .expect("only validators should receive this method call") + .to_owned(); let ext = VoteExtension { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), From c1e9c61c9009e612858d3b7fd9e8463cddd492c7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 22 Jul 2022 12:05:04 +0100 Subject: [PATCH 0175/1995] VoteExtension: comment validator_addr field with TODO --- shared/src/types/ethereum_events/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 60e14a19bc..84fc8af10d 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -19,7 +19,7 @@ use crate::types::storage::BlockHeight; pub struct VoteExtension { /// The block height for which this [`VoteExtension`] was made. pub block_height: BlockHeight, - /// The validator's address is temporarily being included + /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator /// address (see https://github.com/anoma/namada/issues/200) pub validator_addr: Address, From 97fd96c48d01fbe2dab04b395d92c08c422ca686 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 22 Jul 2022 12:11:54 +0100 Subject: [PATCH 0176/1995] vote_extensions.rs: remove unnecessary type signature --- shared/src/types/ethereum_events/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 84fc8af10d..656be780c8 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -267,7 +267,7 @@ mod tests { // we have the `Signed` instances we need, // let us now compress them into a single `VoteExtensionDigest` - let signatures: Vec<(_, Address)> = vec![ + let signatures = vec![ (ext[0].sig.clone(), validator_1), (ext[1].sig.clone(), validator_2), ]; From 075cdf3c2a6dacf6faff2966ea6d92a9ad72c861 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 14:06:31 +0100 Subject: [PATCH 0177/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 43e2af3c6b..3e8a31f8ed 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -138,12 +138,12 @@ mod extend_votes { .ok()?; // verify the signature of the vote extension ext.verify(&pk) - .map_err(|_| { + .map_err(|err| { tracing::error!( - "Failed to verify the signature of a vote extension \ - issued by {validator}" + ?err, + %validator, + "Failed to verify the signature of a vote extension issued by validator" ); - }) .ok() .map(|_| (validator, ext)) } From a61e550b39a396f65c05b791cdc6400ba7784714 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 14:06:45 +0100 Subject: [PATCH 0178/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 3e8a31f8ed..2c4ce6c83b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -118,10 +118,9 @@ mod extend_votes { .get_validator_from_tm_address(tm_address, epoch) .map_err(|err| { tracing::error!( - "Failed to get an address from Tendermint validator \ - {:?}: {}", - tm_address, - err + ?err, + ?tm_address, + "Failed to get an address from Tendermint validator", ); }) .ok()?; From d5f246ce8489377b9cde7a99f1a0be02178888c5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 14:07:01 +0100 Subject: [PATCH 0179/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 2c4ce6c83b..73b58216fb 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -128,10 +128,11 @@ mod extend_votes { let pk = self .get_validator_from_address(&validator, epoch) .map(|(_, pk)| pk) - .map_err(|_| { + .map_err(|err| { tracing::error!( - "Could not get public key from Storage for validator \ - {validator}" + ?err, + %validator, + "Could not get public key from Storage for validator" ); }) .ok()?; From 149791a7853144bdc51e282892a155238284c1b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 14:12:27 +0100 Subject: [PATCH 0180/1995] Small fix --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 73b58216fb..0262f9a76e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -144,6 +144,7 @@ mod extend_votes { %validator, "Failed to verify the signature of a vote extension issued by validator" ); + }) .ok() .map(|_| (validator, ext)) } From 607e714bf1678fe4708db565de96f3262de78a2a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 14:49:15 +0100 Subject: [PATCH 0181/1995] Merge fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 30 +++++-------------- .../lib/node/ledger/shell/vote_extensions.rs | 20 ++++++------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e5e6f26385..7167473167 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -11,6 +11,7 @@ mod prepare_block { VoteExtensionDigest, }; use namada::types::transaction::protocol::ProtocolTxType; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; @@ -180,21 +181,10 @@ mod prepare_block { self.get_total_voting_power(Some(events_epoch)).into(); let mut voting_power = 0u64; - for (validator_addr, vote_extension) in + for (validator_voting_power, vote_extension) in self.filter_invalid_vote_extensions(vote_extensions) { - // TODO: we can return the voting power from - // `filter_invalid_vote_extensions`, therefore - // optimizing this loop a bit - let (validator_voting_power, _) = self - .get_validator_from_address( - &validator_addr, - Some(events_epoch), - ) - .expect( - "We already checked that we have a valid Tendermint \ - address", - ); + let validator_addr = vote_extension.data.validator_addr; // update voting power voting_power += u64::from(validator_voting_power); @@ -239,27 +229,21 @@ mod prepare_block { fn filter_invalid_vote_extensions( &self, vote_extensions: Vec, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { vote_extensions.into_iter().filter_map(|vote| { let vote_extension = Signed::::try_from_slice( &vote.vote_extension[..], ) .map_err(|err| { tracing::error!( - "Failed to deserialize signed vote extension: {}", - err + ?err, + "Failed to deserialize signed vote extension", ); }) .ok()?; - let validator = vote.validator.or_else(|| { - tracing::error!("Vote extension has no validator data"); - None - })?; - - self.validate_vote_ext_and_get_nam_addr( + self.validate_vote_ext_and_get_it_back( vote_extension, - &validator.address[..], self.storage.last_height, ) }) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 1efb4ae377..5e4f9133c3 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -4,6 +4,7 @@ mod extend_votes { use namada::proto::Signed; use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use super::super::*; @@ -92,19 +93,18 @@ mod extend_votes { ext: SignedExt, height: BlockHeight, ) -> bool { - self.validate_vote_ext_and_get_nam_addr(ext, height) + self.validate_vote_ext_and_get_it_back(ext, height) .is_some() } /// This method behaves exactly like [`Self::validate_vote_extension`], - /// with the added bonus of returning the Namada [`Address`] - /// corresponding to `tm_address`, and the respective - /// [`SignedExt`] to be validated. - pub fn validate_vote_ext_and_get_nam_addr( + /// with the added bonus of returning the vote extension back, if it + /// is valid. + pub fn validate_vote_ext_and_get_it_back( &self, ext: SignedExt, height: BlockHeight, - ) -> Option<(Address, SignedExt)> { + ) -> Option<(VotingPower, SignedExt)> { if ext.data.block_height != height { let ext_height = ext.data.block_height; tracing::error!( @@ -115,9 +115,9 @@ mod extend_votes { } let epoch = self.storage.block.pred_epochs.get_epoch(height); // get the public key associated with this validator - let pk = self - .get_validator_from_address(&ext.data.validator_addr, epoch) - .map(|(_, pk)| pk) + let validator = &ext.data.validator_addr; + let (voting_power, pk) = self + .get_validator_from_address(validator, epoch) .map_err(|err| { tracing::error!( ?err, @@ -136,7 +136,7 @@ mod extend_votes { ); }) .ok() - .map(|_| (validator, ext)) + .map(|_| (voting_power, ext)) } /// Checks the channel from the Ethereum oracle monitoring From 4753c1ae926559e238ee31c7bf3e39a48b880192 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 15:17:04 +0100 Subject: [PATCH 0182/1995] Fix tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 55 +++++-------------- .../lib/node/ledger/shell/vote_extensions.rs | 1 - 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7167473167..e782445611 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,7 +5,6 @@ mod prepare_block { use std::collections::{BTreeMap, HashSet}; use namada::proto::Signed; - use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::{ FractionalVotingPower, MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, @@ -290,13 +289,12 @@ mod prepare_block { use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; use namada::types::ethereum_events::EthereumEvent; - use namada::types::hash::Hash; use namada::types::key::{common, ed25519}; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ - ExtendedCommitInfo, ExtendedVoteInfo, Validator, + ExtendedCommitInfo, ExtendedVoteInfo, }; use super::super::super::vote_extensions::SignedExt; @@ -327,36 +325,12 @@ mod prepare_block { ); } - /// Given a secret key `sk`, return a Tendermint compliant address. - fn get_tm_address(sk: &common::SecretKey) -> Vec { - let common::PublicKey::Ed25519(ed25519::PublicKey(pk)) = - sk.ref_to(); - let Hash(raw_hash) = Hash::sha256(pk.as_bytes()); - (&raw_hash[..20]).to_vec() - } - - /// Returns a tuple with the Tendermint validator address and the - /// Namada protocol key. - fn get_validator_keys() -> (Vec, common::SecretKey) { - let validator_tm_addr = { - let consensus_key = wallet::defaults::validator_keypair(); - get_tm_address(&consensus_key) - }; - let (protocol_key, _) = wallet::defaults::validator_keys(); - (validator_tm_addr, protocol_key) - } - /// Serialize a [`SignedExt`] to an [`ExtendedVoteInfo`] fn vote_extension_serialize( - tm_addr: Vec, vext: SignedExt, ) -> ExtendedVoteInfo { ExtendedVoteInfo { vote_extension: vext.try_to_vec().unwrap(), - validator: Some(Validator { - address: tm_addr, - ..Default::default() - }), ..Default::default() } } @@ -364,10 +338,9 @@ mod prepare_block { /// Check if we are filtering out an invalid vote extension `vext` fn check_vote_extension_filtering( shell: &mut TestShell, - tm_addr: Vec, vext: SignedExt, ) { - let votes = vec![vote_extension_serialize(tm_addr, vext)]; + let votes = vec![vote_extension_serialize(vext)]; let filtered_votes: Vec<_> = shell.filter_invalid_vote_extensions(votes).collect(); @@ -385,7 +358,7 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (validator_tm_addr, _) = get_validator_keys(); + let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { // create a fake signature @@ -394,6 +367,7 @@ mod prepare_block { )); let data = VoteExtension { + validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], }; @@ -403,7 +377,6 @@ mod prepare_block { check_vote_extension_filtering( &mut shell, - validator_tm_addr, signed_vote_extension, ); } @@ -421,10 +394,12 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (validator_tm_addr, protocol_key) = get_validator_keys(); + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { let ext = VoteExtension { + validator_addr, block_height: PRED_LAST_HEIGHT, ethereum_events: vec![], } @@ -435,7 +410,6 @@ mod prepare_block { check_vote_extension_filtering( &mut shell, - validator_tm_addr, signed_vote_extension, ); } @@ -451,14 +425,15 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (validator_tm_addr, protocol_key) = { - let bertha = wallet::defaults::bertha_keypair(); - let bertha_tm_addr = get_tm_address(&bertha); - (bertha_tm_addr, bertha) + let (validator_addr, protocol_key) = { + let bertha_key = wallet::defaults::bertha_keypair(); + let bertha_addr = wallet::defaults::bertha_address(); + (bertha_addr, bertha_key) }; let signed_vote_extension = { let ext = VoteExtension { + validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], } @@ -469,7 +444,6 @@ mod prepare_block { check_vote_extension_filtering( &mut shell, - validator_tm_addr, signed_vote_extension, ); } @@ -485,7 +459,8 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (validator_tm_addr, protocol_key) = get_validator_keys(); + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); let ethereum_event = EthereumEvent::TransfersToNamada { nonce: 1u64.into(), @@ -494,6 +469,7 @@ mod prepare_block { let signed_vote_extension = { let ev = ethereum_event.clone(); let ext = VoteExtension { + validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ev.clone(), ev.clone(), ev], } @@ -504,7 +480,6 @@ mod prepare_block { let digest = { let votes = vec![vote_extension_serialize( - validator_tm_addr, signed_vote_extension, )]; shell.compress_vote_extensions(votes).unwrap() diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5e4f9133c3..b622a0eb63 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -2,7 +2,6 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; - use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::VoteExtension; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; From 033dcc88afd71ce27a699a030687a0920dbc690a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 15:24:48 +0100 Subject: [PATCH 0183/1995] Fix small logic error in the dedup unit test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e782445611..af7c2c7cc6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -487,7 +487,12 @@ mod prepare_block { let decompressed = digest.decompress(LAST_HEIGHT); assert_eq!(decompressed.len(), 1); - assert!(decompressed[0].verify(&protocol_key.ref_to()).is_ok()); + + // NOTE: this negation is on purpose. we just want to check if the events + // were de-duped, obv the signature will be different, since we signed + // a `Vec` with duped events + assert!(!decompressed[0].verify(&protocol_key.ref_to()).is_ok()); + assert_eq!( decompressed[0].data.ethereum_events, vec![ethereum_event] From af49196a6eed81b57a7e4ae52ca25b8b98254260 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 15:28:01 +0100 Subject: [PATCH 0184/1995] Run make fmt --- .../lib/node/ledger/shell/prepare_proposal.rs | 36 ++++++------------- .../lib/node/ledger/shell/vote_extensions.rs | 2 +- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index af7c2c7cc6..149a12795e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,13 +4,13 @@ mod prepare_block { use std::collections::{BTreeMap, HashSet}; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; use namada::types::ethereum_events::vote_extensions::{ FractionalVotingPower, MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; use namada::types::transaction::protocol::ProtocolTxType; - use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; @@ -293,9 +293,7 @@ mod prepare_block { use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; use tendermint_proto::abci::tx_record::TxAction; - use tendermint_proto::abci::{ - ExtendedCommitInfo, ExtendedVoteInfo, - }; + use tendermint_proto::abci::{ExtendedCommitInfo, ExtendedVoteInfo}; use super::super::super::vote_extensions::SignedExt; use super::*; @@ -326,9 +324,7 @@ mod prepare_block { } /// Serialize a [`SignedExt`] to an [`ExtendedVoteInfo`] - fn vote_extension_serialize( - vext: SignedExt, - ) -> ExtendedVoteInfo { + fn vote_extension_serialize(vext: SignedExt) -> ExtendedVoteInfo { ExtendedVoteInfo { vote_extension: vext.try_to_vec().unwrap(), ..Default::default() @@ -375,10 +371,7 @@ mod prepare_block { SignedExt { sig, data } }; - check_vote_extension_filtering( - &mut shell, - signed_vote_extension, - ); + check_vote_extension_filtering(&mut shell, signed_vote_extension); } /// Test if we are filtering out vote extensinos for @@ -408,10 +401,7 @@ mod prepare_block { ext }; - check_vote_extension_filtering( - &mut shell, - signed_vote_extension, - ); + check_vote_extension_filtering(&mut shell, signed_vote_extension); } /// Test if we are filtering out vote extensinos for @@ -442,10 +432,7 @@ mod prepare_block { ext }; - check_vote_extension_filtering( - &mut shell, - signed_vote_extension, - ); + check_vote_extension_filtering(&mut shell, signed_vote_extension); } /// Test if we are de-duplicating Ethereum events in @@ -479,18 +466,17 @@ mod prepare_block { }; let digest = { - let votes = vec![vote_extension_serialize( - signed_vote_extension, - )]; + let votes = + vec![vote_extension_serialize(signed_vote_extension)]; shell.compress_vote_extensions(votes).unwrap() }; let decompressed = digest.decompress(LAST_HEIGHT); assert_eq!(decompressed.len(), 1); - // NOTE: this negation is on purpose. we just want to check if the events - // were de-duped, obv the signature will be different, since we signed - // a `Vec` with duped events + // NOTE: this negation is on purpose. we just want to check if the + // events were de-duped, obv the signature will be + // different, since we signed a `Vec` with duped events assert!(!decompressed[0].verify(&protocol_key.ref_to()).is_ok()); assert_eq!( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b622a0eb63..4c5503e839 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,9 +1,9 @@ #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; use namada::types::ethereum_events::vote_extensions::VoteExtension; - use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use super::super::*; From 25e095771567c2094ee88bae2b4c01cd0ab5173e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 22 Jul 2022 16:11:59 +0100 Subject: [PATCH 0185/1995] WIP: test_prepare_proposal_vext_normal_op() This test is failing at the moment, ugh. --- .../lib/node/ledger/shell/prepare_proposal.rs | 93 +++++++++++++++++-- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 149a12795e..627c42d4ee 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -286,6 +286,8 @@ mod prepare_block { #[cfg(test)] // TODO: write tests for ethereum events on prepare proposal mod test_prepare_proposal { + use std::collections::HashSet; + use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; use namada::types::ethereum_events::EthereumEvent; @@ -293,7 +295,9 @@ mod prepare_block { use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::Fee; use tendermint_proto::abci::tx_record::TxAction; - use tendermint_proto::abci::{ExtendedCommitInfo, ExtendedVoteInfo}; + use tendermint_proto::abci::{ + ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, + }; use super::super::super::vote_extensions::SignedExt; use super::*; @@ -485,18 +489,89 @@ mod prepare_block { ); } + /// Creates a vote extension digest manually, and encodes it as a + /// [`TxRecord`]. + fn manually_assemble_digest( + protocol_key: &common::SecretKey, + ext: SignedExt, + last_height: BlockHeight, + ) -> TxRecord { + let events = vec![MultiSignedEthEvent { + event: ext.data.ethereum_events[0].clone(), + signers: { + let mut s = HashSet::new(); + s.insert(ext.data.validator_addr.clone()); + s + }, + }]; + let signatures = + vec![(ext.sig.clone(), ext.data.validator_addr.clone())]; + + let vote_extension_digest = + VoteExtensionDigest { events, signatures }; + + assert_eq!( + vec![ext], + vote_extension_digest.clone().decompress(last_height) + ); + + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + + super::record::add(tx) + } + /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering honest validators. - // TODO: finish this #[test] fn test_prepare_proposal_vext_normal_op() { - // let shell: TestShell = todo!(); - // let votes = todo!(); - // let req = RequestPrepareProposal { - // local_last_commit: Some(ExtendedCommitInfo { round: 0, votes }), - // ..Default::default() - // }; - // let rsp = shell.prepare_proposal(req); + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let signed_vote_extension = { + let ext = VoteExtension { + validator_addr, + block_height: LAST_HEIGHT, + ethereum_events: vec![ethereum_event], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let vote = ExtendedVoteInfo { + vote_extension: signed_vote_extension + .clone() + .try_to_vec() + .unwrap(), + ..Default::default() + }; + + let rsp = shell.prepare_proposal(RequestPrepareProposal { + local_last_commit: Some(ExtendedCommitInfo { + votes: vec![vote], + ..Default::default() + }), + ..Default::default() + }); + + let digest = manually_assemble_digest( + &protocol_key, + signed_vote_extension, + LAST_HEIGHT, + ); + assert_eq!(rsp.tx_records, vec![digest]); } /// Test that if an error is encountered while From 15a2a874be317509f2860eec4f2d84d6ed09211f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 11:58:01 +0100 Subject: [PATCH 0186/1995] Fix normal operation test --- .../lib/node/ledger/shell/prepare_proposal.rs | 49 +++++++++++++++---- .../types/ethereum_events/vote_extensions.rs | 8 ++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 627c42d4ee..7fcdd7ef13 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -288,12 +288,14 @@ mod prepare_block { mod test_prepare_proposal { use std::collections::HashSet; + use namada::proto::SignedTxData; use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::{common, ed25519}; use namada::types::storage::{BlockHeight, Epoch}; - use namada::types::transaction::Fee; + use namada::types::transaction::protocol::ProtocolTxType; + use namada::types::transaction::{Fee, TxType}; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -492,10 +494,10 @@ mod prepare_block { /// Creates a vote extension digest manually, and encodes it as a /// [`TxRecord`]. fn manually_assemble_digest( - protocol_key: &common::SecretKey, + _protocol_key: &common::SecretKey, ext: SignedExt, last_height: BlockHeight, - ) -> TxRecord { + ) -> VoteExtensionDigest { let events = vec![MultiSignedEthEvent { event: ext.data.ethereum_events[0].clone(), signers: { @@ -515,11 +517,12 @@ mod prepare_block { vote_extension_digest.clone().decompress(last_height) ); - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) - .to_bytes(); + vote_extension_digest - super::record::add(tx) + // let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + // .sign(&protocol_key) + // .to_bytes(); + // super::record::add(tx) } /// Test if vote extension validation and inclusion in a block @@ -558,20 +561,48 @@ mod prepare_block { ..Default::default() }; - let rsp = shell.prepare_proposal(RequestPrepareProposal { + let mut rsp = shell.prepare_proposal(RequestPrepareProposal { local_last_commit: Some(ExtendedCommitInfo { votes: vec![vote], ..Default::default() }), ..Default::default() }); + let rsp_digest = { + assert_eq!(rsp.tx_records.len(), 1); + let tx_record = rsp.tx_records.pop().unwrap(); + + assert_eq!(tx_record.action(), TxAction::Added); + + let got = Tx::try_from(&tx_record.tx[..]).unwrap(); + let got_signed_tx = + SignedTxData::try_from_slice(&got.data.unwrap()[..]) + .unwrap(); + let protocol_tx = + TxType::try_from_slice(&got_signed_tx.data.unwrap()[..]) + .unwrap(); + + let protocol_tx = match protocol_tx { + TxType::Protocol(protocol_tx) => protocol_tx.tx, + _ => panic!("Test failed"), + }; + + match protocol_tx { + ProtocolTxType::EthereumEvents(digest) => digest, + _ => panic!("Test failed"), + } + }; let digest = manually_assemble_digest( &protocol_key, signed_vote_extension, LAST_HEIGHT, ); - assert_eq!(rsp.tx_records, vec![digest]); + + assert_eq!(rsp_digest, digest); + + // NOTE: this comparison will not work because of timestamps + // assert_eq!(rsp.tx_records, vec![digest]); } /// Test that if an error is encountered while diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 72b00df232..b668f8ae23 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -116,7 +116,9 @@ impl BorshSchema for FractionalVotingPower { /// Aggregates an Ethereum event with the corresponding // validators who saw this event. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct MultiSignedEthEvent { /// The Ethereum event that was signed. pub event: EthereumEvent, @@ -126,7 +128,9 @@ pub struct MultiSignedEthEvent { /// Compresses a set of signed `VoteExtension` instances, to save /// space on a block. -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct VoteExtensionDigest { /// The signatures and signing address of each VoteExtension pub signatures: Vec<(Signature, Address)>, From 6c415e74f9af028601f3611a6ece4fb9e9881934 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 12:01:13 +0100 Subject: [PATCH 0187/1995] Switch to is_err() in unit test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7fcdd7ef13..2f38e425d7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -480,10 +480,10 @@ mod prepare_block { assert_eq!(decompressed.len(), 1); - // NOTE: this negation is on purpose. we just want to check if the + // NOTE: this check is on purpose. we just want to check if the // events were de-duped, obv the signature will be // different, since we signed a `Vec` with duped events - assert!(!decompressed[0].verify(&protocol_key.ref_to()).is_ok()); + assert!(decompressed[0].verify(&protocol_key.ref_to()).is_err()); assert_eq!( decompressed[0].data.ethereum_events, From dc92f1a4c5eb84a1223a6a3a8822ac4e131ce7e2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 12:02:48 +0100 Subject: [PATCH 0188/1995] Update TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 2f38e425d7..4d4d44d125 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -284,7 +284,8 @@ mod prepare_block { } #[cfg(test)] - // TODO: write tests for ethereum events on prepare proposal + // TODO: write a test to check for unreachable code paths in + // prepare proposals, when processing ethereum events mod test_prepare_proposal { use std::collections::HashSet; From faef1cae05291a5333c210a1a0fda17a0b616ab0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 13:46:32 +0100 Subject: [PATCH 0189/1995] Test insufficient voting power This test should be panicking, but instead it passes. We need a way to set all validators' voting power to 0. --- .../lib/node/ledger/shell/prepare_proposal.rs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4d4d44d125..f52c6ef213 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -289,6 +289,9 @@ mod prepare_block { mod test_prepare_proposal { use std::collections::HashSet; + use namada::ledger::pos::namada_proof_of_stake::types::{ + VotingPower, WeightedValidator, + }; use namada::proto::SignedTxData; use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; @@ -606,6 +609,87 @@ mod prepare_block { // assert_eq!(rsp.tx_records, vec![digest]); } + /// Test if vote extension validation and inclusion in a block + /// behaves as expected, considering <= 2/3 voting power. + #[test] + // TODO: make sure this test panics + //#[should_panic(expected = "entered unreachable code")] + fn test_prepare_proposal_vext_insufficient_voting_power() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + + // starting the shell like this will contain insufficient voting + // power + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + // artificially change the voting power of the default validator to + // 0 + // + // TODO: this is not working + let events_epoch = shell + .storage + .block + .pred_epochs + .get_epoch(LAST_HEIGHT) + .expect("Test failed"); + let validator_set = { + let params = shell.storage.read_pos_params(); + let mut epochs = shell.storage.read_validator_set(); + let mut data = + epochs.get(events_epoch).cloned().expect("Test failed"); + + data.active = data + .active + .iter() + .cloned() + .map(|v| WeightedValidator { + voting_power: VotingPower::from(0u64), + ..v + }) + .collect(); + + epochs.set(data, events_epoch, ¶ms); + epochs + }; + shell.storage.write_validator_set(&validator_set); + + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let signed_vote_extension = { + let ext = VoteExtension { + validator_addr, + block_height: LAST_HEIGHT, + ethereum_events: vec![ethereum_event], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let vote = ExtendedVoteInfo { + vote_extension: signed_vote_extension + .clone() + .try_to_vec() + .unwrap(), + ..Default::default() + }; + + // this should panic + shell.prepare_proposal(RequestPrepareProposal { + local_last_commit: Some(ExtendedCommitInfo { + votes: vec![vote], + ..Default::default() + }), + ..Default::default() + }); + } + /// Test that if an error is encountered while /// trying to process a tx from the mempool, /// we simply exclude it from the proposal From 7430fe8cf2e7aafa280eb5c75add8d944dd799a3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 13:58:09 +0100 Subject: [PATCH 0190/1995] Improve unreachable code message --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f52c6ef213..b01d8cc9e2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -101,13 +101,14 @@ mod prepare_block { _ => unreachable!( "Honest Namada validators will always sign a \ VoteExtension, even if no Ethereum events were \ - observed at a given block height. In fact, signing \ - an empty VoteExtension commits the fact no events \ - were observed by a majority of validators. Likewise, \ - a Tendermint quorum should never decide on a block \ - including vote extensions reflecting less than or \ - equal to 2/3 of the total stake. These scenarios are \ - virtually impossible, so we will panic here." + observed at a given block height. In fact, a quorum \ + of signed empty VoteExtension commits the fact no \ + events were observed by a majority of validators. \ + Likewise, a Tendermint quorum should never decide on \ + a block including vote extensions reflecting less \ + than or equal to 2/3 of the total stake. These \ + scenarios are virtually impossible, so we will panic \ + here." ), }; From 6f35c9f3c76eece65e64b85813b95f15744e7343 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 14:33:00 +0100 Subject: [PATCH 0191/1995] Add a TODO for DKG txs --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b01d8cc9e2..e04854c32b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -140,6 +140,13 @@ mod prepare_block { } /// Builds a batch of DKG decrypted transactions + // TODO: we won't have frontrunning protection until V2 of the Anoma + // protocol; Namada runs V1, therefore this method is + // essentially a NOOP, and ought to be removed + // + // sources: + // - https://specs.anoma.net/main/releases/v2.html + // - https://github.com/anoma/ferveo fn build_decrypted_txs(&mut self) -> Vec { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); From 4da1902d043cbf1808ff8130c6b68ae0b53e1547 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 15:03:26 +0100 Subject: [PATCH 0192/1995] Run clippy and fmt --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 12 +++--------- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e04854c32b..f9368866df 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -113,7 +113,7 @@ mod prepare_block { }; let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) + .sign(protocol_key) .to_bytes(); let tx_record = record::add(tx); @@ -566,10 +566,7 @@ mod prepare_block { ext }; let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension - .clone() - .try_to_vec() - .unwrap(), + vote_extension: signed_vote_extension.try_to_vec().unwrap(), ..Default::default() }; @@ -681,10 +678,7 @@ mod prepare_block { ext }; let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension - .clone() - .try_to_vec() - .unwrap(), + vote_extension: signed_vote_extension.try_to_vec().unwrap(), ..Default::default() }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 4c5503e839..42b5d636a7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -344,7 +344,7 @@ mod extend_votes { }], }], block_height: signed_height, - validator_addr: address.clone(), + validator_addr: address, } .sign(shell.mode.get_protocol_key().expect("Test failed")); From 31e38aeacee2e6cd3bf34404f58ae7c15f2cafc4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 15:57:31 +0100 Subject: [PATCH 0193/1995] Fix insufficient voting power test --- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f9368866df..99ef40982a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -318,6 +318,7 @@ mod prepare_block { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, }; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; /// Test that if a tx from the mempool is not a @@ -617,27 +618,22 @@ mod prepare_block { /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering <= 2/3 voting power. #[test] - // TODO: make sure this test panics - //#[should_panic(expected = "entered unreachable code")] + #[should_panic(expected = "entered unreachable code")] fn test_prepare_proposal_vext_insufficient_voting_power() { - const LAST_HEIGHT: BlockHeight = BlockHeight(3); + const FIRST_HEIGHT: BlockHeight = BlockHeight(0); + const LAST_HEIGHT: BlockHeight = BlockHeight(FIRST_HEIGHT.0 + 11); // starting the shell like this will contain insufficient voting // power let (mut shell, _, _) = test_utils::setup(); - // artificially change the block height - shell.storage.last_height = LAST_HEIGHT; - // artificially change the voting power of the default validator to - // 0 - // - // TODO: this is not working + // zero and change the block height, to move to a new epoch let events_epoch = shell .storage .block .pred_epochs - .get_epoch(LAST_HEIGHT) + .get_epoch(FIRST_HEIGHT) .expect("Test failed"); let validator_set = { let params = shell.storage.read_pos_params(); @@ -660,6 +656,13 @@ mod prepare_block { }; shell.storage.write_validator_set(&validator_set); + let mut req = FinalizeBlock::default(); + req.header.time = namada::types::time::DateTimeUtc::now(); + shell.storage.last_height = LAST_HEIGHT; + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + + // test prepare proposal let (protocol_key, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); From 7a5d4cb540bd8ac07555000ace5c275b55be6955 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 16:02:01 +0100 Subject: [PATCH 0194/1995] Improve comment --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 99ef40982a..096b4c1c10 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -628,7 +628,8 @@ mod prepare_block { let (mut shell, _, _) = test_utils::setup(); // artificially change the voting power of the default validator to - // zero and change the block height, to move to a new epoch + // zero, change the block height, and commit a dummy block, + // to move to a new epoch let events_epoch = shell .storage .block From d42519320ecc9f50edb103258ac3d0d1687c4320 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 16:28:38 +0100 Subject: [PATCH 0195/1995] Misc merge fixes --- .../lib/node/ledger/shell/process_proposal.rs | 32 ++++++++++--------- .../lib/node/ledger/shell/vote_extensions.rs | 1 - .../types/ethereum_events/vote_extensions.rs | 5 ++- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 054c218199..9f1aaa3d5f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -133,22 +133,24 @@ where .get_epoch(BlockHeight(self.storage.last_height.0)); let total_power = u64::from(self.get_total_voting_power(epoch)); - if extensions.into_iter().all(|(ext, validator)| match self - .get_validator_from_address(&validator, epoch) - { - Ok((power, _)) => { - voting_power += FractionalVotingPower::new( - u64::from(power), - total_power, - ) - .unwrap_or_default(); - self.validate_vote_extension( - ext, - validator, - self.storage.last_height, - ) + if extensions.into_iter().all(|ext| { + match self.get_validator_from_address( + &ext.data.validator_addr, + epoch, + ) { + Ok((power, _)) => { + voting_power += FractionalVotingPower::new( + u64::from(power), + total_power, + ) + .unwrap_or_default(); + self.validate_vote_extension( + ext, + self.storage.last_height, + ) + } + _ => false, } - _ => false, }) { if voting_power > FractionalVotingPower::new(2, 3).unwrap() diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b860aabb8d..42b5d636a7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -3,7 +3,6 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; - use namada::types::address::Address; use namada::types::ethereum_events::vote_extensions::VoteExtension; use super::super::*; diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index ae6861d827..1c481899b0 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -164,7 +164,7 @@ impl VoteExtensionDigest { pub fn decompress( self, last_height: BlockHeight, - ) -> Vec<(Signed, Address)> { + ) -> Vec> { let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; @@ -185,7 +185,7 @@ impl VoteExtensionDigest { ext.ethereum_events.sort(); let signed = Signed { data: ext, sig }; - extensions.push((signed, addr)); + extensions.push(signed); } extensions } @@ -324,7 +324,6 @@ mod tests { let decompressed = digest .decompress(last_block_height) .into_iter() - .map(|event| event.0) .collect::>>(); assert_eq!(ext, decompressed); From ccc1c972ba6f2cba1c8be211f25b5fbb28bd0b35 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 16:48:04 +0100 Subject: [PATCH 0196/1995] Add a TODO --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 42b5d636a7..b239b491fd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -112,8 +112,10 @@ mod extend_votes { ); return None; } - let epoch = self.storage.block.pred_epochs.get_epoch(height); + // TODO: verify if we have any duplicate ethereum events + let _ = todo!(); // get the public key associated with this validator + let epoch = self.storage.block.pred_epochs.get_epoch(height); let validator = &ext.data.validator_addr; let (voting_power, pk) = self .get_validator_from_address(validator, epoch) From 01b36345b46cc35ddce111898948a4c6a29666d8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 19:58:35 +0100 Subject: [PATCH 0197/1995] Filter out duplicate Ethereum events TODO: Fix unit tests, to comply with the new logic. --- .../lib/node/ledger/shell/vote_extensions.rs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b239b491fd..8792dd91bf 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -112,11 +112,28 @@ mod extend_votes { ); return None; } - // TODO: verify if we have any duplicate ethereum events - let _ = todo!(); + // verify if we have any duplicate Ethereum events, + // and if these are sorted + let have_dupes = { + let some_ethereum_events = + ext.data.ethereum_events.iter().map(Some); + let first_elems = + std::iter::once(None).chain(some_ethereum_events); + let second_elems = ext.data.ethereum_events.iter().map(Some); + first_elems + .zip(second_elems) + .all(|(ev_1, ev_2)| ev_1 < ev_2) + }; + let validator = &ext.data.validator_addr; + if have_dupes { + tracing::error!( + %validator, + "Found duplicate or non-sorted Ethereum events in a vote extension from validator" + ); + return None; + } // get the public key associated with this validator let epoch = self.storage.block.pred_epochs.get_epoch(height); - let validator = &ext.data.validator_addr; let (voting_power, pk) = self .get_validator_from_address(validator, epoch) .map_err(|err| { From 5000a856e7e40152774a736d085e7195d55bf348 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 25 Jul 2022 20:01:41 +0100 Subject: [PATCH 0198/1995] Improve duped events comment --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 8792dd91bf..f0a4af1b8f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -113,7 +113,7 @@ mod extend_votes { return None; } // verify if we have any duplicate Ethereum events, - // and if these are sorted + // and if these are sorted in ascending order let have_dupes = { let some_ethereum_events = ext.data.ethereum_events.iter().map(Some); From 1319ef3d3c1e3dfc6d24802a790dbd1b7d1fd601 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 09:07:32 +0100 Subject: [PATCH 0199/1995] Simplify filtering of duped events --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index f0a4af1b8f..ab48dfd4af 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -115,14 +115,10 @@ mod extend_votes { // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order let have_dupes = { - let some_ethereum_events = - ext.data.ethereum_events.iter().map(Some); - let first_elems = - std::iter::once(None).chain(some_ethereum_events); - let second_elems = ext.data.ethereum_events.iter().map(Some); - first_elems - .zip(second_elems) - .all(|(ev_1, ev_2)| ev_1 < ev_2) + ext.data + .ethereum_events + .windows(2) + .all(|evs| evs.len() < 2 || (evs[0] < evs[1])) }; let validator = &ext.data.validator_addr; if have_dupes { From db43ebe8c3d712312cce6e31f4ac830d8a57d47d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 09:17:10 +0100 Subject: [PATCH 0200/1995] Fix logic in dedup --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ab48dfd4af..a68fbf577e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -114,14 +114,14 @@ mod extend_votes { } // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order - let have_dupes = { - ext.data + let have_dupes_or_non_sorted = { + !ext.data .ethereum_events .windows(2) .all(|evs| evs.len() < 2 || (evs[0] < evs[1])) }; let validator = &ext.data.validator_addr; - if have_dupes { + if have_dupes_or_non_sorted { tracing::error!( %validator, "Found duplicate or non-sorted Ethereum events in a vote extension from validator" From 5c60bf62664a85c82fe39037090877384f7d55ec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 09:36:19 +0100 Subject: [PATCH 0201/1995] Fix dedup test --- .../lib/node/ledger/shell/prepare_proposal.rs | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 096b4c1c10..678cd12c08 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -454,10 +454,10 @@ mod prepare_block { check_vote_extension_filtering(&mut shell, signed_vote_extension); } - /// Test if we are de-duplicating Ethereum events in + /// Test if we are filtering out duped Ethereum events in /// prepare proposals. #[test] - fn test_prepare_proposal_deduplicate_ethereum_events() { + fn test_prepare_proposal_filter_duped_ethereum_events() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); let (mut shell, _, _) = test_utils::setup(); @@ -473,7 +473,7 @@ mod prepare_block { transfers: vec![], }; let signed_vote_extension = { - let ev = ethereum_event.clone(); + let ev = ethereum_event; let ext = VoteExtension { validator_addr, block_height: LAST_HEIGHT, @@ -484,24 +484,17 @@ mod prepare_block { ext }; - let digest = { + let maybe_digest = { let votes = vec![vote_extension_serialize(signed_vote_extension)]; - shell.compress_vote_extensions(votes).unwrap() + shell.compress_vote_extensions(votes) }; - let decompressed = digest.decompress(LAST_HEIGHT); - assert_eq!(decompressed.len(), 1); - - // NOTE: this check is on purpose. we just want to check if the - // events were de-duped, obv the signature will be - // different, since we signed a `Vec` with duped events - assert!(decompressed[0].verify(&protocol_key.ref_to()).is_err()); - - assert_eq!( - decompressed[0].data.ethereum_events, - vec![ethereum_event] - ); + // we should be filtering out the vote extension with + // duped ethereum events; therefore, no valid vote + // extensions will remain, and we will get no + // digest from compressing nil vote extensions + assert!(maybe_digest.is_none()); } /// Creates a vote extension digest manually, and encodes it as a From e396e7eed429a9c9f175e27b2e150d0f7e5c2eda Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 12:32:35 +0100 Subject: [PATCH 0202/1995] Remove redundant length check --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index a68fbf577e..e8dd7fb04b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -117,8 +117,9 @@ mod extend_votes { let have_dupes_or_non_sorted = { !ext.data .ethereum_events + // TODO: move to `array_windows` when it reaches Rust stable .windows(2) - .all(|evs| evs.len() < 2 || (evs[0] < evs[1])) + .all(|evs| evs[0] < evs[1]) }; let validator = &ext.data.validator_addr; if have_dupes_or_non_sorted { From 8b73c987d03b47ba1f58b4e81dd1280795d64c3f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 13:04:14 +0100 Subject: [PATCH 0203/1995] Prevent block proposers from voting twice on vote extensions In the old VoteExtensionDigest representation, it was possible for block proposers to include more than one vote for a particular set of vote extensions. This commit should fix that. --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 ++++++++++++++----- .../types/ethereum_events/vote_extensions.rs | 22 ++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 678cd12c08..565d4724c0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,7 +2,7 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { - use std::collections::{BTreeMap, HashSet}; + use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; @@ -182,7 +182,7 @@ mod prepare_block { ); let mut event_observers = BTreeMap::new(); - let mut signatures = Vec::new(); + let mut signatures = HashMap::new(); let total_voting_power = self.get_total_voting_power(Some(events_epoch)).into(); @@ -205,10 +205,17 @@ mod prepare_block { } // register the signature of `validator_addr` - let addr = validator_addr; + let addr = validator_addr.clone(); let sig = vote_extension.sig; - signatures.push((sig, addr)); + if let Some(sig) = signatures.insert(addr, sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing VoteExtensionDigest" + ); + } } let voting_power = @@ -512,8 +519,11 @@ mod prepare_block { s }, }]; - let signatures = - vec![(ext.sig.clone(), ext.data.validator_addr.clone())]; + let signatures = { + let mut s = HashMap::new(); + s.insert(ext.data.validator_addr.clone(), ext.sig.clone()); + s + }; let vote_extension_digest = VoteExtensionDigest { events, signatures }; diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 1c481899b0..e1754a7ed8 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -1,7 +1,7 @@ //! Contains types necessary for processing Ethereum events //! in vote extensions. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::ops::{Add, AddAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -154,7 +154,7 @@ pub struct MultiSignedEthEvent { )] pub struct VoteExtensionDigest { /// The signatures and signing address of each VoteExtension - pub signatures: Vec<(Signature, Address)>, + pub signatures: HashMap, /// The events that were reported pub events: Vec, } @@ -169,7 +169,7 @@ impl VoteExtensionDigest { let mut extensions = vec![]; - for (sig, addr) in signatures.into_iter() { + for (addr, sig) in signatures.into_iter() { let mut ext = VoteExtension::empty(last_height, addr.clone()); for event in events.iter() { @@ -274,7 +274,7 @@ mod tests { transfers: vec![], }; - let validator_1 = address::testing::established_address_2(); + let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); let ext = |validator: Address| -> VoteExtension { @@ -296,14 +296,16 @@ mod tests { // we have the `Signed` instances we need, // let us now compress them into a single `VoteExtensionDigest` - let signatures = vec![ - (ext[0].sig.clone(), validator_1), - (ext[1].sig.clone(), validator_2), - ]; + let signatures: HashMap<_, _> = [ + (validator_1.clone(), ext[0].sig.clone()), + (validator_2.clone(), ext[1].sig.clone()), + ] + .into_iter() + .collect(); let signers = { let mut s = HashSet::new(); - s.insert(signatures[0].1.clone()); - s.insert(signatures[1].1.clone()); + s.insert(validator_1); + s.insert(validator_2); s }; let events = vec![ From 0ba7f67c407adb2d0b2ee3f063fa50ab0419f0e7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 13:04:14 +0100 Subject: [PATCH 0204/1995] Prevent block proposers from voting twice on vote extensions In the old VoteExtensionDigest representation, it was possible for block proposers to include more than one vote for a particular set of vote extensions. This commit should fix that. --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 ++++++++++++++----- .../types/ethereum_events/vote_extensions.rs | 22 ++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 096b4c1c10..e05e751e58 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,7 +2,7 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { - use std::collections::{BTreeMap, HashSet}; + use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; @@ -182,7 +182,7 @@ mod prepare_block { ); let mut event_observers = BTreeMap::new(); - let mut signatures = Vec::new(); + let mut signatures = HashMap::new(); let total_voting_power = self.get_total_voting_power(Some(events_epoch)).into(); @@ -205,10 +205,17 @@ mod prepare_block { } // register the signature of `validator_addr` - let addr = validator_addr; + let addr = validator_addr.clone(); let sig = vote_extension.sig; - signatures.push((sig, addr)); + if let Some(sig) = signatures.insert(addr, sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing VoteExtensionDigest" + ); + } } let voting_power = @@ -519,8 +526,11 @@ mod prepare_block { s }, }]; - let signatures = - vec![(ext.sig.clone(), ext.data.validator_addr.clone())]; + let signatures = { + let mut s = HashMap::new(); + s.insert(ext.data.validator_addr.clone(), ext.sig.clone()); + s + }; let vote_extension_digest = VoteExtensionDigest { events, signatures }; diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index b668f8ae23..4ef3905037 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -1,7 +1,7 @@ //! Contains types necessary for processing Ethereum events //! in vote extensions. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -133,7 +133,7 @@ pub struct MultiSignedEthEvent { )] pub struct VoteExtensionDigest { /// The signatures and signing address of each VoteExtension - pub signatures: Vec<(Signature, Address)>, + pub signatures: HashMap, /// The events that were reported pub events: Vec, } @@ -148,7 +148,7 @@ impl VoteExtensionDigest { let mut extensions = vec![]; - for (sig, addr) in signatures.into_iter() { + for (addr, sig) in signatures.into_iter() { let mut ext = VoteExtension::empty(last_height, addr.clone()); for event in events.iter() { @@ -253,7 +253,7 @@ mod tests { transfers: vec![], }; - let validator_1 = address::testing::established_address_2(); + let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); let ext = |validator: Address| -> VoteExtension { @@ -275,14 +275,16 @@ mod tests { // we have the `Signed` instances we need, // let us now compress them into a single `VoteExtensionDigest` - let signatures = vec![ - (ext[0].sig.clone(), validator_1), - (ext[1].sig.clone(), validator_2), - ]; + let signatures: HashMap<_, _> = [ + (validator_1.clone(), ext[0].sig.clone()), + (validator_2.clone(), ext[1].sig.clone()), + ] + .into_iter() + .collect(); let signers = { let mut s = HashSet::new(); - s.insert(signatures[0].1.clone()); - s.insert(signatures[1].1.clone()); + s.insert(validator_1); + s.insert(validator_2); s }; let events = vec![ From 8d6737a163006b381eaa5079085035d2a7174822 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 13:58:05 +0100 Subject: [PATCH 0205/1995] Use TWO_THIRDS const in place of manually constructing a FractionalVotingPower --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 +--- shared/src/types/ethereum_events/vote_extensions.rs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9f1aaa3d5f..8b59e773b3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -152,9 +152,7 @@ where _ => false, } }) { - if voting_power - > FractionalVotingPower::new(2, 3).unwrap() - { + if voting_power > FractionalVotingPower::TWO_THIRDS { TxResult { code: ErrorCodes::Ok.into(), info: "Process proposal accepted this \ diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index e1754a7ed8..92c828c3c7 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -232,7 +232,7 @@ mod tests { #[test] fn test_fractional_voting_power_ord_eq() { assert!( - FractionalVotingPower::new(2, 3).unwrap() + FractionalVotingPower::TWO_THIRDS > FractionalVotingPower::new(1, 4).unwrap() ); assert!( From 184af181bb00f3641ef2d6a9749adc9f014b4f7b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 15:59:09 +0100 Subject: [PATCH 0206/1995] Reuse filter_invalid_vote_extensions in ProcessProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 73 +++++++++++++------ .../lib/node/ledger/shell/process_proposal.rs | 20 ++--- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 565d4724c0..6a8837c2fc 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -185,16 +185,23 @@ mod prepare_block { let mut signatures = HashMap::new(); let total_voting_power = - self.get_total_voting_power(Some(events_epoch)).into(); - let mut voting_power = 0u64; + u64::from(self.get_total_voting_power(Some(events_epoch))); + let mut voting_power = FractionalVotingPower::default(); + + let deserialized = deserialize_vote_extensions(vote_extensions); for (validator_voting_power, vote_extension) in - self.filter_invalid_vote_extensions(vote_extensions) + self.filter_invalid_vote_extensions(deserialized) { let validator_addr = vote_extension.data.validator_addr; // update voting power - voting_power += u64::from(validator_voting_power); + let validator_voting_power = u64::from(validator_voting_power); + voting_power += FractionalVotingPower::new( + validator_voting_power, + total_voting_power, + ) + .unwrap_or_default(); // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { @@ -218,10 +225,6 @@ mod prepare_block { } } - let voting_power = - FractionalVotingPower::new(voting_power, total_voting_power) - .unwrap(); - if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( "Tendermint has decided on a block including vote \ @@ -238,30 +241,51 @@ mod prepare_block { Some(VoteExtensionDigest { events, signatures }) } + /// Takes a list of signed vote extensions, + /// and filters out invalid instances, returning + /// all residual values (including invalid vote + /// extensions). + #[inline] + pub fn filter_invalid_vote_extensions_residuals( + &self, + vote_extensions: impl IntoIterator + 'static, + ) -> impl Iterator> + '_ + { + vote_extensions.into_iter().map(|vote_extension| { + self.validate_vote_ext_and_get_it_back( + vote_extension, + self.storage.last_height, + ) + }) + } + /// Takes a list of signed vote extensions, /// and filters out invalid instances. - fn filter_invalid_vote_extensions( + #[inline] + pub fn filter_invalid_vote_extensions( &self, - vote_extensions: Vec, + vote_extensions: impl IntoIterator + 'static, ) -> impl Iterator + '_ { - vote_extensions.into_iter().filter_map(|vote| { - let vote_extension = Signed::::try_from_slice( - &vote.vote_extension[..], - ) + self.filter_invalid_vote_extensions_residuals(vote_extensions) + .filter_map(|ext| ext) + } + } + + /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the + /// deserialized [`SignedExt`] instances. + fn deserialize_vote_extensions( + vote_extensions: Vec, + ) -> impl Iterator + 'static { + vote_extensions.into_iter().filter_map(|vote| { + Signed::::try_from_slice(&vote.vote_extension[..]) .map_err(|err| { tracing::error!( ?err, "Failed to deserialize signed vote extension", ); }) - .ok()?; - - self.validate_vote_ext_and_get_it_back( - vote_extension, - self.storage.last_height, - ) - }) - } + .ok() + }) } /// Functions for creating the appropriate TxRecord given the @@ -362,7 +386,10 @@ mod prepare_block { shell: &mut TestShell, vext: SignedExt, ) { - let votes = vec![vote_extension_serialize(vext)]; + let votes = + deserialize_vote_extensions(vec![vote_extension_serialize( + vext, + )]); let filtered_votes: Vec<_> = shell.filter_invalid_vote_extensions(votes).collect(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8b59e773b3..5cc3de01d8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -125,6 +125,8 @@ where ProtocolTxType::EthereumEvents(digest) => { let extensions = digest.decompress(self.storage.last_height); + let filtered_extensions = self + .filter_invalid_vote_extensions_residuals(extensions); let mut voting_power = FractionalVotingPower::default(); let epoch = self .storage @@ -133,24 +135,16 @@ where .get_epoch(BlockHeight(self.storage.last_height.0)); let total_power = u64::from(self.get_total_voting_power(epoch)); - if extensions.into_iter().all(|ext| { - match self.get_validator_from_address( - &ext.data.validator_addr, - epoch, - ) { - Ok((power, _)) => { + if filtered_extensions.into_iter().all(|maybe_ext| { + maybe_ext + .map(|(power, _)| { voting_power += FractionalVotingPower::new( u64::from(power), total_power, ) .unwrap_or_default(); - self.validate_vote_extension( - ext, - self.storage.last_height, - ) - } - _ => false, - } + }) + .is_some() }) { if voting_power > FractionalVotingPower::TWO_THIRDS { TxResult { From dafc5c5c09b592e1620b25fab3d15454292f04d6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 26 Jul 2022 16:03:06 +0100 Subject: [PATCH 0207/1995] Move get_epoch() to a new block --- apps/src/lib/node/ledger/shell/process_proposal.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5cc3de01d8..1bd699e029 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -128,13 +128,13 @@ where let filtered_extensions = self .filter_invalid_vote_extensions_residuals(extensions); let mut voting_power = FractionalVotingPower::default(); - let epoch = self - .storage - .block - .pred_epochs - .get_epoch(BlockHeight(self.storage.last_height.0)); - let total_power = - u64::from(self.get_total_voting_power(epoch)); + let total_power = { + let epoch = + self.storage.block.pred_epochs.get_epoch( + BlockHeight(self.storage.last_height.0), + ); + u64::from(self.get_total_voting_power(epoch)) + }; if filtered_extensions.into_iter().all(|maybe_ext| { maybe_ext .map(|(power, _)| { From a836d1dcf728fc205c7ce5665ba49e95556aba56 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 09:20:05 +0100 Subject: [PATCH 0208/1995] Fix vote extension rejection --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1bd699e029..28d256b2b5 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -165,9 +165,9 @@ where } } else { TxResult { - code: ErrorCodes::Ok.into(), + code: ErrorCodes::InvalidVoteExntension.into(), info: "Process proposal rejected this proposal \ - because the at least on of the vote \ + because at least one of the vote \ extensions included was invalid." .into(), } From 5999d818c136e3fe76f7b9ac544c518fa0485a06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 09:58:25 +0100 Subject: [PATCH 0209/1995] Refactor process_single_tx --- .../lib/node/ledger/shell/process_proposal.rs | 89 +++++++++++-------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 28d256b2b5..e2a89cf83b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -41,38 +41,9 @@ where .txs .iter() .map(|tx_bytes| { - ExecTxResult::from(match Tx::try_from(tx_bytes.as_slice()) { - Ok(tx) => match process_tx(tx) { - // This occurs if the wrapper / protocol tx signature is invalid - Err(err) => TxResult { - code: ErrorCodes::InvalidSig.into(), - info: err.to_string(), - }, - Ok(tx) => { - if let TxType::Protocol(ProtocolTx{tx: ProtocolTxType::EthereumEvents(_), ..}) = &tx { - vote_ext_digest_num += 1; - // genesis block should not have vote extensions - if self.storage.last_height.0 == 0 { - TxResult { - code: ErrorCodes::InvalidVoteExntension.into(), - info: "No vote extensions should be included in block height 0".into() - } - } else { - self.process_single_tx(tx) - } - } else { - self.process_single_tx(tx) - } - } - } - Err(_) => { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The submitted transaction was not deserializable" - .into(), - } - } - }) + ExecTxResult::from( + self.process_single_tx(tx_bytes, &mut vote_ext_digest_num), + ) }) .collect(); @@ -109,10 +80,38 @@ 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: TxType) -> TxResult { + pub(crate) fn process_single_tx( + &mut self, + tx_bytes: &[u8], + vote_ext_digest_num: &mut usize, + ) -> TxResult { + let maybe_tx = Tx::try_from(tx_bytes).map_or_else( + |_| { + Err(TxResult { + code: ErrorCodes::InvalidTx.into(), + info: "The submitted transaction was not deserializable" + .into(), + }) + }, + |tx| { + process_tx(tx).map_err(|err| { + // This occurs if the wrapper / protocol tx signature is + // invalid + TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + } + }) + }, + ); + let tx = match maybe_tx { + Ok(tx) => tx, + Err(tx_result) => return tx_result, + }; + // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - let hash = hash_tx(&Tx::from(tx.clone()).to_bytes()); + match tx { // If it is a raw transaction, we do no further validation TxType::Raw(_) => TxResult { @@ -123,10 +122,22 @@ where }, TxType::Protocol(protocol_tx) => match protocol_tx.tx { ProtocolTxType::EthereumEvents(digest) => { + *vote_ext_digest_num += 1; + + if self.storage.last_height.0 == 0 { + return TxResult { + code: ErrorCodes::InvalidVoteExntension.into(), + info: "No vote extensions should be included in \ + block height 0" + .into(), + }; + } + let extensions = digest.decompress(self.storage.last_height); let filtered_extensions = self .filter_invalid_vote_extensions_residuals(extensions); + let mut voting_power = FractionalVotingPower::default(); let total_power = { let epoch = @@ -135,6 +146,7 @@ where ); u64::from(self.get_total_voting_power(epoch)) }; + if filtered_extensions.into_iter().all(|maybe_ext| { maybe_ext .map(|(power, _)| { @@ -215,7 +227,7 @@ where code: ErrorCodes::InvalidTx.into(), info: format!( "The ciphertext of the wrapped tx {} is invalid", - hash + hash_tx(tx_bytes) ), } } else { @@ -269,7 +281,7 @@ where match process_tx(req_tx.clone()) { Ok(TxType::Wrapper(_)) => {} Ok(TxType::Protocol(_)) => { - let result = self.process_single_tx(&req.tx); + let result = self.process_single_tx(&req.tx, &mut 0usize); return shim::request::ProcessedTx { tx: req.tx, result }; } Ok(_) => { @@ -289,7 +301,7 @@ where } } - let wrapper_resp = self.process_single_tx(&req.tx); + let wrapper_resp = self.process_single_tx(&req.tx, &mut 0usize); let privkey = ::G2Affine::prime_subgroup_generator(); if wrapper_resp.code == 0 { @@ -303,7 +315,8 @@ where // 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); + let decoded_resp = + self.process_single_tx(&decoded, &mut 0usize); // this ensures that the tx queue is empty even if an error // happened in [`process_proposal`]. self.storage.tx_queue.pop(); From 981649b0fe7b2e102cb10c015bdfa5f9a0f530a2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 10:17:29 +0100 Subject: [PATCH 0210/1995] Fix test_decompress_ethereum_events() --- shared/src/types/ethereum_events/vote_extensions.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 92c828c3c7..f8cc801f14 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -323,11 +323,17 @@ mod tests { // finally, decompress the `VoteExtensionDigest` back into a // `Vec>` - let decompressed = digest + let mut decompressed = digest .decompress(last_block_height) .into_iter() .collect::>>(); + // decompressing yields an arbitrary ordering of `VoteExtension` + // instances, which is fine + if decompressed[0].data.validator_addr != ext[0].data.validator_addr { + decompressed.swap(0, 1); + } + assert_eq!(ext, decompressed); assert!(decompressed[0].verify(&sk_1.ref_to()).is_ok()); assert!(decompressed[1].verify(&sk_2.ref_to()).is_ok()); From 286762d2575e4ac645353de654664685dfe2b502 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 10:53:24 +0100 Subject: [PATCH 0211/1995] Misc fixes/changes --- .../lib/node/ledger/shell/process_proposal.rs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e2a89cf83b..02fd7f20a9 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,7 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; -use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; +use namada::types::transaction::protocol::ProtocolTxType; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -48,8 +48,8 @@ where .collect(); ResponseProcessProposal { - status: if vote_ext_digest_num <= 1 - && tx_results.iter().any(|res| res.code > 3) + status: if vote_ext_digest_num > 1 + || tx_results.iter().any(|res| res.code > 3) { ProposalStatus::Reject as i32 } else { @@ -166,14 +166,23 @@ where .into(), } } else { - TxResult { - code: ErrorCodes::InvalidVoteExntension.into(), - info: "Process proposal rejected this \ - proposal because the backing stake of \ - the vote extensions published in the \ - proposal was insufficient" - .into(), - } + // ```ignore + // TxResult { + // code: ErrorCodes::InvalidVoteExntension. into(), + // info: " Process proposal rejected this \ + // proposal because the backing stake of \ + // the vote extensions published in the \ + // proposal was insufficient" + // .into(), + // } + // ``` + // + // TODO: is unreachable really fine? + unreachable!( + "We should never get an insufficient backing \ + stake on vote extensions published in \ + proposals." + ) } } else { TxResult { From 841353a3addc38b22667b8972a7fcbed8c32247a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 11:13:57 +0100 Subject: [PATCH 0212/1995] Document reasons for rejecting a proposal --- .../src/lib/node/ledger/shell/process_proposal.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 02fd7f20a9..c6cb1e9d55 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -47,10 +47,19 @@ where }) .collect(); + // We should not have more than one `VoteExtensionDigest` in + // a proposal from some round's leader. + let too_many_vext_digests = vote_ext_digest_num > 1; + + // Erroneous transactions were detected when processing + // the leader's proposal. We allow txs that do not + // deserialize properly, that have invalid signatures + // and that have invalid wasm code to reach FinalizeBlock. + // This is the reason behind that greater than three check. + let invalid_txs = tx_results.iter().any(|res| res.code > 3); + ResponseProcessProposal { - status: if vote_ext_digest_num > 1 - || tx_results.iter().any(|res| res.code > 3) - { + status: if too_many_vext_digests || invalid_txs { ProposalStatus::Reject as i32 } else { ProposalStatus::Accept as i32 From b596befd46692f07d7395a0211a488b6c343d5e9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 12:58:22 +0100 Subject: [PATCH 0213/1995] WIP: Test if we drop proposals with more than one digest --- .../lib/node/ledger/shell/process_proposal.rs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c6cb1e9d55..3073e86d8b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -391,8 +391,64 @@ mod test_process_proposal { #[cfg(not(feature = "ABCI"))] use crate::node::ledger::shell::test_utils::TestError; use crate::node::ledger::shell::test_utils::{ - gen_keypair, ProcessProposal, TestShell, + self, gen_keypair, ProcessProposal, TestShell, }; + use crate::wallet; + + /// Test that if a proposal contains more than one `VoteExtensionDigest`, + /// we reject it. + #[test] + fn test_more_than_one_vext_digest_rejected() { + let (mut shell, _, _) = test_utils::setup(); + let validator_addr = wallet::defaults::validator_address(); + drop((shell, validator_addr)); + /* + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction data".as_bytes().to_owned()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 0.into(), + token: xan(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let tx = Tx::new( + vec![], + Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), + ) + .to_bytes(); + #[allow(clippy::redundant_clone)] + let request = ProcessProposal { + txs: vec![tx.clone()], + }; + + let response = if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + }; + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); + assert_eq!( + 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 if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. From f748ecf46194807559b11f2fd7c0b6061b246ace Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 13:19:20 +0100 Subject: [PATCH 0214/1995] Return the reason for rejecting a vote extension --- .../lib/node/ledger/shell/prepare_proposal.rs | 12 ++- .../lib/node/ledger/shell/process_proposal.rs | 94 +++++++++---------- .../lib/node/ledger/shell/vote_extensions.rs | 30 ++++-- 3 files changed, 76 insertions(+), 60 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6a8837c2fc..a02f788bd5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -15,7 +15,7 @@ mod prepare_block { ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use super::super::vote_extensions::SignedExt; + use super::super::vote_extensions::{SignedExt, VoteExtensionError}; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -249,8 +249,12 @@ mod prepare_block { pub fn filter_invalid_vote_extensions_residuals( &self, vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator> + '_ - { + ) -> impl Iterator< + Item = core::result::Result< + (VotingPower, SignedExt), + VoteExtensionError, + >, + > + '_ { vote_extensions.into_iter().map(|vote_extension| { self.validate_vote_ext_and_get_it_back( vote_extension, @@ -267,7 +271,7 @@ mod prepare_block { vote_extensions: impl IntoIterator + 'static, ) -> impl Iterator + '_ { self.filter_invalid_vote_extensions_residuals(vote_extensions) - .filter_map(|ext| ext) + .filter_map(|ext| ext.ok()) } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 3073e86d8b..1ec5ab0659 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -165,7 +165,7 @@ where ) .unwrap_or_default(); }) - .is_some() + .is_ok() }) { if voting_power > FractionalVotingPower::TWO_THIRDS { TxResult { @@ -399,55 +399,53 @@ mod test_process_proposal { /// we reject it. #[test] fn test_more_than_one_vext_digest_rejected() { - let (mut shell, _, _) = test_utils::setup(); + let (shell, _, _) = test_utils::setup(); let validator_addr = wallet::defaults::validator_address(); drop((shell, validator_addr)); - /* - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ); - let wrapper = WrapperTx::new( - Fee { - amount: 0.into(), - token: xan(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - ); - let tx = Tx::new( - vec![], - Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), - ) - .to_bytes(); - #[allow(clippy::redundant_clone)] - let request = ProcessProposal { - txs: vec![tx.clone()], - }; - - let response = if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") - }; - assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); - assert_eq!( - 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()) - } - */ + // let tx = Tx::new( + // "wasm_code".as_bytes().to_owned(), + // Some("transaction data".as_bytes().to_owned()), + // ); + // let wrapper = WrapperTx::new( + // Fee { + // amount: 0.into(), + // token: xan(), + // }, + // &keypair, + // Epoch(0), + // 0.into(), + // tx, + // Default::default(), + // ); + // let tx = Tx::new( + // vec![], + // Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), + // ) + // .to_bytes(); + // #[allow(clippy::redundant_clone)] + // let request = ProcessProposal { + // txs: vec![tx.clone()], + // }; + // + // let response = if let [resp] = shell + // .process_proposal(request) + // .expect("Test failed") + // .as_slice() + // { + // resp.clone() + // } else { + // panic!("Test failed") + // }; + // assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); + // assert_eq!( + // 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 if a wrapper tx is not signed, it is rejected diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e8dd7fb04b..c4dbf3d31d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -10,6 +10,20 @@ mod extend_votes { /// A [`VoteExtension`] signed by a Namada validator. pub type SignedExt = Signed; + /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. + pub enum VoteExtensionError { + /// The vote extension has an unexpected block height. + UnexpectedBlockHeight, + /// The vote extension contains duplicate or non-sorted + /// Ethereum events. + HaveDupesOrNonSorted, + /// The public key of the vote extension's associated validator + /// could not be found in storage. + PubKeyNotInStorage, + /// The vote extension's signature is invalid. + VerifySigFailed, + } + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -92,8 +106,7 @@ mod extend_votes { ext: SignedExt, height: BlockHeight, ) -> bool { - self.validate_vote_ext_and_get_it_back(ext, height) - .is_some() + self.validate_vote_ext_and_get_it_back(ext, height).is_ok() } /// This method behaves exactly like [`Self::validate_vote_extension`], @@ -103,14 +116,15 @@ mod extend_votes { &self, ext: SignedExt, height: BlockHeight, - ) -> Option<(VotingPower, SignedExt)> { + ) -> core::result::Result<(VotingPower, SignedExt), VoteExtensionError> + { if ext.data.block_height != height { let ext_height = ext.data.block_height; tracing::error!( "Vote extension issued for a block height {ext_height} \ different from the expected height {height}" ); - return None; + return Err(VoteExtensionError::UnexpectedBlockHeight); } // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order @@ -127,7 +141,7 @@ mod extend_votes { %validator, "Found duplicate or non-sorted Ethereum events in a vote extension from validator" ); - return None; + return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator let epoch = self.storage.block.pred_epochs.get_epoch(height); @@ -139,8 +153,8 @@ mod extend_votes { %validator, "Could not get public key from Storage for validator" ); - }) - .ok()?; + VoteExtensionError::PubKeyNotInStorage + })?; // verify the signature of the vote extension ext.verify(&pk) .map_err(|err| { @@ -149,8 +163,8 @@ mod extend_votes { %validator, "Failed to verify the signature of a vote extension issued by validator" ); + VoteExtensionError::VerifySigFailed }) - .ok() .map(|_| (voting_power, ext)) } From a3e3631d3987228ed7d05363aef8a3efdb6de675 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 13:22:11 +0100 Subject: [PATCH 0215/1995] Derive Error on VoteExtensionError --- .../lib/node/ledger/shell/vote_extensions.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c4dbf3d31d..077fb5ea39 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -11,16 +11,21 @@ mod extend_votes { pub type SignedExt = Signed; /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. + #[derive(Error, Debug)] pub enum VoteExtensionError { - /// The vote extension has an unexpected block height. + #[error("The vote extension has an unexpected block height.")] UnexpectedBlockHeight, - /// The vote extension contains duplicate or non-sorted - /// Ethereum events. + #[error( + "The vote extension contains duplicate or non-sorted Ethereum \ + events." + )] HaveDupesOrNonSorted, - /// The public key of the vote extension's associated validator - /// could not be found in storage. + #[error( + "The public key of the vote extension's associated validator \ + could not be found in storage." + )] PubKeyNotInStorage, - /// The vote extension's signature is invalid. + #[error("The vote extension's signature is invalid.")] VerifySigFailed, } From fbeda94afefb7901d0487d27ea15013f417582c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 13:46:18 +0100 Subject: [PATCH 0216/1995] Drop vote extensions issued at genesis in validate_vote_extension() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 +++++-------- apps/src/lib/node/ledger/shell/process_proposal.rs | 9 --------- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 ++++++ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a02f788bd5..7be3ed29ae 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -88,13 +88,10 @@ mod prepare_block { // handle genesis block (None, BlockHeight(0)) => return vec![], (Some(_), BlockHeight(0)) => { - tracing::error!( - "The genesis block should not contain vote \ - extensions" - ); - // TODO: maybe slash validators who claim to have - // seen vote extensions at H=0 - return vec![]; + unreachable!( + "We already handle this scenario in \ + validate_vote_extension." + ) } // handle block heights > 0 (Some(digest), _) => digest, @@ -652,7 +649,7 @@ mod prepare_block { /// Test if vote extension validation and inclusion in a block /// behaves as expected, considering <= 2/3 voting power. #[test] - #[should_panic(expected = "entered unreachable code")] + #[should_panic(expected = "Honest Namada validators")] fn test_prepare_proposal_vext_insufficient_voting_power() { const FIRST_HEIGHT: BlockHeight = BlockHeight(0); const LAST_HEIGHT: BlockHeight = BlockHeight(FIRST_HEIGHT.0 + 11); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1ec5ab0659..3636640840 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -133,15 +133,6 @@ where ProtocolTxType::EthereumEvents(digest) => { *vote_ext_digest_num += 1; - if self.storage.last_height.0 == 0 { - return TxResult { - code: ErrorCodes::InvalidVoteExntension.into(), - info: "No vote extensions should be included in \ - block height 0" - .into(), - }; - } - let extensions = digest.decompress(self.storage.last_height); let filtered_extensions = self diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 077fb5ea39..b54bf4d4c0 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -13,6 +13,8 @@ mod extend_votes { /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] pub enum VoteExtensionError { + #[error("The vote extension was issued at block height 0.")] + IssuedAtGenesis, #[error("The vote extension has an unexpected block height.")] UnexpectedBlockHeight, #[error( @@ -131,6 +133,10 @@ mod extend_votes { ); return Err(VoteExtensionError::UnexpectedBlockHeight); } + if height.0 == 0 { + tracing::error!("Dropping vote extension issued at genesis"); + return Err(VoteExtensionError::IssuedAtGenesis); + } // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order let have_dupes_or_non_sorted = { From bcad9a407e7bd71bbc7f0e7f1fe7db63c15b9b0c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 14:09:40 +0100 Subject: [PATCH 0217/1995] Small comment fix --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b54bf4d4c0..12c457863d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -180,7 +180,7 @@ mod extend_votes { } /// Checks the channel from the Ethereum oracle monitoring - /// the fullnode and retrieves all VoteExtensionmessages sent. + /// the fullnode and retrieves all VoteExtension messages sent. pub fn new_ethereum_events(&mut self) -> Vec { match &mut self.mode { ShellMode::Validator { From fb82cf2fcb28492187e136a0cac32b7d679c6972 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 14:32:49 +0100 Subject: [PATCH 0218/1995] Test if we drop proposals with more than one digest --- .../lib/node/ledger/shell/process_proposal.rs | 89 +++++++++---------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 3636640840..9cfe5a2d59 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -185,6 +185,9 @@ where ) } } else { + // TODO: maybe return a summary of the reasons for + // dropping a vote extension. we have access to the + // motives in `filtered_extensions` TxResult { code: ErrorCodes::InvalidVoteExntension.into(), info: "Process proposal rejected this proposal \ @@ -360,9 +363,14 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { + use std::collections::HashMap; + use borsh::BorshDeserialize; use namada::proto::SignedTxData; use namada::types::address::xan; + use namada::types::ethereum_events::vote_extensions::{ + VoteExtension, VoteExtensionDigest, + }; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; @@ -390,53 +398,40 @@ mod test_process_proposal { /// we reject it. #[test] fn test_more_than_one_vext_digest_rejected() { - let (shell, _, _) = test_utils::setup(); - let validator_addr = wallet::defaults::validator_address(); - drop((shell, validator_addr)); - // let tx = Tx::new( - // "wasm_code".as_bytes().to_owned(), - // Some("transaction data".as_bytes().to_owned()), - // ); - // let wrapper = WrapperTx::new( - // Fee { - // amount: 0.into(), - // token: xan(), - // }, - // &keypair, - // Epoch(0), - // 0.into(), - // tx, - // Default::default(), - // ); - // let tx = Tx::new( - // vec![], - // Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), - // ) - // .to_bytes(); - // #[allow(clippy::redundant_clone)] - // let request = ProcessProposal { - // txs: vec![tx.clone()], - // }; - // - // let response = if let [resp] = shell - // .process_proposal(request) - // .expect("Test failed") - // .as_slice() - // { - // resp.clone() - // } else { - // panic!("Test failed") - // }; - // assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); - // assert_eq!( - // 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()) - // } + const LAST_HEIGHT: BlockHeight = BlockHeight(2); + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = LAST_HEIGHT; + let (protocol_key, _) = wallet::defaults::validator_keys(); + let vote_extension_digest = { + let validator_addr = wallet::defaults::validator_address(); + let signed_vote_extension = { + let ext = + VoteExtension::empty(LAST_HEIGHT, validator_addr.clone()) + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + // vote extension digest with no observed events + VoteExtensionDigest { + signatures: { + let mut s = HashMap::new(); + s.insert(validator_addr, signed_vote_extension.sig); + s + }, + events: vec![], + } + }; + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + #[allow(clippy::redundant_clone)] + let request = ProcessProposal { + txs: vec![tx.clone(), tx], + }; + let results = shell.process_proposal(request); + assert!( + matches!(results, Err(TestError::RejectProposal(s)) if s.len() == 2) + ); } /// Test that if a wrapper tx is not signed, it is rejected From 9d744947f3de4f206e0e967fbea1b298f18e4e36 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 15:04:22 +0100 Subject: [PATCH 0219/1995] Test if we reject proposals with invalid validator signatures --- .../lib/node/ledger/shell/process_proposal.rs | 71 ++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9cfe5a2d59..ea8dd9b1b9 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -363,14 +363,15 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { - use std::collections::HashMap; + use std::collections::{HashMap, HashSet}; use borsh::BorshDeserialize; use namada::proto::SignedTxData; use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::{ - VoteExtension, VoteExtensionDigest, + MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, }; + use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; @@ -386,6 +387,7 @@ mod test_process_proposal { #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf::Timestamp; + use super::super::vote_extensions::SignedExt; use super::*; #[cfg(not(feature = "ABCI"))] use crate::node::ledger::shell::test_utils::TestError; @@ -434,6 +436,71 @@ mod test_process_proposal { ); } + /// Test that if a proposal contains vote extensions with + /// invalid validator signatures, we reject it. + #[test] + fn test_drop_vext_digest_with_invalid_sigs() { + const LAST_HEIGHT: BlockHeight = BlockHeight(2); + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = LAST_HEIGHT; + let (protocol_key, _) = wallet::defaults::validator_keys(); + let vote_extension_digest = { + let addr = wallet::defaults::validator_address(); + let ext = { + // create a fake signature + let sig = common::Signature::Ed25519(ed25519::Signature( + [0u8; 64].into(), + )); + + let data = VoteExtension { + validator_addr: addr.clone(), + block_height: LAST_HEIGHT, + ethereum_events: vec![], + }; + + SignedExt { sig, data } + }; + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + VoteExtensionDigest { + signatures: { + let mut s = HashMap::new(); + s.insert(addr.clone(), ext.sig); + s + }, + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = HashSet::new(); + s.insert(addr); + s + }, + }], + } + }; + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExntension) + ); + } + /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. #[test] From 2ff7bb4403519c8f80dd1c2d1b5845d371a82214 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 15:09:37 +0100 Subject: [PATCH 0220/1995] Fix test --- apps/src/lib/node/ledger/shell/process_proposal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ea8dd9b1b9..c0e63c03c0 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -446,6 +446,10 @@ mod test_process_proposal { let (protocol_key, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { let addr = wallet::defaults::validator_address(); + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; let ext = { // create a fake signature let sig = common::Signature::Ed25519(ed25519::Signature( @@ -455,15 +459,11 @@ mod test_process_proposal { let data = VoteExtension { validator_addr: addr.clone(), block_height: LAST_HEIGHT, - ethereum_events: vec![], + ethereum_events: vec![event.clone()], }; SignedExt { sig, data } }; - let event = EthereumEvent::TransfersToNamada { - nonce: 1u64.into(), - transfers: vec![], - }; VoteExtensionDigest { signatures: { let mut s = HashMap::new(); From b22fb839158eacc75804a71a73ff9e1efa7f4af4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 15:15:13 +0100 Subject: [PATCH 0221/1995] Test if we reject proposals with invalid block heights --- .../lib/node/ledger/shell/process_proposal.rs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c0e63c03c0..99337e62be 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -501,6 +501,68 @@ mod test_process_proposal { ); } + /// Test that if a proposal contains vote extensions with + /// invalid block heights, we reject it. + #[test] + fn test_drop_vext_digest_with_invalid_bheights() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = LAST_HEIGHT; + let (protocol_key, _) = wallet::defaults::validator_keys(); + let vote_extension_digest = { + let addr = wallet::defaults::validator_address(); + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ext = { + let ext = VoteExtension { + validator_addr: addr.clone(), + block_height: PRED_LAST_HEIGHT, + ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + VoteExtensionDigest { + signatures: { + let mut s = HashMap::new(); + s.insert(addr.clone(), ext.sig); + s + }, + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = HashSet::new(); + s.insert(addr); + s + }, + }], + } + }; + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExntension) + ); + } + /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. #[test] From 59387b18bdc847f0009704a4847556721c52ad51 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 15:19:03 +0100 Subject: [PATCH 0222/1995] Test if we reject proposals with invalid validators --- .../lib/node/ledger/shell/process_proposal.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 99337e62be..f689b8d595 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -563,6 +563,70 @@ mod test_process_proposal { ); } + /// Test that if a proposal contains vote extensions with + /// invalid validators, we reject it. + #[test] + fn test_drop_vext_digest_with_invalid_validators() { + const LAST_HEIGHT: BlockHeight = BlockHeight(2); + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = LAST_HEIGHT; + let (addr, protocol_key) = { + let bertha_key = wallet::defaults::bertha_keypair(); + let bertha_addr = wallet::defaults::bertha_address(); + (bertha_addr, bertha_key) + }; + let vote_extension_digest = { + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ext = { + let ext = VoteExtension { + validator_addr: addr.clone(), + block_height: LAST_HEIGHT, + ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + VoteExtensionDigest { + signatures: { + let mut s = HashMap::new(); + s.insert(addr.clone(), ext.sig); + s + }, + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = HashSet::new(); + s.insert(addr); + s + }, + }], + } + }; + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExntension) + ); + } + /// Test that if a wrapper tx is not signed, it is rejected /// by [`process_proposal`]. #[test] From da7063f7af1f057331fde9ed56d0490f1fb1d76e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:08:24 +0100 Subject: [PATCH 0223/1995] Factor out check_rejected_digest() --- .../lib/node/ledger/shell/process_proposal.rs | 86 +++++++------------ 1 file changed, 29 insertions(+), 57 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f689b8d595..62b4da4ecc 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -436,6 +436,32 @@ mod test_process_proposal { ); } + fn check_rejected_digest( + shell: &mut TestShell, + vote_extension_digest: VoteExtensionDigest, + protocol_key: common::SecretKey, + ) { + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExntension) + ); + } + /// Test that if a proposal contains vote extensions with /// invalid validator signatures, we reject it. #[test] @@ -480,25 +506,7 @@ mod test_process_proposal { }], } }; - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) - .to_bytes(); - let request = ProcessProposal { txs: vec![tx] }; - let response = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(request) - { - if let [resp] = resp.as_slice() { - resp.clone() - } else { - panic!("Test failed") - } - } else { - panic!("Test failed") - }; - assert_eq!( - response.result.code, - u32::from(ErrorCodes::InvalidVoteExntension) - ); + check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); } /// Test that if a proposal contains vote extensions with @@ -542,25 +550,7 @@ mod test_process_proposal { }], } }; - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) - .to_bytes(); - let request = ProcessProposal { txs: vec![tx] }; - let response = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(request) - { - if let [resp] = resp.as_slice() { - resp.clone() - } else { - panic!("Test failed") - } - } else { - panic!("Test failed") - }; - assert_eq!( - response.result.code, - u32::from(ErrorCodes::InvalidVoteExntension) - ); + check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); } /// Test that if a proposal contains vote extensions with @@ -606,25 +596,7 @@ mod test_process_proposal { }], } }; - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) - .to_bytes(); - let request = ProcessProposal { txs: vec![tx] }; - let response = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(request) - { - if let [resp] = resp.as_slice() { - resp.clone() - } else { - panic!("Test failed") - } - } else { - panic!("Test failed") - }; - assert_eq!( - response.result.code, - u32::from(ErrorCodes::InvalidVoteExntension) - ); + check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); } /// Test that if a wrapper tx is not signed, it is rejected From c968e8f97fc3aa8340af4fff05004b3e59520f77 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:23:47 +0100 Subject: [PATCH 0224/1995] Remove unreachable!() expression --- .../lib/node/ledger/shell/process_proposal.rs | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 62b4da4ecc..22844f8588 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -166,23 +166,14 @@ where .into(), } } else { - // ```ignore - // TxResult { - // code: ErrorCodes::InvalidVoteExntension. into(), - // info: " Process proposal rejected this \ - // proposal because the backing stake of \ - // the vote extensions published in the \ - // proposal was insufficient" - // .into(), - // } - // ``` - // - // TODO: is unreachable really fine? - unreachable!( - "We should never get an insufficient backing \ - stake on vote extensions published in \ - proposals." - ) + TxResult { + code: ErrorCodes::InvalidVoteExntension.into(), + info: " Process proposal rejected this \ + proposal because the backing stake of \ + the vote extensions published in the \ + proposal was insufficient" + .into(), + } } } else { // TODO: maybe return a summary of the reasons for From d26f52efbd17e000e558a69b25c6bc34136a7e54 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:25:25 +0100 Subject: [PATCH 0225/1995] Update apps/src/lib/node/ledger/shell/mod.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 68cdb03ee7..a9779c19ba 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -134,7 +134,7 @@ pub enum ErrorCodes { InvalidOrder = 4, ExtraTxs = 5, Undecryptable = 6, - InvalidVoteExntension = 7, + InvalidVoteExtension = 7, } impl From for u32 { From 11e331e4bc0104d4ffb910eae8ed55d85df338e2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:26:37 +0100 Subject: [PATCH 0226/1995] Fix typo --- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 22844f8588..cc9a33547b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -167,7 +167,7 @@ where } } else { TxResult { - code: ErrorCodes::InvalidVoteExntension.into(), + code: ErrorCodes::InvalidVoteExtension.into(), info: " Process proposal rejected this \ proposal because the backing stake of \ the vote extensions published in the \ @@ -180,7 +180,7 @@ where // dropping a vote extension. we have access to the // motives in `filtered_extensions` TxResult { - code: ErrorCodes::InvalidVoteExntension.into(), + code: ErrorCodes::InvalidVoteExtension.into(), info: "Process proposal rejected this proposal \ because at least one of the vote \ extensions included was invalid." @@ -449,7 +449,7 @@ mod test_process_proposal { }; assert_eq!( response.result.code, - u32::from(ErrorCodes::InvalidVoteExntension) + u32::from(ErrorCodes::InvalidVoteExtension) ); } From bec2f1a90cdd7256730becd189fbdb0f046806cc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:28:34 +0100 Subject: [PATCH 0227/1995] Rename height param to last_height --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 12c457863d..b713e4d88d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -111,9 +111,10 @@ mod extend_votes { pub fn validate_vote_extension( &self, ext: SignedExt, - height: BlockHeight, + last_height: BlockHeight, ) -> bool { - self.validate_vote_ext_and_get_it_back(ext, height).is_ok() + self.validate_vote_ext_and_get_it_back(ext, last_height) + .is_ok() } /// This method behaves exactly like [`Self::validate_vote_extension`], @@ -122,18 +123,18 @@ mod extend_votes { pub fn validate_vote_ext_and_get_it_back( &self, ext: SignedExt, - height: BlockHeight, + last_height: BlockHeight, ) -> core::result::Result<(VotingPower, SignedExt), VoteExtensionError> { - if ext.data.block_height != height { + if ext.data.block_height != last_height { let ext_height = ext.data.block_height; tracing::error!( "Vote extension issued for a block height {ext_height} \ - different from the expected height {height}" + different from the expected height {last_height}" ); return Err(VoteExtensionError::UnexpectedBlockHeight); } - if height.0 == 0 { + if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); return Err(VoteExtensionError::IssuedAtGenesis); } @@ -155,7 +156,7 @@ mod extend_votes { return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator - let epoch = self.storage.block.pred_epochs.get_epoch(height); + let epoch = self.storage.block.pred_epochs.get_epoch(last_height); let (voting_power, pk) = self .get_validator_from_address(validator, epoch) .map_err(|err| { From bde7f2b5b75ce9a0c91d3159c8332e7cd83b9b94 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:33:09 +0100 Subject: [PATCH 0228/1995] Update apps/src/lib/node/ledger/shell/process_proposal.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/process_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index cc9a33547b..9a56a3bb5e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -83,7 +83,7 @@ where /// 3: Wasm runtime error /// 4: Invalid order of decrypted txs /// 5. More decrypted txs than expected - /// 6. A transaciton could not be decrypted + /// 6. A transaction could not be decrypted /// 7. An error in the vote extensions included in the proposal /// /// INVARIANT: Any changes applied in this method must be reverted if the From 9c47f135698dd30aed4fa5b40ffe43b252bcf82a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:34:41 +0100 Subject: [PATCH 0229/1995] Update apps/src/lib/node/ledger/shell/process_proposal.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/process_proposal.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9a56a3bb5e..6ce03a5016 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -95,7 +95,12 @@ where vote_ext_digest_num: &mut usize, ) -> TxResult { let maybe_tx = Tx::try_from(tx_bytes).map_or_else( - |_| { + |err| { + tracing::debug!( + ?err, + "couldn't deserialize transaction received during \ + PrepareProposal" + ); Err(TxResult { code: ErrorCodes::InvalidTx.into(), info: "The submitted transaction was not deserializable" From a590b1ebe69982a37478c88b75ffb29055d138bb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:35:42 +0100 Subject: [PATCH 0230/1995] Capitalize error msg --- apps/src/lib/node/ledger/shell/process_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6ce03a5016..d96a702e36 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -98,7 +98,7 @@ where |err| { tracing::debug!( ?err, - "couldn't deserialize transaction received during \ + "Couldn't deserialize transaction received during \ PrepareProposal" ); Err(TxResult { From 2364f7a8d076c65ca20c9d36c38928e735d8c4b6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 27 Jul 2022 16:41:02 +0100 Subject: [PATCH 0231/1995] Remove leading space in error msg --- apps/src/lib/node/ledger/shell/process_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d96a702e36..6ad5949dc4 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -173,7 +173,7 @@ where } else { TxResult { code: ErrorCodes::InvalidVoteExtension.into(), - info: " Process proposal rejected this \ + info: "Process proposal rejected this \ proposal because the backing stake of \ the vote extensions published in the \ proposal was insufficient" From fc20d1501f2ee22446a13270dd055993f6ce2e3d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 27 Jul 2022 17:13:30 +0100 Subject: [PATCH 0232/1995] Log less while trying to connect to Geth --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 13977490f5..f653905c66 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -132,11 +132,15 @@ pub mod eth_fullnode { tracing::info!("Finished syncing"); break; } - if let Err(e) = client.eth_syncing().await { + if let Err(error) = client.eth_syncing().await { // This is very noisy and usually not interesting. // Still can be very useful - tracing::debug!("Error trying to connect to Geth: {:?}", e); + tracing::debug!( + ?error, + "Couldn't connect to Geth, will retry" + ); } + std::thread::sleep(std::time::Duration::from_secs(1)); } let (abort_sender, receiver) = channel(); From 150fcdac4474baf66c99c1086bc3ffef89a2b910 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 27 Jul 2022 17:23:01 +0100 Subject: [PATCH 0233/1995] Only do state update transaction if events were in vote extensions --- apps/src/lib/node/ledger/protocol/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 48dd43552a..73e9b5b710 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -14,6 +14,7 @@ use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; +use namada::types::ethereum_events::vote_extensions::VoteExtensionDigest; use namada::types::storage; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; @@ -157,9 +158,12 @@ where }) } TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::EthereumEvents(_), + tx: + ProtocolTxType::EthereumEvents(VoteExtensionDigest { + events, .. + }), .. - }) => { + }) if !events.is_empty() => { tracing::debug!("Ethereum events received"); let gas_used = block_gas_meter .finalize_transaction() From d70d4e36d3a1ee8e8c83e4d8c2fe6763e60b43b4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 27 Jul 2022 17:37:56 +0100 Subject: [PATCH 0234/1995] Don't log when receiving 0 Ethereum events --- apps/src/lib/node/ledger/shell/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a2f8c06f80..9b36a469ed 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -203,7 +203,9 @@ impl EthereumReceiver { new_events += 1; }; } - tracing::debug!(n = new_events, "received Ethereum events"); + if new_events > 0 { + tracing::info!(n = new_events, "received Ethereum events"); + } } /// Get a copy of the queue From b4fe98089d17096dd2591da44a7d9dbbf4fa165e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 29 Jul 2022 09:53:11 +0100 Subject: [PATCH 0235/1995] Use named constants for `Duration`s and tokio::time::sleep --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index f653905c66..b7e4c52e7e 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -63,6 +63,8 @@ pub async fn run( #[cfg(feature = "eth-fullnode")] /// Tools for running a geth fullnode process pub mod eth_fullnode { + use std::time::Duration; + use tokio::process::{Child, Command}; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{channel, Receiver, Sender}; @@ -125,8 +127,10 @@ pub mod eth_fullnode { // it takes a brief amount of time to open up the websocket on // geth's end - let client = Web3::new(url, std::time::Duration::from_secs(5)); + const CLIENT_TIMEOUT: Duration = Duration::from_secs(5); + let client = Web3::new(url, CLIENT_TIMEOUT); + const SLEEP_DUR: Duration = Duration::from_secs(1); loop { if let Ok(false) = client.eth_syncing().await { tracing::info!("Finished syncing"); @@ -140,7 +144,7 @@ pub mod eth_fullnode { "Couldn't connect to Geth, will retry" ); } - std::thread::sleep(std::time::Duration::from_secs(1)); + tokio::time::sleep(SLEEP_DUR).await; } let (abort_sender, receiver) = channel(); From d373a36c9f7bd146d99e0496b4c1055837dabc4f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 29 Jul 2022 09:53:20 +0100 Subject: [PATCH 0236/1995] Change log --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index b7e4c52e7e..6b6c0df9c4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -139,10 +139,7 @@ pub mod eth_fullnode { if let Err(error) = client.eth_syncing().await { // This is very noisy and usually not interesting. // Still can be very useful - tracing::debug!( - ?error, - "Couldn't connect to Geth, will retry" - ); + tracing::debug!(?error, "Couldn't check Geth sync status"); } tokio::time::sleep(SLEEP_DUR).await; } From dfaf562ea3a21582f6d611ccbc8cdd9ad8dc2091 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 11:40:48 +0100 Subject: [PATCH 0237/1995] Move code from prepare_proposal.rs to vote_extensions.rs --- .../lib/node/ledger/shell/prepare_proposal.rs | 57 +------------------ .../lib/node/ledger/shell/vote_extensions.rs | 51 +++++++++++++++++ 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7be3ed29ae..c0a7a3da20 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,18 +4,15 @@ mod prepare_block { use std::collections::{BTreeMap, HashMap, HashSet}; - use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; - use namada::proto::Signed; use namada::types::ethereum_events::vote_extensions::{ - FractionalVotingPower, MultiSignedEthEvent, VoteExtension, - VoteExtensionDigest, + FractionalVotingPower, MultiSignedEthEvent, VoteExtensionDigest, }; use namada::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use super::super::vote_extensions::{SignedExt, VoteExtensionError}; + use super::super::vote_extensions::deserialize_vote_extensions; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -237,56 +234,6 @@ mod prepare_block { Some(VoteExtensionDigest { events, signatures }) } - - /// Takes a list of signed vote extensions, - /// and filters out invalid instances, returning - /// all residual values (including invalid vote - /// extensions). - #[inline] - pub fn filter_invalid_vote_extensions_residuals( - &self, - vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator< - Item = core::result::Result< - (VotingPower, SignedExt), - VoteExtensionError, - >, - > + '_ { - vote_extensions.into_iter().map(|vote_extension| { - self.validate_vote_ext_and_get_it_back( - vote_extension, - self.storage.last_height, - ) - }) - } - - /// Takes a list of signed vote extensions, - /// and filters out invalid instances. - #[inline] - pub fn filter_invalid_vote_extensions( - &self, - vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator + '_ { - self.filter_invalid_vote_extensions_residuals(vote_extensions) - .filter_map(|ext| ext.ok()) - } - } - - /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// deserialized [`SignedExt`] instances. - fn deserialize_vote_extensions( - vote_extensions: Vec, - ) -> impl Iterator + 'static { - vote_extensions.into_iter().filter_map(|vote| { - Signed::::try_from_slice(&vote.vote_extension[..]) - .map_err(|err| { - tracing::error!( - ?err, - "Failed to deserialize signed vote extension", - ); - }) - .ok() - }) } /// Functions for creating the appropriate TxRecord given the diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b713e4d88d..e6058ab430 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -4,6 +4,7 @@ mod extend_votes { use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; use namada::types::ethereum_events::vote_extensions::VoteExtension; + use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; @@ -194,6 +195,56 @@ mod extend_votes { _ => vec![], } } + + /// Takes a list of signed vote extensions, + /// and filters out invalid instances, returning + /// all residual values (including invalid vote + /// extensions). + #[inline] + pub fn filter_invalid_vote_extensions_residuals( + &self, + vote_extensions: impl IntoIterator + 'static, + ) -> impl Iterator< + Item = core::result::Result< + (VotingPower, SignedExt), + VoteExtensionError, + >, + > + '_ { + vote_extensions.into_iter().map(|vote_extension| { + self.validate_vote_ext_and_get_it_back( + vote_extension, + self.storage.last_height, + ) + }) + } + + /// Takes a list of signed vote extensions, + /// and filters out invalid instances. + #[inline] + pub fn filter_invalid_vote_extensions( + &self, + vote_extensions: impl IntoIterator + 'static, + ) -> impl Iterator + '_ { + self.filter_invalid_vote_extensions_residuals(vote_extensions) + .filter_map(|ext| ext.ok()) + } + } + + /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the + /// deserialized [`SignedExt`] instances. + pub fn deserialize_vote_extensions( + vote_extensions: Vec, + ) -> impl Iterator + 'static { + vote_extensions.into_iter().filter_map(|vote| { + SignedExt::try_from_slice(&vote.vote_extension[..]) + .map_err(|err| { + tracing::error!( + ?err, + "Failed to deserialize signed vote extension", + ); + }) + .ok() + }) } #[cfg(test)] From 6180488c9e95c050983a76a3b75dd6235dc0ec96 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 12:56:08 +0100 Subject: [PATCH 0238/1995] Use assert_matches!() in place of assert!(matches!()) --- Cargo.lock | 1 + apps/Cargo.toml | 1 + apps/src/lib/node/ledger/shell/process_proposal.rs | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43f8c7bb31..d822a7abd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4261,6 +4261,7 @@ version = "0.7.0" dependencies = [ "ark-serialize", "ark-std", + "assert_matches", "async-std", "async-trait", "base64 0.13.0", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 57e8be4df8..d53bfe7622 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -152,6 +152,7 @@ websocket = "0.26.2" winapi = "0.3.9" [dev-dependencies] +assert_matches = "1.5.0" namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} cargo-watch = "7.5.0" bit-set = "0.5.2" diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6ad5949dc4..c9022c358c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -361,6 +361,7 @@ where mod test_process_proposal { use std::collections::{HashMap, HashSet}; + use assert_matches::assert_matches; use borsh::BorshDeserialize; use namada::proto::SignedTxData; use namada::types::address::xan; @@ -427,8 +428,8 @@ mod test_process_proposal { txs: vec![tx.clone(), tx], }; let results = shell.process_proposal(request); - assert!( - matches!(results, Err(TestError::RejectProposal(s)) if s.len() == 2) + assert_matches!( + results, Err(TestError::RejectProposal(s)) if s.len() == 2 ); } From 868a5930e78175660a8141d4a3442deeba460edb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 13:29:01 +0100 Subject: [PATCH 0239/1995] Invalidate a signature in the unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 28 +++++++++++++------ .../lib/node/ledger/shell/process_proposal.rs | 26 +++++++++++------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c0a7a3da20..8bb504bdf3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -355,21 +355,33 @@ mod prepare_block { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let validator_addr = wallet::defaults::validator_address(); - let signed_vote_extension = { - // create a fake signature - let sig = common::Signature::Ed25519(ed25519::Signature( - [0u8; 64].into(), - )); + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); - let data = VoteExtension { + // generate a valid signature + let mut ext = VoteExtension { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + + // modify this signature such that it becomes invalid + ext.sig = { + let mut sig_bytes = match ext.sig { + common::Signature::Ed25519(ed25519::Signature( + ref sig, + )) => sig.to_bytes(), + }; + sig_bytes[0] = sig_bytes[0].wrapping_add(1); + common::Signature::Ed25519(ed25519::Signature( + sig_bytes.into(), + )) }; - SignedExt { sig, data } + ext }; check_vote_extension_filtering(&mut shell, signed_vote_extension); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c9022c358c..5be49aa76a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -384,7 +384,6 @@ mod test_process_proposal { #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf::Timestamp; - use super::super::vote_extensions::SignedExt; use super::*; #[cfg(not(feature = "ABCI"))] use crate::node::ledger::shell::test_utils::TestError; @@ -474,18 +473,27 @@ mod test_process_proposal { transfers: vec![], }; let ext = { - // create a fake signature - let sig = common::Signature::Ed25519(ed25519::Signature( - [0u8; 64].into(), - )); - - let data = VoteExtension { + // generate a valid signature + let mut ext = VoteExtension { validator_addr: addr.clone(), block_height: LAST_HEIGHT, ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + // modify this signature such that it becomes invalid + ext.sig = { + let mut sig_bytes = match ext.sig { + common::Signature::Ed25519(ed25519::Signature( + ref sig, + )) => sig.to_bytes(), + }; + sig_bytes[0] = sig_bytes[0].wrapping_add(1); + common::Signature::Ed25519(ed25519::Signature( + sig_bytes.into(), + )) }; - - SignedExt { sig, data } + ext }; VoteExtensionDigest { signatures: { From 748f0a2ae6eb58800cda014b00cbe3f94bb9e185 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 14:18:25 +0100 Subject: [PATCH 0240/1995] Misc changes --- apps/src/lib/node/ledger/shell/mod.rs | 25 ++++++++++++++++++- .../lib/node/ledger/shell/prepare_proposal.rs | 15 ++--------- .../lib/node/ledger/shell/process_proposal.rs | 21 ++++++---------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a9779c19ba..db5e2d4b8e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -125,7 +125,7 @@ impl From for TxResult { /// The different error codes that the ledger may /// send back to a client indicating the status /// of their submitted tx -#[derive(Debug, Clone, FromPrimitive, ToPrimitive, PartialEq)] +#[derive(Debug, Copy, Clone, FromPrimitive, ToPrimitive, PartialEq)] pub enum ErrorCodes { Ok = 0, InvalidTx = 1, @@ -135,6 +135,16 @@ pub enum ErrorCodes { ExtraTxs = 5, Undecryptable = 6, InvalidVoteExtension = 7, + // NOTE: keep these values in sync with + // [`ErrorCodes::is_recoverable`] +} + +impl ErrorCodes { + /// Checks if the given [`ErrorCodes`] value is a protocol level error, + /// that can be recovered from at the finalize block stage. + pub const fn is_recoverable(self) -> bool { + (self as u32) <= 3 + } } impl From for u32 { @@ -776,6 +786,19 @@ mod test_utils { ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap() } + /// Invalidate a valid signature `sig`. + pub(super) fn invalidate_signature( + sig: common::Signature, + ) -> common::Signature { + let mut sig_bytes = match sig { + common::Signature::Ed25519(ed25519::Signature(ref sig)) => { + sig.to_bytes() + } + }; + sig_bytes[0] = sig_bytes[0].wrapping_add(1); + common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) + } + /// A wrapper around the shell that implements /// Drop so as to clean up the files that it /// generates. Also allows illegal state diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 8bb504bdf3..bfc7131846 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -283,7 +283,7 @@ mod prepare_block { use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::VoteExtension; use namada::types::ethereum_events::EthereumEvent; - use namada::types::key::{common, ed25519}; + use namada::types::key::common; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; @@ -369,18 +369,7 @@ mod prepare_block { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); // modify this signature such that it becomes invalid - ext.sig = { - let mut sig_bytes = match ext.sig { - common::Signature::Ed25519(ed25519::Signature( - ref sig, - )) => sig.to_bytes(), - }; - sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Ed25519(ed25519::Signature( - sig_bytes.into(), - )) - }; - + ext.sig = test_utils::invalidate_signature(ext.sig); ext }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5be49aa76a..d194e1099c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -55,8 +55,12 @@ where // the leader's proposal. We allow txs that do not // deserialize properly, that have invalid signatures // and that have invalid wasm code to reach FinalizeBlock. - // This is the reason behind that greater than three check. - let invalid_txs = tx_results.iter().any(|res| res.code > 3); + let invalid_txs = tx_results.iter().any(|res| { + let error = ErrorCodes::from_u32(res.code).expect( + "All error codes returned from process_single_tx are valid", + ); + !error.is_recoverable() + }); ResponseProcessProposal { status: if too_many_vext_digests || invalid_txs { @@ -481,18 +485,9 @@ mod test_process_proposal { } .sign(&protocol_key); assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + // modify this signature such that it becomes invalid - ext.sig = { - let mut sig_bytes = match ext.sig { - common::Signature::Ed25519(ed25519::Signature( - ref sig, - )) => sig.to_bytes(), - }; - sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Ed25519(ed25519::Signature( - sig_bytes.into(), - )) - }; + ext.sig = test_utils::invalidate_signature(ext.sig); ext }; VoteExtensionDigest { From 06adc95c62076ed3adec5b94b6e1a9c9c8f3272a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 16:34:23 +0100 Subject: [PATCH 0241/1995] Crash when we obtain an invalid voting power from storage --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 5 ++++- apps/src/lib/node/ledger/shell/process_proposal.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index bfc7131846..7e575322b2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -195,7 +195,10 @@ mod prepare_block { validator_voting_power, total_voting_power, ) - .unwrap_or_default(); + .expect( + "The voting power we obtain from storage should always be \ + valid", + ); // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d194e1099c..69bb683ae3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -163,7 +163,10 @@ where u64::from(power), total_power, ) - .unwrap_or_default(); + .expect( + "The voting power we obtain from storage \ + should always be valid", + ); }) .is_ok() }) { From 056f14d65ac68aee7bc5411055c0772feab334f4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 16:35:21 +0100 Subject: [PATCH 0242/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e6058ab430..dfb3d61793 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -125,7 +125,7 @@ mod extend_votes { &self, ext: SignedExt, last_height: BlockHeight, - ) -> core::result::Result<(VotingPower, SignedExt), VoteExtensionError> + ) -> std::result::Result<(VotingPower, SignedExt), VoteExtensionError> { if ext.data.block_height != last_height { let ext_height = ext.data.block_height; From 861ef81de474cf1b0f24697534def1448a4f8ea5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 16:37:58 +0100 Subject: [PATCH 0243/1995] Update apps/src/lib/node/ledger/shell/vote_extensions.rs Co-authored-by: James --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index dfb3d61793..57b5a1a79f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -231,7 +231,7 @@ mod extend_votes { } /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// deserialized [`SignedExt`] instances. + /// ones we could deserialize to [`SignedExt`] instances. pub fn deserialize_vote_extensions( vote_extensions: Vec, ) -> impl Iterator + 'static { From 3112352753a8dcfe45d2f3bfc9d126a7819f23c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 29 Jul 2022 16:50:03 +0100 Subject: [PATCH 0244/1995] Rename method that validates a set of vote extensions From `filter_invalid_vote_extensions_residuals` to `validate_vote_extension_list`. --- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- apps/src/lib/node/ledger/shell/vote_extensions.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 69bb683ae3..bf1748e749 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -144,8 +144,8 @@ where let extensions = digest.decompress(self.storage.last_height); - let filtered_extensions = self - .filter_invalid_vote_extensions_residuals(extensions); + let valid_extensions = + self.validate_vote_extension_list(extensions); let mut voting_power = FractionalVotingPower::default(); let total_power = { @@ -156,7 +156,7 @@ where u64::from(self.get_total_voting_power(epoch)) }; - if filtered_extensions.into_iter().all(|maybe_ext| { + if valid_extensions.into_iter().all(|maybe_ext| { maybe_ext .map(|(power, _)| { voting_power += FractionalVotingPower::new( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 57b5a1a79f..6fb329ac77 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -196,16 +196,16 @@ mod extend_votes { } } - /// Takes a list of signed vote extensions, - /// and filters out invalid instances, returning - /// all residual values (including invalid vote - /// extensions). + /// Takes an iterator over signed vote extensions, + /// and returns another iterator. The latter yields + /// valid vote extensions, or the reason why these + /// are invalid, in the form of a [`VoteExtensionError`]. #[inline] - pub fn filter_invalid_vote_extensions_residuals( + pub fn validate_vote_extension_list( &self, vote_extensions: impl IntoIterator + 'static, ) -> impl Iterator< - Item = core::result::Result< + Item = std::result::Result< (VotingPower, SignedExt), VoteExtensionError, >, @@ -225,7 +225,7 @@ mod extend_votes { &self, vote_extensions: impl IntoIterator + 'static, ) -> impl Iterator + '_ { - self.filter_invalid_vote_extensions_residuals(vote_extensions) + self.validate_vote_extension_list(vote_extensions) .filter_map(|ext| ext.ok()) } } From 8da25cdca80bd4e1306c2f9b47e44f94ed90b051 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 29 Jul 2022 18:00:53 +0100 Subject: [PATCH 0245/1995] queries.rs: add get_active_validators helper --- apps/src/lib/node/ledger/shell/queries.rs | 51 +++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 864da28ab6..8c8c957d23 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -6,6 +6,7 @@ use ferveo_common::TendermintValidator; use namada::ledger::parameters::EpochDuration; #[cfg(not(feature = "ABCI"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; +use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; use namada::types::address::Address; use namada::types::key; @@ -376,12 +377,7 @@ where epoch: Option, ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); - // get the active validator set - self.storage - .read_validator_set() - .get(epoch) - .expect("Validators for an epoch should be known") - .active + get_active_validators(&self.storage, Some(epoch)) .iter() .find(|validator| address == &validator.address) .map(|validator| { @@ -405,21 +401,11 @@ where /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { - // get the current epoch - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); - // get the active validator set - self.storage - .read_validator_set() - .get(epoch) - .map(|validators| { - validators - .active - .iter() - .map(|validator| u64::from(validator.voting_power)) - .sum::() - .into() - }) - .unwrap_or_default() + get_active_validators(&self.storage, epoch) + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() } /// Given a tendermint validator, the address is the hash @@ -433,7 +419,6 @@ where tm_address: &[u8], epoch: Option, ) -> std::result::Result { - // get the current epoch let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); let validator_raw_hash = core::str::from_utf8(tm_address) .map_err(|_| Error::InvalidTMAddress)?; @@ -447,3 +432,25 @@ where }) } } + +// Some helper functions are not methods on the [`Shell`], so that we can use +// them in other places such as `apply_tx`. + +/// Get the set of active validators for a given epoch (defaulting to the +/// epoch of the current yet-to-be-committed block). +pub fn get_active_validators( + storage: &Storage, + epoch: Option, +) -> BTreeSet> +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let epoch = epoch.unwrap_or_else(|| storage.get_current_epoch().0); + let validator_set = storage.read_validator_set(); + validator_set + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .clone() +} From b6bb7cfb8cd1dffba476dd060c4ee90601b4de7e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 29 Jul 2022 18:17:49 +0100 Subject: [PATCH 0246/1995] queries.rs: make get_total_voting_power a helper --- .../lib/node/ledger/shell/prepare_proposal.rs | 7 +++-- .../lib/node/ledger/shell/process_proposal.rs | 3 ++- apps/src/lib/node/ledger/shell/queries.rs | 27 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7e575322b2..e738f09917 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,6 +14,7 @@ mod prepare_block { use super::super::vote_extensions::deserialize_vote_extensions; use super::super::*; + use crate::node::ledger::shell::queries::get_total_voting_power; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -178,8 +179,10 @@ mod prepare_block { let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); - let total_voting_power = - u64::from(self.get_total_voting_power(Some(events_epoch))); + let total_voting_power = u64::from(get_total_voting_power( + &self.storage, + Some(events_epoch), + )); let mut voting_power = FractionalVotingPower::default(); let deserialized = deserialize_vote_extensions(vote_extensions); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index bf1748e749..c73179bfdd 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -11,6 +11,7 @@ use tendermint_proto::abci::{ #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestDeliverTx; +use super::queries::get_total_voting_power; use super::*; impl Shell @@ -153,7 +154,7 @@ where self.storage.block.pred_epochs.get_epoch( BlockHeight(self.storage.last_height.0), ); - u64::from(self.get_total_voting_power(epoch)) + u64::from(get_total_voting_power(&self.storage, epoch)) }; if valid_extensions.into_iter().all(|maybe_ext| { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 8c8c957d23..e43684e87f 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -398,16 +398,6 @@ where .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } - /// Lookup the total voting power for an epoch - #[cfg(not(feature = "ABCI"))] - pub fn get_total_voting_power(&self, epoch: Option) -> VotingPower { - get_active_validators(&self.storage, epoch) - .iter() - .map(|validator| u64::from(validator.voting_power)) - .sum::() - .into() - } - /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native /// address from storage using this hash. @@ -454,3 +444,20 @@ where .active .clone() } + +/// Lookup the total voting power for an epoch +#[cfg(not(feature = "ABCI"))] +pub fn get_total_voting_power( + storage: &Storage, + epoch: Option, +) -> VotingPower +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + get_active_validators(storage, epoch) + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() +} From 0e040ac9b2005b94d5dbae3cf4c7881f47a9f21b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 29 Jul 2022 11:51:57 +0100 Subject: [PATCH 0247/1995] Standardize on a way to represent EthAddress as a string --- shared/src/types/ethereum_events.rs | 75 ++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index c7e1930b5c..1b5fcf7657 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -2,8 +2,11 @@ pub mod vote_extensions; +use std::str::FromStr; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; +use eyre::{eyre, Context}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -41,7 +44,8 @@ impl From for Uint { } } -/// Representation of address on Ethereum +/// Representation of address on Ethereum. The inner value is the last 20 bytes +/// of the public key that controls the account. #[derive( Clone, Debug, @@ -55,6 +59,27 @@ impl From for Uint { )] pub struct EthAddress(pub [u8; 20]); +impl EthAddress { + /// The canonical way we represent an [`EthAddress`] in storage keys. A + /// 40-character lower case hexadecimal address prefixed by '0x'. + /// e.g. "0x6b175474e89094c44da98b954eedeac495271d0f" + pub fn to_canonical(&self) -> String { + format!("{:?}", ethabi::ethereum_types::Address::from(&self.0)) + } +} + +impl FromStr for EthAddress { + type Err = eyre::Error; + + /// Parses an [`EthAddress`] from a standard hex-encoded Ethereum address + /// string. e.g. "0x6B175474E89094C44Da98b954EedeAC495271d0F" + fn from_str(s: &str) -> Result { + let h160 = ethabi::ethereum_types::Address::from_str(s) + .wrap_err_with(|| eyre!("couldn't parse Ethereum address {}", s))?; + Ok(Self(h160.into())) + } +} + /// A Keccak hash #[derive( Clone, @@ -219,3 +244,51 @@ pub struct TokenWhitelist { /// Maximum amount of token allowed on the bridge pub cap: Amount, } + +#[cfg(test)] +pub mod tests { + use std::str::FromStr; + + use super::*; + + #[test] + fn test_eth_address_to_canonical() { + let canonical = testing::DAI_ERC20_ETH_ADDRESS.to_canonical(); + + assert_eq!( + testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + canonical, + ); + } + + #[test] + fn test_eth_address_from_str() { + let addr = + EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED) + .unwrap(); + + assert_eq!(testing::DAI_ERC20_ETH_ADDRESS, addr); + } + + #[test] + fn test_eth_address_from_str_error() { + let result = EthAddress::from_str( + "arbitrary string which isn't an Ethereum address", + ); + + assert!(result.is_err()); + } +} + +#[allow(missing_docs)] +#[cfg(any(test, feature = "testing"))] +pub mod testing { + use super::*; + + pub const DAI_ERC20_ETH_ADDRESS_CHECKSUMMED: &str = + "0x6B175474E89094C44Da98b954EedeAC495271d0F"; + pub const DAI_ERC20_ETH_ADDRESS: EthAddress = EthAddress([ + 107, 23, 84, 116, 232, 144, 148, 196, 77, 169, 139, 149, 78, 237, 234, + 196, 149, 39, 29, 15, + ]); +} From 56ff53dcdf0560b40b73fe962a974a32d7a671c3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 1 Aug 2022 10:50:03 +0100 Subject: [PATCH 0248/1995] impl QueriesExt for Storage --- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/init_chain.rs | 3 +- .../lib/node/ledger/shell/prepare_proposal.rs | 9 +- .../lib/node/ledger/shell/process_proposal.rs | 5 +- apps/src/lib/node/ledger/shell/queries.rs | 373 +++++++++++------- .../lib/node/ledger/shell/vote_extensions.rs | 5 + 6 files changed, 248 insertions(+), 153 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 35d642c3d4..05491c4fab 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -20,6 +20,7 @@ use tendermint_proto_abci::abci::Evidence; #[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; +use super::queries::QueriesExt; use super::*; use crate::node::ledger::events::EventType; @@ -496,8 +497,9 @@ where 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); + let evidence_params = self + .storage + .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 347c3bff53..4e5ec0940f 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -18,6 +18,7 @@ use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; #[cfg(feature = "ABCI")] use tendermint_proto_abci::google::protobuf; +use super::queries::QueriesExt; use super::*; use crate::wasm_loader; @@ -266,7 +267,7 @@ where ); ibc::init_genesis_storage(&mut self.storage); - let evidence_params = self.get_evidence_params( + let evidence_params = self.storage.get_evidence_params( &genesis.parameters.epoch_duration, &genesis.pos_params, ); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e738f09917..49abfbf10d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,7 +14,7 @@ mod prepare_block { use super::super::vote_extensions::deserialize_vote_extensions; use super::super::*; - use crate::node::ledger::shell::queries::get_total_voting_power; + use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -179,10 +179,9 @@ mod prepare_block { let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); - let total_voting_power = u64::from(get_total_voting_power( - &self.storage, - Some(events_epoch), - )); + let total_voting_power = u64::from( + self.storage.get_total_voting_power(Some(events_epoch)), + ); let mut voting_power = FractionalVotingPower::default(); let deserialized = deserialize_vote_extensions(vote_extensions); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c73179bfdd..9f971b831e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -11,7 +11,7 @@ use tendermint_proto::abci::{ #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::RequestDeliverTx; -use super::queries::get_total_voting_power; +use super::queries::QueriesExt; use super::*; impl Shell @@ -154,7 +154,7 @@ where self.storage.block.pred_epochs.get_epoch( BlockHeight(self.storage.last_height.0), ); - u64::from(get_total_voting_power(&self.storage, epoch)) + u64::from(self.storage.get_total_voting_power(epoch)) }; if valid_extensions.into_iter().all(|maybe_ext| { @@ -249,6 +249,7 @@ where } else { // check that the fee payer has sufficient balance let balance = self + .storage .get_balance(&wrapper.fee.token, &wrapper.fee_payer()) .unwrap_or_default(); diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e43684e87f..64bc05d921 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -103,89 +103,13 @@ where } } - /// Simple helper function for the ledger to get balances - /// of the specified token at the specified address - pub fn get_balance( - &self, - token: &Address, - owner: &Address, - ) -> std::result::Result { - let height = self.storage.get_block_height().0; - let query_resp = self.read_storage_value( - &token::balance_key(token, owner), - height, - false, - ); - if query_resp.code != 0 { - Err(format!( - "Unable to read token {} balance of the given address {}", - token, owner - )) - } else { - BorshDeserialize::try_from_slice(&query_resp.value[..]).map_err( - |_| { - "Unable to deserialize the balance of the given address" - .into() - }, - ) - } - } - - /// Query to read a value from storage - pub fn read_storage_value( - &self, - key: &Key, - height: BlockHeight, - is_proven: bool, - ) -> response::Query { - match self.storage.read_with_height(key, height) { - Ok((Some(value), _gas)) => { - let proof_ops = if is_proven { - match self.storage.get_existence_proof( - key, - value.clone(), - height, - ) { - Ok(proof) => Some(proof.into()), - Err(err) => { - return response::Query { - code: 2, - info: format!("Storage error: {}", err), - ..Default::default() - }; - } - } - } else { - None - }; - response::Query { - value, - proof_ops, - ..Default::default() - } - } - Ok((None, _gas)) => { - let proof_ops = if is_proven { - match self.storage.get_non_existence_proof(key, height) { - Ok(proof) => Some(proof.into()), - Err(err) => { - return response::Query { - code: 2, - info: format!("Storage error: {}", err), - ..Default::default() - }; - } - } - } else { - None - }; - response::Query { - code: 1, - info: format!("No value found for key: {}", key), - proof_ops, - ..Default::default() - } - } + /// Query to check if a storage key exists. + fn has_storage_key(&self, key: &Key) -> response::Query { + match self.storage.has_key(key) { + Ok((has_key, _gas)) => response::Query { + value: has_key.try_to_vec().unwrap(), + ..Default::default() + }, Err(err) => response::Query { code: 2, info: format!("Storage error: {}", err), @@ -197,7 +121,7 @@ where /// Query to read a range of values from storage with a matching prefix. The /// value in successful response is a [`Vec`] encoded with /// [`BorshSerialize`]. - pub fn read_storage_prefix( + fn read_storage_prefix( &self, key: &Key, height: BlockHeight, @@ -282,13 +206,219 @@ where } } - /// Query to check if a storage key exists. - fn has_storage_key(&self, key: &Key) -> response::Query { - match self.storage.has_key(key) { - Ok((has_key, _gas)) => response::Query { - value: has_key.try_to_vec().unwrap(), + /// Query to read a value from storage + fn read_storage_value( + &self, + key: &Key, + height: BlockHeight, + is_proven: bool, + ) -> response::Query { + match self.storage.read_with_height(key, height) { + Ok((Some(value), _gas)) => { + let proof_ops = if is_proven { + match self.storage.get_existence_proof( + key, + value.clone(), + height, + ) { + Ok(proof) => Some(proof.into()), + Err(err) => { + return response::Query { + code: 2, + info: format!("Storage error: {}", err), + ..Default::default() + }; + } + } + } else { + None + }; + response::Query { + value, + proof_ops, + ..Default::default() + } + } + Ok((None, _gas)) => { + let proof_ops = if is_proven { + match self.storage.get_non_existence_proof(key, height) { + Ok(proof) => Some(proof.into()), + Err(err) => { + return response::Query { + code: 2, + info: format!("Storage error: {}", err), + ..Default::default() + }; + } + } + } else { + None + }; + response::Query { + code: 1, + info: format!("No value found for key: {}", key), + proof_ops, + ..Default::default() + } + } + Err(err) => response::Query { + code: 2, + info: format!("Storage error: {}", err), ..Default::default() }, + } + } +} + +/// API for querying the blockchain state. +pub(crate) trait QueriesExt { + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet>; + fn get_total_voting_power(&self, epoch: Option) -> VotingPower; + fn get_balance( + &self, + token: &Address, + owner: &Address, + ) -> std::result::Result; + fn read_storage_value( + &self, + key: &Key, + height: BlockHeight, + is_proven: bool, + ) -> response::Query; + fn get_evidence_params( + &self, + epoch_duration: &EpochDuration, + pos_params: &PosParams, + ) -> EvidenceParams; + fn get_validator_from_protocol_pk( + &self, + pk: &key::common::PublicKey, + epoch: Option, + ) -> std::result::Result, Error>; + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> std::result::Result<(VotingPower, common::PublicKey), Error>; + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> std::result::Result; +} + +impl QueriesExt for Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Get the set of active validators for a given epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet> { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_set = self.read_validator_set(); + validator_set + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .clone() + } + + /// Lookup the total voting power for an epoch + #[cfg(not(feature = "ABCI"))] + fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + self.get_active_validators(epoch) + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + } + + /// Simple helper function for the ledger to get balances + /// of the specified token at the specified address + fn get_balance( + &self, + token: &Address, + owner: &Address, + ) -> std::result::Result { + let height = self.get_block_height().0; + let query_resp = self.read_storage_value( + &token::balance_key(token, owner), + height, + false, + ); + if query_resp.code != 0 { + Err(format!( + "Unable to read token {} balance of the given address {}", + token, owner + )) + } else { + BorshDeserialize::try_from_slice(&query_resp.value[..]).map_err( + |_| { + "Unable to deserialize the balance of the given address" + .into() + }, + ) + } + } + + /// Query to read a value from storage + fn read_storage_value( + &self, + key: &Key, + height: BlockHeight, + is_proven: bool, + ) -> response::Query { + match self.read_with_height(key, height) { + Ok((Some(value), _gas)) => { + let proof_ops = if is_proven { + match self.get_existence_proof(key, value.clone(), height) { + Ok(proof) => Some(proof.into()), + Err(err) => { + return response::Query { + code: 2, + info: format!("Storage error: {}", err), + ..Default::default() + }; + } + } + } else { + None + }; + response::Query { + value, + proof_ops, + ..Default::default() + } + } + Ok((None, _gas)) => { + let proof_ops = if is_proven { + match self.get_non_existence_proof(key, height) { + Ok(proof) => Some(proof.into()), + Err(err) => { + return response::Query { + code: 2, + info: format!("Storage error: {}", err), + ..Default::default() + }; + } + } + } else { + None + }; + response::Query { + code: 1, + info: format!("No value found for key: {}", key), + proof_ops, + ..Default::default() + } + } Err(err) => response::Query { code: 2, info: format!("Storage error: {}", err), @@ -297,7 +427,7 @@ where } } - pub fn get_evidence_params( + fn get_evidence_params( &self, epoch_duration: &EpochDuration, pos_params: &PosParams, @@ -321,7 +451,7 @@ where /// Lookup data about a validator from their protocol signing key #[allow(dead_code)] - pub fn get_validator_from_protocol_pk( + fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, epoch: Option, @@ -329,17 +459,16 @@ where let pk_bytes = pk .try_to_vec() .expect("Serializing public key should not fail"); - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); // get the active validator set - self.storage - .read_validator_set() + self.read_validator_set() .get(epoch) .expect("Validators for an epoch should be known") .active .iter() .find(|validator| { let pk_key = key::protocol_pk_key(&validator.address); - match self.storage.read(&pk_key) { + match self.read(&pk_key) { Ok((Some(bytes), _)) => bytes == pk_bytes, _ => false, } @@ -348,7 +477,6 @@ where let dkg_key = key::dkg_session_keys::dkg_pk_key(&validator.address); let bytes = self - .storage .read(&dkg_key) .expect("Validator should have public dkg key") .0 @@ -371,19 +499,18 @@ where /// Lookup data about a validator from their address #[cfg(not(feature = "ABCI"))] - pub fn get_validator_from_address( + fn get_validator_from_address( &self, address: &Address, epoch: Option, ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); - get_active_validators(&self.storage, Some(epoch)) + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) .iter() .find(|validator| address == &validator.address) .map(|validator| { let protocol_pk_key = key::protocol_pk_key(&validator.address); let bytes = self - .storage .read(&protocol_pk_key) .expect("Validator should have public protocol key") .0 @@ -404,16 +531,15 @@ where // TODO: We may change how this lookup is done, see // https://github.com/anoma/namada/issues/200 #[allow(dead_code)] - pub fn get_validator_from_tm_address( + fn get_validator_from_tm_address( &self, tm_address: &[u8], epoch: Option, ) -> std::result::Result { - let epoch = epoch.unwrap_or_else(|| self.storage.get_current_epoch().0); + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); let validator_raw_hash = core::str::from_utf8(tm_address) .map_err(|_| Error::InvalidTMAddress)?; - self.storage - .read_validator_address_raw_hash(&validator_raw_hash) + self.read_validator_address_raw_hash(&validator_raw_hash) .ok_or_else(|| { Error::NotValidatorKeyHash( validator_raw_hash.to_string(), @@ -422,42 +548,3 @@ where }) } } - -// Some helper functions are not methods on the [`Shell`], so that we can use -// them in other places such as `apply_tx`. - -/// Get the set of active validators for a given epoch (defaulting to the -/// epoch of the current yet-to-be-committed block). -pub fn get_active_validators( - storage: &Storage, - epoch: Option, -) -> BTreeSet> -where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - let epoch = epoch.unwrap_or_else(|| storage.get_current_epoch().0); - let validator_set = storage.read_validator_set(); - validator_set - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .clone() -} - -/// Lookup the total voting power for an epoch -#[cfg(not(feature = "ABCI"))] -pub fn get_total_voting_power( - storage: &Storage, - epoch: Option, -) -> VotingPower -where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - get_active_validators(storage, epoch) - .iter() - .map(|validator| u64::from(validator.voting_power)) - .sum::() - .into() -} diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 6fb329ac77..5ebb4b3d15 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -7,6 +7,7 @@ mod extend_votes { use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; + use crate::node::ledger::shell::queries::QueriesExt; /// A [`VoteExtension`] signed by a Namada validator. pub type SignedExt = Signed; @@ -159,6 +160,7 @@ mod extend_votes { // get the public key associated with this validator let epoch = self.storage.block.pred_epochs.get_epoch(last_height); let (voting_power, pk) = self + .storage .get_validator_from_address(validator, epoch) .map_err(|err| { tracing::error!( @@ -264,6 +266,7 @@ mod extend_votes { use tower_abci::request; use super::SignedExt; + use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; @@ -462,6 +465,7 @@ mod extend_votes { assert_eq!(shell.storage.get_current_epoch().0.0, 1); assert!( shell + .storage .get_validator_from_protocol_pk(&signing_key.ref_to(), None) .is_err() ); @@ -469,6 +473,7 @@ mod extend_votes { assert!( shell .shell + .storage .get_validator_from_protocol_pk( &signing_key.ref_to(), Some(prev_epoch) From 4f30ca3b35dfb4b031533272d56935b12fa9e09c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 1 Aug 2022 11:36:18 +0100 Subject: [PATCH 0249/1995] Refactor get_balance to not use read_storage_value --- apps/src/lib/node/ledger/shell/queries.rs | 105 +++++----------------- 1 file changed, 23 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 64bc05d921..0a04cde9d0 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -282,12 +282,6 @@ pub(crate) trait QueriesExt { token: &Address, owner: &Address, ) -> std::result::Result; - fn read_storage_value( - &self, - key: &Key, - height: BlockHeight, - is_proven: bool, - ) -> response::Query; fn get_evidence_params( &self, epoch_duration: &EpochDuration, @@ -348,83 +342,30 @@ where owner: &Address, ) -> std::result::Result { let height = self.get_block_height().0; - let query_resp = self.read_storage_value( - &token::balance_key(token, owner), - height, - false, - ); - if query_resp.code != 0 { - Err(format!( - "Unable to read token {} balance of the given address {}", - token, owner - )) - } else { - BorshDeserialize::try_from_slice(&query_resp.value[..]).map_err( - |_| { - "Unable to deserialize the balance of the given address" - .into() - }, - ) - } - } - - /// Query to read a value from storage - fn read_storage_value( - &self, - key: &Key, - height: BlockHeight, - is_proven: bool, - ) -> response::Query { - match self.read_with_height(key, height) { - Ok((Some(value), _gas)) => { - let proof_ops = if is_proven { - match self.get_existence_proof(key, value.clone(), height) { - Ok(proof) => Some(proof.into()), - Err(err) => { - return response::Query { - code: 2, - info: format!("Storage error: {}", err), - ..Default::default() - }; - } - } - } else { - None - }; - response::Query { - value, - proof_ops, - ..Default::default() - } - } - Ok((None, _gas)) => { - let proof_ops = if is_proven { - match self.get_non_existence_proof(key, height) { - Ok(proof) => Some(proof.into()), - Err(err) => { - return response::Query { - code: 2, - info: format!("Storage error: {}", err), - ..Default::default() - }; - } - } - } else { - None - }; - response::Query { - code: 1, - info: format!("No value found for key: {}", key), - proof_ops, - ..Default::default() - } + let (balance, _) = self + .read_with_height(&token::balance_key(token, owner), height) + .map_err(|err| { + format!( + "Unable to read token {} balance of the given address {}: \ + {:?}", + token, owner, err + ) + })?; + let balance = match balance { + Some(balance) => balance, + None => { + return Err(format!( + "Unable to read token {} balance of the given address {}", + token, owner + )); } - Err(err) => response::Query { - code: 2, - info: format!("Storage error: {}", err), - ..Default::default() - }, - } + }; + BorshDeserialize::try_from_slice(&balance[..]).map_err(|err| { + format!( + "Unable to deserialize the balance of the given address: {:?}", + err + ) + }) } fn get_evidence_params( From 27e6b90e1401023fd0cee8c726569d6a6111296b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 1 Aug 2022 11:43:39 +0100 Subject: [PATCH 0250/1995] Refactor imports to be consistent --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 49abfbf10d..1075897672 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,9 +12,9 @@ mod prepare_block { ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; + use super::super::queries::QueriesExt; use super::super::vote_extensions::deserialize_vote_extensions; use super::super::*; - use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5ebb4b3d15..4256e12633 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -6,8 +6,8 @@ mod extend_votes { use namada::types::ethereum_events::vote_extensions::VoteExtension; use tendermint_proto::abci::ExtendedVoteInfo; + use super::super::queries::QueriesExt; use super::super::*; - use crate::node::ledger::shell::queries::QueriesExt; /// A [`VoteExtension`] signed by a Namada validator. pub type SignedExt = Signed; From 418188e1bfb42063f024a2f2beb620fcbedc038f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 1 Aug 2022 14:13:44 +0100 Subject: [PATCH 0251/1995] WIP: Rename VoteExtension to EthEventsVext --- .../types/ethereum_events/vote_extensions.rs | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index f8cc801f14..9e26dd4280 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -14,11 +14,15 @@ use crate::types::address::Address; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; +/// Represents a set of `EthereumEvent` instances +/// seen by some validator. +/// /// This struct will be created and signed over by each -/// validator as their vote extension. +/// active validator, to be included as a vote extension at the end of a +/// Tendermint PreCommit phase. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct VoteExtension { - /// The block height for which this [`VoteExtension`] was made. +pub struct EthEventsVext { + /// The block height for which this [`EthEventsVext`] was made. pub block_height: BlockHeight, /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator @@ -29,8 +33,8 @@ pub struct VoteExtension { pub ethereum_events: Vec, } -impl VoteExtension { - /// Creates a [`VoteExtension`] without any Ethereum events. +impl EthEventsVext { + /// Creates a [`EthEventsVext`] without any Ethereum events. pub fn empty(block_height: BlockHeight, validator_addr: Address) -> Self { Self { block_height, @@ -39,7 +43,8 @@ impl VoteExtension { } } - /// Sign a vote extension and return the data with signature + /// Sign a [`EthEventsVext`] with a validator's `signing_key`, + /// and return the signed data. pub fn sign(self, signing_key: &common::SecretKey) -> Signed { Signed::new(signing_key, self) } @@ -147,30 +152,30 @@ pub struct MultiSignedEthEvent { pub signers: HashSet
, } -/// Compresses a set of signed `VoteExtension` instances, to save +/// Compresses a set of signed [`EthEventsVext`] instances, to save /// space on a block. #[derive( Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct VoteExtensionDigest { - /// The signatures and signing address of each VoteExtension + /// The signatures and signing address of each [`EthEventsVext`] pub signatures: HashMap, /// The events that were reported pub events: Vec, } impl VoteExtensionDigest { - /// Decompresses a set of signed `VoteExtension` instances. + /// Decompresses a set of signed [`EthEventsVext`] instances. pub fn decompress( self, last_height: BlockHeight, - ) -> Vec> { + ) -> Vec> { let VoteExtensionDigest { signatures, events } = self; let mut extensions = vec![]; for (addr, sig) in signatures.into_iter() { - let mut ext = VoteExtension::empty(last_height, addr.clone()); + let mut ext = EthEventsVext::empty(last_height, addr.clone()); for event in events.iter() { if event.signers.contains(&addr) { @@ -259,7 +264,7 @@ mod tests { /// Test decompression of a set of Ethereum events #[test] fn test_decompress_ethereum_events() { - // we need to construct a `Vec>` + // we need to construct a `Vec>` let sk_1 = key::testing::keypair_1(); let sk_2 = key::testing::keypair_2(); @@ -277,8 +282,8 @@ mod tests { let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); - let ext = |validator: Address| -> VoteExtension { - let mut ext = VoteExtension::empty(last_block_height, validator); + let ext = |validator: Address| -> EthEventsVext { + let mut ext = EthEventsVext::empty(last_block_height, validator); ext.ethereum_events.push(ev_1.clone()); ext.ethereum_events.push(ev_2.clone()); @@ -294,7 +299,7 @@ mod tests { let ext = vec![ext_1, ext_2]; - // we have the `Signed` instances we need, + // we have the `Signed` instances we need, // let us now compress them into a single `VoteExtensionDigest` let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), @@ -322,13 +327,13 @@ mod tests { let digest = VoteExtensionDigest { events, signatures }; // finally, decompress the `VoteExtensionDigest` back into a - // `Vec>` + // `Vec>` let mut decompressed = digest .decompress(last_block_height) .into_iter() - .collect::>>(); + .collect::>>(); - // decompressing yields an arbitrary ordering of `VoteExtension` + // decompressing yields an arbitrary ordering of `EthEventsVext` // instances, which is fine if decompressed[0].data.validator_addr != ext[0].data.validator_addr { decompressed.swap(0, 1); From 8ef9bfe7628c8ae8de3d088b21ed07925b9b79ac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 09:20:04 +0100 Subject: [PATCH 0252/1995] Rename VoteExtension to EthEventsVext --- .../lib/node/ledger/shell/prepare_proposal.rs | 59 +++++++++---------- .../lib/node/ledger/shell/process_proposal.rs | 16 ++--- .../lib/node/ledger/shell/vote_extensions.rs | 26 ++++---- 3 files changed, 50 insertions(+), 51 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7e575322b2..7a93b8a0b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -65,7 +65,7 @@ mod prepare_block { } /// Builds a batch of vote extension transactions, comprised of Ethereum - /// events + /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, local_last_commit: Option, @@ -93,16 +93,16 @@ mod prepare_block { // handle block heights > 0 (Some(digest), _) => digest, _ => unreachable!( - "Honest Namada validators will always sign a \ - VoteExtension, even if no Ethereum events were \ - observed at a given block height. In fact, a quorum \ - of signed empty VoteExtension commits the fact no \ - events were observed by a majority of validators. \ - Likewise, a Tendermint quorum should never decide on \ - a block including vote extensions reflecting less \ - than or equal to 2/3 of the total stake. These \ - scenarios are virtually impossible, so we will panic \ - here." + "Honest Namada validators will always sign \ + EthEventsVext instances, even if no Ethereum events \ + were observed at a given block height. In fact, a \ + quorum of signed empty EthEventsVext instances \ + commits the fact no events were observed by a \ + majority of validators. Likewise, a Tendermint \ + quorum should never decide on a block including vote \ + extensions reflecting less than or equal to 2/3 of \ + the total stake. These scenarios are virtually \ + impossible, so we will panic here." ), }; @@ -111,6 +111,8 @@ mod prepare_block { .to_bytes(); let tx_record = record::add(tx); + // TODO: include here a validator set update tx, + // if we are at the end of an epoch vec![tx_record] } @@ -284,7 +286,7 @@ mod prepare_block { }; use namada::proto::SignedTxData; use namada::types::address::xan; - use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::ethereum_events::vote_extensions::EthEventsVext; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::common; use namada::types::storage::{BlockHeight, Epoch}; @@ -333,10 +335,7 @@ mod prepare_block { } /// Check if we are filtering out an invalid vote extension `vext` - fn check_vote_extension_filtering( - shell: &mut TestShell, - vext: SignedExt, - ) { + fn check_eth_events_filtering(shell: &mut TestShell, vext: SignedExt) { let votes = deserialize_vote_extensions(vec![vote_extension_serialize( vext, @@ -347,7 +346,7 @@ mod prepare_block { assert_eq!(filtered_votes, vec![]); } - /// Test if we are filtering out vote extensinos with bad + /// Test if we are filtering out Ethereum events with bad /// signatures in a prepare proposal. #[test] fn test_prepare_proposal_filter_out_bad_vext_signatures() { @@ -363,7 +362,7 @@ mod prepare_block { let validator_addr = wallet::defaults::validator_address(); // generate a valid signature - let mut ext = VoteExtension { + let mut ext = EthEventsVext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], @@ -376,10 +375,10 @@ mod prepare_block { ext }; - check_vote_extension_filtering(&mut shell, signed_vote_extension); + check_eth_events_filtering(&mut shell, signed_vote_extension); } - /// Test if we are filtering out vote extensinos for + /// Test if we are filtering out Ethereum events seen at /// block heights different than the last height. #[test] fn test_prepare_proposal_filter_out_bad_vext_bheights() { @@ -396,7 +395,7 @@ mod prepare_block { let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr, block_height: PRED_LAST_HEIGHT, ethereum_events: vec![], @@ -406,10 +405,10 @@ mod prepare_block { ext }; - check_vote_extension_filtering(&mut shell, signed_vote_extension); + check_eth_events_filtering(&mut shell, signed_vote_extension); } - /// Test if we are filtering out vote extensinos for + /// Test if we are filtering out Ethereum events seen by /// non-validator nodes. #[test] fn test_prepare_proposal_filter_out_bad_vext_validators() { @@ -427,7 +426,7 @@ mod prepare_block { }; let signed_vote_extension = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], @@ -437,7 +436,7 @@ mod prepare_block { ext }; - check_vote_extension_filtering(&mut shell, signed_vote_extension); + check_eth_events_filtering(&mut shell, signed_vote_extension); } /// Test if we are filtering out duped Ethereum events in @@ -460,7 +459,7 @@ mod prepare_block { }; let signed_vote_extension = { let ev = ethereum_event; - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ev.clone(), ev.clone(), ev], @@ -520,7 +519,7 @@ mod prepare_block { // super::record::add(tx) } - /// Test if vote extension validation and inclusion in a block + /// Test if Ethereum events validation and inclusion in a block /// behaves as expected, considering honest validators. #[test] fn test_prepare_proposal_vext_normal_op() { @@ -539,7 +538,7 @@ mod prepare_block { transfers: vec![], }; let signed_vote_extension = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ethereum_event], @@ -597,7 +596,7 @@ mod prepare_block { // assert_eq!(rsp.tx_records, vec![digest]); } - /// Test if vote extension validation and inclusion in a block + /// Test if Ethereum events validation and inclusion in a block /// behaves as expected, considering <= 2/3 voting power. #[test] #[should_panic(expected = "Honest Namada validators")] @@ -654,7 +653,7 @@ mod prepare_block { transfers: vec![], }; let signed_vote_extension = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ethereum_event], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index bf1748e749..499cadb83b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -373,7 +373,7 @@ mod test_process_proposal { use namada::proto::SignedTxData; use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::{ - MultiSignedEthEvent, VoteExtension, VoteExtensionDigest, + EthEventsVext, MultiSignedEthEvent, VoteExtensionDigest, }; use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; @@ -411,7 +411,7 @@ mod test_process_proposal { let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { let ext = - VoteExtension::empty(LAST_HEIGHT, validator_addr.clone()) + EthEventsVext::empty(LAST_HEIGHT, validator_addr.clone()) .sign(&protocol_key); assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext @@ -465,7 +465,7 @@ mod test_process_proposal { ); } - /// Test that if a proposal contains vote extensions with + /// Test that if a proposal contains Ethereum events with /// invalid validator signatures, we reject it. #[test] fn test_drop_vext_digest_with_invalid_sigs() { @@ -481,7 +481,7 @@ mod test_process_proposal { }; let ext = { // generate a valid signature - let mut ext = VoteExtension { + let mut ext = EthEventsVext { validator_addr: addr.clone(), block_height: LAST_HEIGHT, ethereum_events: vec![event.clone()], @@ -512,7 +512,7 @@ mod test_process_proposal { check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); } - /// Test that if a proposal contains vote extensions with + /// Test that if a proposal contains Ethereum events with /// invalid block heights, we reject it. #[test] fn test_drop_vext_digest_with_invalid_bheights() { @@ -528,7 +528,7 @@ mod test_process_proposal { transfers: vec![], }; let ext = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr: addr.clone(), block_height: PRED_LAST_HEIGHT, ethereum_events: vec![event.clone()], @@ -556,7 +556,7 @@ mod test_process_proposal { check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); } - /// Test that if a proposal contains vote extensions with + /// Test that if a proposal contains Ethereum events with /// invalid validators, we reject it. #[test] fn test_drop_vext_digest_with_invalid_validators() { @@ -574,7 +574,7 @@ mod test_process_proposal { transfers: vec![], }; let ext = { - let ext = VoteExtension { + let ext = EthEventsVext { validator_addr: addr.clone(), block_height: LAST_HEIGHT, ethereum_events: vec![event.clone()], diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 6fb329ac77..96cf4825c7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -3,13 +3,13 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; - use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::ethereum_events::vote_extensions::EthEventsVext; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; - /// A [`VoteExtension`] signed by a Namada validator. - pub type SignedExt = Signed; + /// A [`EthEventsVext`] signed by a Namada validator. + pub type SignedExt = Signed; /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] @@ -47,7 +47,7 @@ mod extend_votes { .get_validator_address() .expect("only validators should receive this method call") .to_owned(); - let ext = VoteExtension { + let ext = EthEventsVext { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), validator_addr, @@ -182,7 +182,7 @@ mod extend_votes { } /// Checks the channel from the Ethereum oracle monitoring - /// the fullnode and retrieves all VoteExtension messages sent. + /// the fullnode and retrieves all seen Ethereum events. pub fn new_ethereum_events(&mut self) -> Vec { match &mut self.mode { ShellMode::Validator { @@ -254,7 +254,7 @@ mod extend_votes { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::types::ethereum_events::vote_extensions::VoteExtension; + use namada::types::ethereum_events::vote_extensions::EthEventsVext; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; @@ -371,7 +371,7 @@ mod extend_votes { assert_eq!(res.status, i32::from(VerifyStatus::Accept)); } - /// Test that Ethereum headers signed by a non-validator is rejected + /// Test that Ethereum events signed by a non-validator are rejected #[test] fn test_eth_events_must_be_signed_by_validator() { let (shell, _, _) = setup(); @@ -381,7 +381,7 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let vote_ext = VoteExtension { + let vote_ext = EthEventsVext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -412,7 +412,7 @@ mod extend_votes { ); } - /// Test that validation of vote extensions cast during the + /// Test that validation of Ethereum events cast during the /// previous block are accepted for the current block. This /// should pass even if the epoch changed resulting in a /// change to the validator set. @@ -427,7 +427,7 @@ mod extend_votes { .expect("Test failed") .clone(); let signed_height = shell.storage.last_height + 1; - let vote_ext = VoteExtension { + let vote_ext = EthEventsVext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -479,13 +479,13 @@ mod extend_votes { assert!(shell.validate_vote_extension(vote_ext, signed_height)); } - /// Test that that an event that incorrectly labels what block it was - /// included in a vote extension on is rejected + /// Test that an [`EthEventsVext`] that incorrectly labels what block it + /// was included on in a vote extension is rejected #[test] fn reject_incorrect_block_number() { let (shell, _, _) = setup(); let address = shell.mode.get_validator_address().unwrap().clone(); - let vote_ext = VoteExtension { + let vote_ext = EthEventsVext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { From fea6969318993536ecec61e664116286f06f8988 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 09:33:00 +0100 Subject: [PATCH 0253/1995] Rename VoteExtensionError --- .../lib/node/ledger/shell/vote_extensions.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 96cf4825c7..7526407ba1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -13,7 +13,7 @@ mod extend_votes { /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] - pub enum VoteExtensionError { + pub enum EthEventsVextError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, #[error("The vote extension has an unexpected block height.")] @@ -125,7 +125,7 @@ mod extend_votes { &self, ext: SignedExt, last_height: BlockHeight, - ) -> std::result::Result<(VotingPower, SignedExt), VoteExtensionError> + ) -> std::result::Result<(VotingPower, SignedExt), EthEventsVextError> { if ext.data.block_height != last_height { let ext_height = ext.data.block_height; @@ -133,11 +133,11 @@ mod extend_votes { "Vote extension issued for a block height {ext_height} \ different from the expected height {last_height}" ); - return Err(VoteExtensionError::UnexpectedBlockHeight); + return Err(EthEventsVextError::UnexpectedBlockHeight); } if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); - return Err(VoteExtensionError::IssuedAtGenesis); + return Err(EthEventsVextError::IssuedAtGenesis); } // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order @@ -154,7 +154,7 @@ mod extend_votes { %validator, "Found duplicate or non-sorted Ethereum events in a vote extension from validator" ); - return Err(VoteExtensionError::HaveDupesOrNonSorted); + return Err(EthEventsVextError::HaveDupesOrNonSorted); } // get the public key associated with this validator let epoch = self.storage.block.pred_epochs.get_epoch(last_height); @@ -166,7 +166,7 @@ mod extend_votes { %validator, "Could not get public key from Storage for validator" ); - VoteExtensionError::PubKeyNotInStorage + EthEventsVextError::PubKeyNotInStorage })?; // verify the signature of the vote extension ext.verify(&pk) @@ -176,7 +176,7 @@ mod extend_votes { %validator, "Failed to verify the signature of a vote extension issued by validator" ); - VoteExtensionError::VerifySigFailed + EthEventsVextError::VerifySigFailed }) .map(|_| (voting_power, ext)) } @@ -199,7 +199,7 @@ mod extend_votes { /// Takes an iterator over signed vote extensions, /// and returns another iterator. The latter yields /// valid vote extensions, or the reason why these - /// are invalid, in the form of a [`VoteExtensionError`]. + /// are invalid, in the form of a [`EthEventsVextError`]. #[inline] pub fn validate_vote_extension_list( &self, @@ -207,7 +207,7 @@ mod extend_votes { ) -> impl Iterator< Item = std::result::Result< (VotingPower, SignedExt), - VoteExtensionError, + EthEventsVextError, >, > + '_ { vote_extensions.into_iter().map(|vote_extension| { From 3ab47dc3d4e0fa7a7a84b502a240b32c6d65aea0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 09:48:40 +0100 Subject: [PATCH 0254/1995] Adjust msgs in Ethereum events error enum --- .../lib/node/ledger/shell/vote_extensions.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 7526407ba1..2eac2cfa02 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -14,13 +14,18 @@ mod extend_votes { /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] pub enum EthEventsVextError { - #[error("The vote extension was issued at block height 0.")] + #[error( + "The Ethereum events vote extension was issued at block height 0." + )] IssuedAtGenesis, - #[error("The vote extension has an unexpected block height.")] + #[error( + "The Ethereum events vote extension has an unexpected block \ + height." + )] UnexpectedBlockHeight, #[error( - "The vote extension contains duplicate or non-sorted Ethereum \ - events." + "The Ethereum events vote extension contains duplicate or \ + non-sorted events." )] HaveDupesOrNonSorted, #[error( @@ -28,7 +33,10 @@ mod extend_votes { could not be found in storage." )] PubKeyNotInStorage, - #[error("The vote extension's signature is invalid.")] + #[error( + "The signature of the vote extension containing the given \ + Ethereum events is invalid." + )] VerifySigFailed, } From 669c7ea25e3d523fd8413a046628ce5c0b027523 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 09:49:38 +0100 Subject: [PATCH 0255/1995] Rename VoteExtensionDigest --- apps/src/lib/node/ledger/protocol/mod.rs | 4 +-- .../lib/node/ledger/shell/prepare_proposal.rs | 28 +++++++++---------- .../lib/node/ledger/shell/process_proposal.rs | 18 ++++++------ .../types/ethereum_events/vote_extensions.rs | 12 ++++---- shared/src/types/transaction/protocol.rs | 4 +-- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 73e9b5b710..1243cc71ee 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -14,7 +14,7 @@ use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; -use namada::types::ethereum_events::vote_extensions::VoteExtensionDigest; +use namada::types::ethereum_events::vote_extensions::EthEventsVextDigest; use namada::types::storage; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; @@ -159,7 +159,7 @@ where } TxType::Protocol(ProtocolTx { tx: - ProtocolTxType::EthereumEvents(VoteExtensionDigest { + ProtocolTxType::EthereumEvents(EthEventsVextDigest { events, .. }), .. diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7a93b8a0b3..af7b5d1668 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -5,7 +5,7 @@ mod prepare_block { use std::collections::{BTreeMap, HashMap, HashSet}; use namada::types::ethereum_events::vote_extensions::{ - FractionalVotingPower, MultiSignedEthEvent, VoteExtensionDigest, + EthEventsVextDigest, FractionalVotingPower, MultiSignedEthEvent, }; use namada::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ @@ -78,7 +78,7 @@ mod prepare_block { let vote_extension_digest = local_last_commit.and_then(|local_last_commit| { let votes = local_last_commit.votes; - self.compress_vote_extensions(votes) + self.compress_ethereum_events(votes) }); let vote_extension_digest = match (vote_extension_digest, self.storage.last_height) { @@ -161,13 +161,13 @@ mod prepare_block { .collect() } - /// Compresses a set of vote extensions into a single - /// [`VoteExtensionDigest`], whilst filtering invalid + /// Compresses a set of signed Ethereum events into a single + /// [`EthEventsVextDigest`], whilst filtering invalid /// [`SignedExt`] instances in the process - fn compress_vote_extensions( + fn compress_ethereum_events( &self, vote_extensions: Vec, - ) -> Option { + ) -> Option { let events_epoch = self .storage .block @@ -219,15 +219,15 @@ mod prepare_block { ?sig, ?validator_addr, "Overwrote old signature from validator while \ - constructing VoteExtensionDigest" + constructing EthEventsVextDigest" ); } } if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( - "Tendermint has decided on a block including vote \ - extensions reflecting <= 2/3 of the total stake" + "Tendermint has decided on a block including Ethereum \ + events reflecting <= 2/3 of the total stake" ); return None; } @@ -237,7 +237,7 @@ mod prepare_block { .map(|(event, signers)| MultiSignedEthEvent { event, signers }) .collect(); - Some(VoteExtensionDigest { events, signatures }) + Some(EthEventsVextDigest { events, signatures }) } } @@ -472,7 +472,7 @@ mod prepare_block { let maybe_digest = { let votes = vec![vote_extension_serialize(signed_vote_extension)]; - shell.compress_vote_extensions(votes) + shell.compress_ethereum_events(votes) }; // we should be filtering out the vote extension with @@ -482,13 +482,13 @@ mod prepare_block { assert!(maybe_digest.is_none()); } - /// Creates a vote extension digest manually, and encodes it as a + /// Creates an Ethereum events digest manually, and encodes it as a /// [`TxRecord`]. fn manually_assemble_digest( _protocol_key: &common::SecretKey, ext: SignedExt, last_height: BlockHeight, - ) -> VoteExtensionDigest { + ) -> EthEventsVextDigest { let events = vec![MultiSignedEthEvent { event: ext.data.ethereum_events[0].clone(), signers: { @@ -504,7 +504,7 @@ mod prepare_block { }; let vote_extension_digest = - VoteExtensionDigest { events, signatures }; + EthEventsVextDigest { events, signatures }; assert_eq!( vec![ext], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 499cadb83b..41436402d7 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -47,7 +47,7 @@ where }) .collect(); - // We should not have more than one `VoteExtensionDigest` in + // We should not have more than one `EthEventsVextDigest` in // a proposal from some round's leader. let too_many_vext_digests = vote_ext_digest_num > 1; @@ -373,7 +373,7 @@ mod test_process_proposal { use namada::proto::SignedTxData; use namada::types::address::xan; use namada::types::ethereum_events::vote_extensions::{ - EthEventsVext, MultiSignedEthEvent, VoteExtensionDigest, + EthEventsVext, EthEventsVextDigest, MultiSignedEthEvent, }; use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; @@ -399,7 +399,7 @@ mod test_process_proposal { }; use crate::wallet; - /// Test that if a proposal contains more than one `VoteExtensionDigest`, + /// Test that if a proposal contains more than one `EthEventsVextDigest`, /// we reject it. #[test] fn test_more_than_one_vext_digest_rejected() { @@ -416,8 +416,8 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - // vote extension digest with no observed events - VoteExtensionDigest { + // Ethereum events digest with no observed events + EthEventsVextDigest { signatures: { let mut s = HashMap::new(); s.insert(validator_addr, signed_vote_extension.sig); @@ -441,7 +441,7 @@ mod test_process_proposal { fn check_rejected_digest( shell: &mut TestShell, - vote_extension_digest: VoteExtensionDigest, + vote_extension_digest: EthEventsVextDigest, protocol_key: common::SecretKey, ) { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) @@ -493,7 +493,7 @@ mod test_process_proposal { ext.sig = test_utils::invalidate_signature(ext.sig); ext }; - VoteExtensionDigest { + EthEventsVextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); @@ -537,7 +537,7 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - VoteExtensionDigest { + EthEventsVextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); @@ -583,7 +583,7 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - VoteExtensionDigest { + EthEventsVextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/ethereum_events/vote_extensions.rs index 9e26dd4280..eadc552a2d 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/ethereum_events/vote_extensions.rs @@ -157,20 +157,20 @@ pub struct MultiSignedEthEvent { #[derive( Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] -pub struct VoteExtensionDigest { +pub struct EthEventsVextDigest { /// The signatures and signing address of each [`EthEventsVext`] pub signatures: HashMap, /// The events that were reported pub events: Vec, } -impl VoteExtensionDigest { +impl EthEventsVextDigest { /// Decompresses a set of signed [`EthEventsVext`] instances. pub fn decompress( self, last_height: BlockHeight, ) -> Vec> { - let VoteExtensionDigest { signatures, events } = self; + let EthEventsVextDigest { signatures, events } = self; let mut extensions = vec![]; @@ -300,7 +300,7 @@ mod tests { let ext = vec![ext_1, ext_2]; // we have the `Signed` instances we need, - // let us now compress them into a single `VoteExtensionDigest` + // let us now compress them into a single `EthEventsVextDigest` let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), (validator_2.clone(), ext[1].sig.clone()), @@ -324,9 +324,9 @@ mod tests { }, ]; - let digest = VoteExtensionDigest { events, signatures }; + let digest = EthEventsVextDigest { events, signatures }; - // finally, decompress the `VoteExtensionDigest` back into a + // finally, decompress the `EthEventsVextDigest` back into a // `Vec>` let mut decompressed = digest .decompress(last_block_height) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index a956935a52..faf36baff4 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -33,7 +33,7 @@ mod protocol_txs { use super::*; use crate::proto::Tx; - use crate::types::ethereum_events::vote_extensions::VoteExtensionDigest; + use crate::types::ethereum_events::vote_extensions::EthEventsVextDigest; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; @@ -78,7 +78,7 @@ mod protocol_txs { /// Tx requesting a new DKG session keypair NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions - EthereumEvents(VoteExtensionDigest), + EthereumEvents(EthEventsVextDigest), } impl ProtocolTxType { From 381abe0cb8b270001b07a886d59669b4eaa483c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 10:05:38 +0100 Subject: [PATCH 0256/1995] Add a TODO --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 2eac2cfa02..7c96e07d4a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -248,6 +248,8 @@ mod extend_votes { .map_err(|err| { tracing::error!( ?err, + // TODO: change this error message, probably, such that + // it mentions Ethereum events rather than vote extensions "Failed to deserialize signed vote extension", ); }) From fb61990f6f643aeb4302eb8a361cbe58d0312cb7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 2 Aug 2022 10:36:51 +0100 Subject: [PATCH 0257/1995] Move docstrings onto QueriesExt --- apps/src/lib/node/ledger/shell/queries.rs | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0a04cde9d0..b8bbedb7f1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -272,31 +272,50 @@ where /// API for querying the blockchain state. pub(crate) trait QueriesExt { + /// Get the set of active validators for a given epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). fn get_active_validators( &self, epoch: Option, ) -> BTreeSet>; + + /// Lookup the total voting power for an epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). fn get_total_voting_power(&self, epoch: Option) -> VotingPower; + + /// Simple helper function for the ledger to get balances + /// of the specified token at the specified address fn get_balance( &self, token: &Address, owner: &Address, ) -> std::result::Result; + fn get_evidence_params( &self, epoch_duration: &EpochDuration, pos_params: &PosParams, ) -> EvidenceParams; + + /// Lookup data about a validator from their protocol signing key fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, epoch: Option, ) -> std::result::Result, Error>; + + /// Lookup data about a validator from their address fn get_validator_from_address( &self, address: &Address, epoch: Option, ) -> std::result::Result<(VotingPower, common::PublicKey), Error>; + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. + // TODO: We may change how this lookup is done, see + // https://github.com/anoma/namada/issues/200 fn get_validator_from_tm_address( &self, tm_address: &[u8], @@ -309,8 +328,6 @@ where D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, { - /// Get the set of active validators for a given epoch (defaulting to the - /// epoch of the current yet-to-be-committed block). fn get_active_validators( &self, epoch: Option, @@ -324,7 +341,6 @@ where .clone() } - /// Lookup the total voting power for an epoch #[cfg(not(feature = "ABCI"))] fn get_total_voting_power(&self, epoch: Option) -> VotingPower { self.get_active_validators(epoch) @@ -334,8 +350,6 @@ where .into() } - /// Simple helper function for the ledger to get balances - /// of the specified token at the specified address fn get_balance( &self, token: &Address, @@ -390,7 +404,6 @@ where } } - /// Lookup data about a validator from their protocol signing key #[allow(dead_code)] fn get_validator_from_protocol_pk( &self, @@ -438,7 +451,6 @@ where .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) } - /// Lookup data about a validator from their address #[cfg(not(feature = "ABCI"))] fn get_validator_from_address( &self, @@ -466,11 +478,6 @@ where .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } - /// Given a tendermint validator, the address is the hash - /// of the validators public key. We look up the native - /// address from storage using this hash. - // TODO: We may change how this lookup is done, see - // https://github.com/anoma/namada/issues/200 #[allow(dead_code)] fn get_validator_from_tm_address( &self, From 9e100d461eaf59214c10eea810c23bc83a29257c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 2 Aug 2022 10:37:25 +0100 Subject: [PATCH 0258/1995] Factor get_validator_from_protocol_pk to use get_active_validators --- apps/src/lib/node/ledger/shell/queries.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b8bbedb7f1..4f6a885677 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -414,11 +414,7 @@ where .try_to_vec() .expect("Serializing public key should not fail"); let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - // get the active validator set - self.read_validator_set() - .get(epoch) - .expect("Validators for an epoch should be known") - .active + self.get_active_validators(Some(epoch)) .iter() .find(|validator| { let pk_key = key::protocol_pk_key(&validator.address); From ea40090e7303cac4c4845b9fe4c64dcab51823a5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 11:34:20 +0100 Subject: [PATCH 0259/1995] Rename SignedExt --- .../lib/node/ledger/shell/prepare_proposal.rs | 17 +++++---- .../lib/node/ledger/shell/vote_extensions.rs | 36 ++++++++++--------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index af7b5d1668..1c96648a4d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -163,7 +163,7 @@ mod prepare_block { /// Compresses a set of signed Ethereum events into a single /// [`EthEventsVextDigest`], whilst filtering invalid - /// [`SignedExt`] instances in the process + /// [`SignedEthEventsVext`] instances in the process fn compress_ethereum_events( &self, vote_extensions: Vec, @@ -297,7 +297,7 @@ mod prepare_block { ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use super::super::super::vote_extensions::SignedExt; + use super::super::super::vote_extensions::SignedEthEventsVext; use super::*; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, @@ -326,8 +326,10 @@ mod prepare_block { ); } - /// Serialize a [`SignedExt`] to an [`ExtendedVoteInfo`] - fn vote_extension_serialize(vext: SignedExt) -> ExtendedVoteInfo { + /// Serialize a [`SignedEthEventsVext`] to an [`ExtendedVoteInfo`] + fn vote_extension_serialize( + vext: SignedEthEventsVext, + ) -> ExtendedVoteInfo { ExtendedVoteInfo { vote_extension: vext.try_to_vec().unwrap(), ..Default::default() @@ -335,7 +337,10 @@ mod prepare_block { } /// Check if we are filtering out an invalid vote extension `vext` - fn check_eth_events_filtering(shell: &mut TestShell, vext: SignedExt) { + fn check_eth_events_filtering( + shell: &mut TestShell, + vext: SignedEthEventsVext, + ) { let votes = deserialize_vote_extensions(vec![vote_extension_serialize( vext, @@ -486,7 +491,7 @@ mod prepare_block { /// [`TxRecord`]. fn manually_assemble_digest( _protocol_key: &common::SecretKey, - ext: SignedExt, + ext: SignedEthEventsVext, last_height: BlockHeight, ) -> EthEventsVextDigest { let events = vec![MultiSignedEthEvent { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 7c96e07d4a..c03ba6cae5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,7 +9,7 @@ mod extend_votes { use super::super::*; /// A [`EthEventsVext`] signed by a Namada validator. - pub type SignedExt = Signed; + pub type SignedEthEventsVext = Signed; /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] @@ -79,7 +79,7 @@ mod extend_votes { req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { if let Ok(signed) = - SignedExt::try_from_slice(&req.vote_extension[..]) + SignedEthEventsVext::try_from_slice(&req.vote_extension[..]) { response::VerifyVoteExtension { status: if self.validate_vote_extension( @@ -119,7 +119,7 @@ mod extend_votes { #[inline] pub fn validate_vote_extension( &self, - ext: SignedExt, + ext: SignedEthEventsVext, last_height: BlockHeight, ) -> bool { self.validate_vote_ext_and_get_it_back(ext, last_height) @@ -131,10 +131,12 @@ mod extend_votes { /// is valid. pub fn validate_vote_ext_and_get_it_back( &self, - ext: SignedExt, + ext: SignedEthEventsVext, last_height: BlockHeight, - ) -> std::result::Result<(VotingPower, SignedExt), EthEventsVextError> - { + ) -> std::result::Result< + (VotingPower, SignedEthEventsVext), + EthEventsVextError, + > { if ext.data.block_height != last_height { let ext_height = ext.data.block_height; tracing::error!( @@ -211,10 +213,10 @@ mod extend_votes { #[inline] pub fn validate_vote_extension_list( &self, - vote_extensions: impl IntoIterator + 'static, + vote_extensions: impl IntoIterator + 'static, ) -> impl Iterator< Item = std::result::Result< - (VotingPower, SignedExt), + (VotingPower, SignedEthEventsVext), EthEventsVextError, >, > + '_ { @@ -231,25 +233,27 @@ mod extend_votes { #[inline] pub fn filter_invalid_vote_extensions( &self, - vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator + '_ { + vote_extensions: impl IntoIterator + 'static, + ) -> impl Iterator + '_ + { self.validate_vote_extension_list(vote_extensions) .filter_map(|ext| ext.ok()) } } /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// ones we could deserialize to [`SignedExt`] instances. + /// ones we could deserialize to [`SignedEthEventsVext`] instances. pub fn deserialize_vote_extensions( vote_extensions: Vec, - ) -> impl Iterator + 'static { + ) -> impl Iterator + 'static { vote_extensions.into_iter().filter_map(|vote| { - SignedExt::try_from_slice(&vote.vote_extension[..]) + SignedEthEventsVext::try_from_slice(&vote.vote_extension[..]) .map_err(|err| { tracing::error!( ?err, // TODO: change this error message, probably, such that - // it mentions Ethereum events rather than vote extensions + // it mentions Ethereum events rather than vote + // extensions "Failed to deserialize signed vote extension", ); }) @@ -273,7 +277,7 @@ mod extend_votes { use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; - use super::SignedExt; + use super::SignedEthEventsVext; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; @@ -350,7 +354,7 @@ mod extend_votes { oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); let vote_extension = - ::try_from_slice( + ::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], ) .expect("Test failed"); From 527cd14119aff13e998259822c21709c150c5171 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 13:02:58 +0100 Subject: [PATCH 0260/1995] Reorganize vote extensions mod --- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 ++++---- apps/src/lib/node/ledger/shell/vote_extensions.rs | 4 ++-- shared/src/types/ethereum_events.rs | 4 +--- shared/src/types/mod.rs | 1 + shared/src/types/transaction/protocol.rs | 2 +- shared/src/types/vote_extensions.rs | 3 +++ .../ethereum_events.rs} | 5 ++--- 9 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 shared/src/types/vote_extensions.rs rename shared/src/types/{ethereum_events/vote_extensions.rs => vote_extensions/ethereum_events.rs} (99%) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 1243cc71ee..e6ff3866f1 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -14,10 +14,10 @@ use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; -use namada::types::ethereum_events::vote_extensions::EthEventsVextDigest; use namada::types::storage; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; +use namada::types::vote_extensions::ethereum_events::EthEventsVextDigest; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1c96648a4d..ff1cff8e44 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,10 +4,10 @@ mod prepare_block { use std::collections::{BTreeMap, HashMap, HashSet}; - use namada::types::ethereum_events::vote_extensions::{ + use namada::types::transaction::protocol::ProtocolTxType; + use namada::types::vote_extensions::ethereum_events::{ EthEventsVextDigest, FractionalVotingPower, MultiSignedEthEvent, }; - use namada::types::transaction::protocol::ProtocolTxType; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; @@ -286,12 +286,12 @@ mod prepare_block { }; use namada::proto::SignedTxData; use namada::types::address::xan; - use namada::types::ethereum_events::vote_extensions::EthEventsVext; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::common; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; + use namada::types::vote_extensions::ethereum_events::EthEventsVext; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 41436402d7..eacc14fc5a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,7 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -use namada::types::ethereum_events::vote_extensions::FractionalVotingPower; use namada::types::transaction::protocol::ProtocolTxType; +use namada::types::vote_extensions::ethereum_events::FractionalVotingPower; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] @@ -372,9 +372,6 @@ mod test_process_proposal { use borsh::BorshDeserialize; use namada::proto::SignedTxData; use namada::types::address::xan; - use namada::types::ethereum_events::vote_extensions::{ - EthEventsVext, EthEventsVextDigest, MultiSignedEthEvent, - }; use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; use namada::types::key::*; @@ -382,6 +379,9 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; + use namada::types::vote_extensions::ethereum_events::{ + EthEventsVext, EthEventsVextDigest, MultiSignedEthEvent, + }; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::RequestInitChain; #[cfg(not(feature = "ABCI"))] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c03ba6cae5..908d371b1b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -3,7 +3,7 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; - use namada::types::ethereum_events::vote_extensions::EthEventsVext; + use namada::types::vote_extensions::ethereum_events::EthEventsVext; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; @@ -268,12 +268,12 @@ mod extend_votes { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::types::ethereum_events::vote_extensions::EthEventsVext; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; use namada::types::key::*; use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::vote_extensions::ethereum_events::EthEventsVext; use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 1b5fcf7657..ce12e6704b 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -1,7 +1,5 @@ //! Types representing data intended for Anoma via Ethereum events -pub mod vote_extensions; - use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -174,7 +172,7 @@ pub enum EthereumEvent { impl EthereumEvent { /// SHA256 of the Borsh serialization of the [`EthereumEvent`]. #[allow(dead_code)] - fn hash(&self) -> Result { + pub(crate) fn hash(&self) -> Result { let bytes = self.try_to_vec()?; Ok(Hash::sha256(&bytes)) } diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 748475a423..9a4641facc 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -17,3 +17,4 @@ pub mod time; pub mod token; pub mod transaction; pub mod validity_predicate; +pub mod vote_extensions; diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index faf36baff4..d09dfaec99 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -33,9 +33,9 @@ mod protocol_txs { use super::*; use crate::proto::Tx; - use crate::types::ethereum_events::vote_extensions::EthEventsVextDigest; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; + use crate::types::vote_extensions::ethereum_events::EthEventsVextDigest; const TX_NEW_DKG_KP_WASM: &str = "tx_update_dkg_session_keypair.wasm"; diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs new file mode 100644 index 0000000000..b67d3bbe61 --- /dev/null +++ b/shared/src/types/vote_extensions.rs @@ -0,0 +1,3 @@ +//! This module contains types necessary for processing vote extensions. + +pub mod ethereum_events; diff --git a/shared/src/types/ethereum_events/vote_extensions.rs b/shared/src/types/vote_extensions/ethereum_events.rs similarity index 99% rename from shared/src/types/ethereum_events/vote_extensions.rs rename to shared/src/types/vote_extensions/ethereum_events.rs index eadc552a2d..2042aa1a0c 100644 --- a/shared/src/types/ethereum_events/vote_extensions.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -8,9 +8,9 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use num_rational::Ratio; -use super::EthereumEvent; use crate::proto::Signed; use crate::types::address::Address; +use crate::types::ethereum_events::EthereumEvent; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; @@ -200,11 +200,10 @@ impl EthEventsVextDigest { mod tests { use std::collections::HashSet; - use super::super::EthereumEvent; use super::*; use crate::proto::Signed; use crate::types::address::{self, Address}; - use crate::types::ethereum_events::Uint; + use crate::types::ethereum_events::{EthereumEvent, Uint}; use crate::types::hash::Hash; use crate::types::key; use crate::types::key::RefTo; From 854af7260db58dec8c9d5e5eb00ea86a7590c030 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 13:16:35 +0100 Subject: [PATCH 0261/1995] Small typo fix --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 8f847a3c56..7716050eab 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,7 +9,7 @@ mod extend_votes { use super::super::queries::QueriesExt; use super::super::*; - /// A [`EthEventsVext`] signed by a Namada validator. + /// An [`EthEventsVext`] signed by a Namada validator. pub type SignedEthEventsVext = Signed; /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. From 21263c87adf41caacab74f06da069615b59763d2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 13:37:01 +0100 Subject: [PATCH 0262/1995] Move FractionalVotingPower to its own mod --- .../lib/node/ledger/shell/prepare_proposal.rs | 3 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- shared/src/types/mod.rs | 1 + .../types/vote_extensions/ethereum_events.rs | 124 ---------------- shared/src/types/voting_power.rs | 133 ++++++++++++++++++ 5 files changed, 137 insertions(+), 126 deletions(-) create mode 100644 shared/src/types/voting_power.rs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c7314a1809..673a7b9f8a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,8 +6,9 @@ mod prepare_block { use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::ethereum_events::{ - EthEventsVextDigest, FractionalVotingPower, MultiSignedEthEvent, + EthEventsVextDigest, MultiSignedEthEvent, }; + use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d509eba35d..7b7edc1ed8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,7 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell use namada::types::transaction::protocol::ProtocolTxType; -use namada::types::vote_extensions::ethereum_events::FractionalVotingPower; +use namada::types::voting_power::FractionalVotingPower; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; #[cfg(not(feature = "ABCI"))] diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 9a4641facc..ddef48f8eb 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -18,3 +18,4 @@ pub mod token; pub mod transaction; pub mod validity_predicate; pub mod vote_extensions; +pub mod voting_power; diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 2042aa1a0c..519653fcb2 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -2,11 +2,8 @@ //! in vote extensions. use std::collections::{HashMap, HashSet}; -use std::ops::{Add, AddAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use eyre::{eyre, Result}; -use num_rational::Ratio; use crate::proto::Signed; use crate::types::address::Address; @@ -50,96 +47,6 @@ impl EthEventsVext { } } -/// A fraction of the total voting power. This should always be a reduced -/// fraction that is between zero and one inclusive. -#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] -pub struct FractionalVotingPower(Ratio); - -impl FractionalVotingPower { - /// Two thirds of the voting power. - pub const TWO_THIRDS: FractionalVotingPower = - FractionalVotingPower(Ratio::new_raw(2, 3)); - - /// Create a new FractionalVotingPower. It must be between zero and one - /// inclusive. - pub fn new(numer: u64, denom: u64) -> Result { - if denom == 0 { - return Err(eyre!("denominator can't be zero")); - } - let ratio: Ratio = (numer, denom).into(); - if ratio > 1.into() { - return Err(eyre!( - "fractional voting power cannot be greater than one" - )); - } - Ok(Self(ratio)) - } -} - -impl Default for FractionalVotingPower { - fn default() -> Self { - Self::new(0, 1).unwrap() - } -} - -impl From<&FractionalVotingPower> for (u64, u64) { - fn from(ratio: &FractionalVotingPower) -> Self { - (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) - } -} - -impl Add for FractionalVotingPower { - type Output = Self; - - fn add(self, rhs: FractionalVotingPower) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for FractionalVotingPower { - fn add_assign(&mut self, rhs: FractionalVotingPower) { - *self = Self(self.0 + rhs.0) - } -} - -impl BorshSerialize for FractionalVotingPower { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let (numer, denom): (u64, u64) = self.into(); - (numer, denom).serialize(writer) - } -} - -impl BorshDeserialize for FractionalVotingPower { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; - Ok(FractionalVotingPower(Ratio::::new(numer, denom))) - } -} - -impl BorshSchema for FractionalVotingPower { - fn add_definitions_recursively( - definitions: &mut std::collections::HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - u64::declaration(), - u64::declaration() - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "FractionalVotingPower".into() - } -} - /// Aggregates an Ethereum event with the corresponding // validators who saw this event. #[derive( @@ -229,37 +136,6 @@ mod tests { ); } - /// This test is ultimately just exercising the underlying - /// library we use for fractions, we want to make sure - /// operators work as expected with our FractionalVotingPower - /// type itself - #[test] - fn test_fractional_voting_power_ord_eq() { - assert!( - FractionalVotingPower::TWO_THIRDS - > FractionalVotingPower::new(1, 4).unwrap() - ); - assert!( - FractionalVotingPower::new(1, 3).unwrap() - > FractionalVotingPower::new(1, 4).unwrap() - ); - assert!( - FractionalVotingPower::new(1, 3).unwrap() - == FractionalVotingPower::new(2, 6).unwrap() - ); - } - - /// Test error handling on the FractionalVotingPower type - #[test] - fn test_fractional_voting_power_valid_fractions() { - assert!(FractionalVotingPower::new(0, 0).is_err()); - assert!(FractionalVotingPower::new(1, 0).is_err()); - assert!(FractionalVotingPower::new(0, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 1).is_ok()); - assert!(FractionalVotingPower::new(1, 2).is_ok()); - assert!(FractionalVotingPower::new(3, 2).is_err()); - } - /// Test decompression of a set of Ethereum events #[test] fn test_decompress_ethereum_events() { diff --git a/shared/src/types/voting_power.rs b/shared/src/types/voting_power.rs new file mode 100644 index 0000000000..e5c35c20bb --- /dev/null +++ b/shared/src/types/voting_power.rs @@ -0,0 +1,133 @@ +//! This module contains types related with validator voting power calculations. + +use std::ops::{Add, AddAssign}; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::{eyre, Result}; +use num_rational::Ratio; + +/// A fraction of the total voting power. This should always be a reduced +/// fraction that is between zero and one inclusive. +#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +pub struct FractionalVotingPower(Ratio); + +impl FractionalVotingPower { + /// Two thirds of the voting power. + pub const TWO_THIRDS: FractionalVotingPower = + FractionalVotingPower(Ratio::new_raw(2, 3)); + + /// Create a new FractionalVotingPower. It must be between zero and one + /// inclusive. + pub fn new(numer: u64, denom: u64) -> Result { + if denom == 0 { + return Err(eyre!("denominator can't be zero")); + } + let ratio: Ratio = (numer, denom).into(); + if ratio > 1.into() { + return Err(eyre!( + "fractional voting power cannot be greater than one" + )); + } + Ok(Self(ratio)) + } +} + +impl Default for FractionalVotingPower { + fn default() -> Self { + Self::new(0, 1).unwrap() + } +} + +impl From<&FractionalVotingPower> for (u64, u64) { + fn from(ratio: &FractionalVotingPower) -> Self { + (ratio.0.numer().to_owned(), ratio.0.denom().to_owned()) + } +} + +impl Add for FractionalVotingPower { + type Output = Self; + + fn add(self, rhs: FractionalVotingPower) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FractionalVotingPower { + fn add_assign(&mut self, rhs: FractionalVotingPower) { + *self = Self(self.0 + rhs.0) + } +} + +impl BorshSerialize for FractionalVotingPower { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let (numer, denom): (u64, u64) = self.into(); + (numer, denom).serialize(writer) + } +} + +impl BorshDeserialize for FractionalVotingPower { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; + Ok(FractionalVotingPower(Ratio::::new(numer, denom))) + } +} + +impl BorshSchema for FractionalVotingPower { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + u64::declaration(), + u64::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "FractionalVotingPower".into() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// This test is ultimately just exercising the underlying + /// library we use for fractions, we want to make sure + /// operators work as expected with our FractionalVotingPower + /// type itself + #[test] + fn test_fractional_voting_power_ord_eq() { + assert!( + FractionalVotingPower::TWO_THIRDS + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + > FractionalVotingPower::new(1, 4).unwrap() + ); + assert!( + FractionalVotingPower::new(1, 3).unwrap() + == FractionalVotingPower::new(2, 6).unwrap() + ); + } + + /// Test error handling on the FractionalVotingPower type + #[test] + fn test_fractional_voting_power_valid_fractions() { + assert!(FractionalVotingPower::new(0, 0).is_err()); + assert!(FractionalVotingPower::new(1, 0).is_err()); + assert!(FractionalVotingPower::new(0, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 1).is_ok()); + assert!(FractionalVotingPower::new(1, 2).is_ok()); + assert!(FractionalVotingPower::new(3, 2).is_err()); + } +} From 548e1fcbffa089c45684583f9ad3a1a3a5037986 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:05:55 +0100 Subject: [PATCH 0263/1995] Move SignedEthEventsVext to shared --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 5 +++-- apps/src/lib/node/ledger/shell/vote_extensions.rs | 8 +++----- .../src/types/vote_extensions/ethereum_events.rs | 15 +++++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 673a7b9f8a..b69c1e06fe 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -294,13 +294,14 @@ mod prepare_block { use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; - use namada::types::vote_extensions::ethereum_events::EthEventsVext; + use namada::types::vote_extensions::ethereum_events::{ + EthEventsVext, SignedEthEventsVext, + }; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use super::super::super::vote_extensions::SignedEthEventsVext; use super::*; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 7716050eab..20c053112b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -2,16 +2,14 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; - use namada::proto::Signed; - use namada::types::vote_extensions::ethereum_events::EthEventsVext; + use namada::types::vote_extensions::ethereum_events::{ + EthEventsVext, SignedEthEventsVext, + }; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::queries::QueriesExt; use super::super::*; - /// An [`EthEventsVext`] signed by a Namada validator. - pub type SignedEthEventsVext = Signed; - /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] pub enum EthEventsVextError { diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 519653fcb2..17dd08e496 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -11,6 +11,9 @@ use crate::types::ethereum_events::EthereumEvent; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; +/// An [`EthEventsVext`] instance signed by a Namada validator. +pub type SignedEthEventsVext = Signed; + /// Represents a set of `EthereumEvent` instances /// seen by some validator. /// @@ -42,7 +45,7 @@ impl EthEventsVext { /// Sign a [`EthEventsVext`] with a validator's `signing_key`, /// and return the signed data. - pub fn sign(self, signing_key: &common::SecretKey) -> Signed { + pub fn sign(self, signing_key: &common::SecretKey) -> SignedEthEventsVext { Signed::new(signing_key, self) } } @@ -76,7 +79,7 @@ impl EthEventsVextDigest { pub fn decompress( self, last_height: BlockHeight, - ) -> Vec> { + ) -> Vec { let EthEventsVextDigest { signatures, events } = self; let mut extensions = vec![]; @@ -139,7 +142,7 @@ mod tests { /// Test decompression of a set of Ethereum events #[test] fn test_decompress_ethereum_events() { - // we need to construct a `Vec>` + // we need to construct a `Vec` let sk_1 = key::testing::keypair_1(); let sk_2 = key::testing::keypair_2(); @@ -174,7 +177,7 @@ mod tests { let ext = vec![ext_1, ext_2]; - // we have the `Signed` instances we need, + // we have the `SignedEthEventsVext` instances we need, // let us now compress them into a single `EthEventsVextDigest` let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), @@ -202,11 +205,11 @@ mod tests { let digest = EthEventsVextDigest { events, signatures }; // finally, decompress the `EthEventsVextDigest` back into a - // `Vec>` + // `Vec` let mut decompressed = digest .decompress(last_block_height) .into_iter() - .collect::>>(); + .collect::>(); // decompressing yields an arbitrary ordering of `EthEventsVext` // instances, which is fine From 79984e0ceacd2296547a18de736c109af1dab736 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:10:33 +0100 Subject: [PATCH 0264/1995] Add a TODO --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 20c053112b..ede3632ca7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -77,6 +77,10 @@ mod extend_votes { &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { + // TODO: this should deserialize to + // `namada::types::vote_extensions::VoteExtension`, + // which contains an optional validator set update and + // a set of ethereum events seen at the previous block height if let Ok(signed) = SignedEthEventsVext::try_from_slice(&req.vote_extension[..]) { From 180844b0657d5a349463a89baba3fa7cac222ee5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:49:42 +0100 Subject: [PATCH 0265/1995] Rename validate_vote_extension() --- .../lib/node/ledger/shell/prepare_proposal.rs | 2 +- .../src/lib/node/ledger/shell/vote_extensions.rs | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b69c1e06fe..6056df6a57 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -89,7 +89,7 @@ mod prepare_block { (Some(_), BlockHeight(0)) => { unreachable!( "We already handle this scenario in \ - validate_vote_extension." + validate_eth_events_vext." ) } // handle block heights > 0 diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ede3632ca7..103c724bc8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -85,7 +85,7 @@ mod extend_votes { SignedEthEventsVext::try_from_slice(&req.vote_extension[..]) { response::VerifyVoteExtension { - status: if self.validate_vote_extension( + status: if self.validate_eth_events_vext( signed, self.storage.last_height + 1, ) { @@ -113,14 +113,18 @@ mod extend_votes { } } - /// Validates a vote extension issued at the provided block height - /// Checks that at epoch of the provided height - /// * The tendermint address corresponds to an active validator + /// Validates an Ethereum events vote extension issued at the provided + /// block height + /// + /// Checks that at epoch of the provided height: + /// * The Tendermint address corresponds to an active validator /// * The validator correctly signed the extension /// * The validator signed over the correct height inside of the /// extension + /// * There are no duplicate Ethereum events in this vote extension, + /// and the events are sorted in ascending order #[inline] - pub fn validate_vote_extension( + pub fn validate_eth_events_vext( &self, ext: SignedEthEventsVext, last_height: BlockHeight, @@ -497,7 +501,7 @@ mod extend_votes { .is_ok() ); - assert!(shell.validate_vote_extension(vote_ext, signed_height)); + assert!(shell.validate_eth_events_vext(vote_ext, signed_height)); } /// Test that an [`EthEventsVext`] that incorrectly labels what block it From 803af46d4bed6203969a505acb88bb511ab75ae1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:51:18 +0100 Subject: [PATCH 0266/1995] Rename validate_vote_ext_and_get_it_back() --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 103c724bc8..b1230151a6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -129,14 +129,14 @@ mod extend_votes { ext: SignedEthEventsVext, last_height: BlockHeight, ) -> bool { - self.validate_vote_ext_and_get_it_back(ext, last_height) + self.validate_eth_events_vext_and_get_it_back(ext, last_height) .is_ok() } - /// This method behaves exactly like [`Self::validate_vote_extension`], + /// This method behaves exactly like [`Self::validate_eth_events_vext`], /// with the added bonus of returning the vote extension back, if it /// is valid. - pub fn validate_vote_ext_and_get_it_back( + pub fn validate_eth_events_vext_and_get_it_back( &self, ext: SignedEthEventsVext, last_height: BlockHeight, @@ -229,7 +229,7 @@ mod extend_votes { >, > + '_ { vote_extensions.into_iter().map(|vote_extension| { - self.validate_vote_ext_and_get_it_back( + self.validate_eth_events_vext_and_get_it_back( vote_extension, self.storage.last_height, ) From b7fc53be65e4b574628fe65fbfd0ae60898241c4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:53:06 +0100 Subject: [PATCH 0267/1995] Add a TODO --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b1230151a6..f38caf18bc 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -251,6 +251,9 @@ mod extend_votes { /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the /// ones we could deserialize to [`SignedEthEventsVext`] instances. + // TODO: we need to return an iterator over instances of `VoteExtension`, + // which contain both the ethereum events vote extensions and validator + // set update vote extensions pub fn deserialize_vote_extensions( vote_extensions: Vec, ) -> impl Iterator + 'static { From 2b79082a3c6862edebd317e6560898412466879a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 14:58:19 +0100 Subject: [PATCH 0268/1995] More TODOs --- .../src/lib/node/ledger/shell/vote_extensions.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index f38caf18bc..7ac2119bfc 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -214,10 +214,22 @@ mod extend_votes { } } - /// Takes an iterator over signed vote extensions, + /// Takes an iterator over vote extension instances, /// and returns another iterator. The latter yields /// valid vote extensions, or the reason why these /// are invalid, in the form of a [`EthEventsVextError`]. + // TODO: the error type we return should be some kind of + // enum like: + // + // ```ignore + // enum VoteExtensionError { + // EthEventsVext(EthEventsVextError), + // ValidatorSetUpdateVext(ValidatorSetUpdateVextError), + // } + // ``` + // + // the `vote_extensions` iterator should be over `VoteExtension` + // instances #[inline] pub fn validate_vote_extension_list( &self, @@ -238,6 +250,8 @@ mod extend_votes { /// Takes a list of signed vote extensions, /// and filters out invalid instances. + // TODO: the `vote_extensions` iterator should be over `VoteExtension` + // instances #[inline] pub fn filter_invalid_vote_extensions( &self, From a3d53b671910d05253b1610a6abdda2808279ef0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 15:03:01 +0100 Subject: [PATCH 0269/1995] VoteExtension type TODO --- shared/src/types/vote_extensions.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index b67d3bbe61..34f69b0064 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -1,3 +1,12 @@ //! This module contains types necessary for processing vote extensions. pub mod ethereum_events; + +// TODO: add a `VoteExtension` type +// +// ```ignore +// pub struct VoteExtension { +// ethereum_events: SignedEthEventsVext, +// validator_set_update: Option, +// } +// ``` From 2a93154f173ffaec29525e70ec56618327e5ece8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 2 Aug 2022 15:36:15 +0100 Subject: [PATCH 0270/1995] Misc TODOs --- .../lib/node/ledger/shell/prepare_proposal.rs | 3 +++ shared/src/types/vote_extensions.rs | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6056df6a57..83dce156c0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -166,6 +166,9 @@ mod prepare_block { /// Compresses a set of signed Ethereum events into a single /// [`EthEventsVextDigest`], whilst filtering invalid /// [`SignedEthEventsVext`] instances in the process + // TODO: rename this as `compress_vote_extensions`, and return + // a `VoteExtensionDigest`, which will contain both digests of + // ethereum events and validator set update vote extensions fn compress_ethereum_events( &self, vote_extensions: Vec, diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 34f69b0064..03a62ca754 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -6,7 +6,23 @@ pub mod ethereum_events; // // ```ignore // pub struct VoteExtension { -// ethereum_events: SignedEthEventsVext, -// validator_set_update: Option, +// pub ethereum_events: SignedEthEventsVext, +// pub validator_set_update: Option, // } // ``` + +// TODO: add a `VoteExtensionDigest` type; this will contain +// the values to be proposed, for a quorum of Ethereum events +// vote extensions, and a separate quorum of validator set update +// vote extensions +// +// ```ignore +// pub struct VoteExtensionDigest { +// pub ethereum_events: EthEventsVextDigest, +// pub validator_set_update: Option, +// } +// ``` +// +// from a `VoteExtensionDigest` we yield two signed `ProtocolTxType` values, +// one of `ProtocolTxType::EthereumEvents` and the other of +// `ProtocolTxType::ValidatorSetUpdate` From 3ec9b33717ee22ff6ba1e5e5335f995a50d0b82b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 09:10:55 +0100 Subject: [PATCH 0271/1995] Adjust docstring on build_vote_extensions_txs() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 83dce156c0..6bad20a2f1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -67,7 +67,9 @@ mod prepare_block { } /// Builds a batch of vote extension transactions, comprised of Ethereum - /// events and, optionally, a validator set update + /// events + // TODO: add `and, optionally, a validator set update` to the docstring, + // after validator set updates are implemented fn build_vote_extensions_txs( &mut self, local_last_commit: Option, From 5669978bbb98cd76dffc63f215ee89fadd2b5d96 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 09:42:57 +0100 Subject: [PATCH 0272/1995] Revert "Adjust msgs in Ethereum events error enum" This reverts commit 3ab47dc3d4e0fa7a7a84b502a240b32c6d65aea0. --- .../lib/node/ledger/shell/vote_extensions.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 7ac2119bfc..ec877cbef0 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -13,18 +13,13 @@ mod extend_votes { /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. #[derive(Error, Debug)] pub enum EthEventsVextError { - #[error( - "The Ethereum events vote extension was issued at block height 0." - )] + #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, - #[error( - "The Ethereum events vote extension has an unexpected block \ - height." - )] + #[error("The vote extension has an unexpected block height.")] UnexpectedBlockHeight, #[error( - "The Ethereum events vote extension contains duplicate or \ - non-sorted events." + "The vote extension contains duplicate or non-sorted Ethereum \ + events." )] HaveDupesOrNonSorted, #[error( @@ -32,10 +27,7 @@ mod extend_votes { could not be found in storage." )] PubKeyNotInStorage, - #[error( - "The signature of the vote extension containing the given \ - Ethereum events is invalid." - )] + #[error("The vote extension's signature is invalid.")] VerifySigFailed, } From c2ebb6cbf9d24613124e41b608b06c8f2f4865d1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 10:01:02 +0100 Subject: [PATCH 0273/1995] Rename EthEventsVext -> ethereum_events::Vext --- .../lib/node/ledger/shell/prepare_proposal.rs | 39 +++---- .../lib/node/ledger/shell/process_proposal.rs | 16 +-- .../lib/node/ledger/shell/vote_extensions.rs | 110 +++++++++--------- .../types/vote_extensions/ethereum_events.rs | 44 +++---- 4 files changed, 99 insertions(+), 110 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6bad20a2f1..5ff2aa8df5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -98,11 +98,11 @@ mod prepare_block { (Some(digest), _) => digest, _ => unreachable!( "Honest Namada validators will always sign \ - EthEventsVext instances, even if no Ethereum events \ - were observed at a given block height. In fact, a \ - quorum of signed empty EthEventsVext instances \ - commits the fact no events were observed by a \ - majority of validators. Likewise, a Tendermint \ + ethereum_events::Vext instances, even if no Ethereum \ + events were observed at a given block height. In \ + fact, a quorum of signed empty ethereum_events::Vext \ + instances commits the fact no events were observed \ + by a majority of validators. Likewise, a Tendermint \ quorum should never decide on a block including vote \ extensions reflecting less than or equal to 2/3 of \ the total stake. These scenarios are virtually \ @@ -167,7 +167,7 @@ mod prepare_block { /// Compresses a set of signed Ethereum events into a single /// [`EthEventsVextDigest`], whilst filtering invalid - /// [`SignedEthEventsVext`] instances in the process + /// [`Signed`] instances in the process // TODO: rename this as `compress_vote_extensions`, and return // a `VoteExtensionDigest`, which will contain both digests of // ethereum events and validator set update vote extensions @@ -292,16 +292,14 @@ mod prepare_block { use namada::ledger::pos::namada_proof_of_stake::types::{ VotingPower, WeightedValidator, }; - use namada::proto::SignedTxData; + use namada::proto::{Signed, SignedTxData}; use namada::types::address::xan; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::common; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; - use namada::types::vote_extensions::ethereum_events::{ - EthEventsVext, SignedEthEventsVext, - }; + use namada::types::vote_extensions::ethereum_events; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -335,9 +333,10 @@ mod prepare_block { ); } - /// Serialize a [`SignedEthEventsVext`] to an [`ExtendedVoteInfo`] + /// Serialize a [`Signed`] to an + /// [`ExtendedVoteInfo`] fn vote_extension_serialize( - vext: SignedEthEventsVext, + vext: Signed, ) -> ExtendedVoteInfo { ExtendedVoteInfo { vote_extension: vext.try_to_vec().unwrap(), @@ -348,7 +347,7 @@ mod prepare_block { /// Check if we are filtering out an invalid vote extension `vext` fn check_eth_events_filtering( shell: &mut TestShell, - vext: SignedEthEventsVext, + vext: Signed, ) { let votes = deserialize_vote_extensions(vec![vote_extension_serialize( @@ -376,7 +375,7 @@ mod prepare_block { let validator_addr = wallet::defaults::validator_address(); // generate a valid signature - let mut ext = EthEventsVext { + let mut ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], @@ -409,7 +408,7 @@ mod prepare_block { let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr, block_height: PRED_LAST_HEIGHT, ethereum_events: vec![], @@ -440,7 +439,7 @@ mod prepare_block { }; let signed_vote_extension = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![], @@ -473,7 +472,7 @@ mod prepare_block { }; let signed_vote_extension = { let ev = ethereum_event; - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ev.clone(), ev.clone(), ev], @@ -500,7 +499,7 @@ mod prepare_block { /// [`TxRecord`]. fn manually_assemble_digest( _protocol_key: &common::SecretKey, - ext: SignedEthEventsVext, + ext: Signed, last_height: BlockHeight, ) -> EthEventsVextDigest { let events = vec![MultiSignedEthEvent { @@ -552,7 +551,7 @@ mod prepare_block { transfers: vec![], }; let signed_vote_extension = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ethereum_event], @@ -667,7 +666,7 @@ mod prepare_block { transfers: vec![], }; let signed_vote_extension = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, ethereum_events: vec![ethereum_event], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 7b7edc1ed8..5d91df6c20 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -382,7 +382,7 @@ mod test_process_proposal { use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; use namada::types::vote_extensions::ethereum_events::{ - EthEventsVext, EthEventsVextDigest, MultiSignedEthEvent, + self, EthEventsVextDigest, MultiSignedEthEvent, }; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::RequestInitChain; @@ -412,9 +412,11 @@ mod test_process_proposal { let vote_extension_digest = { let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { - let ext = - EthEventsVext::empty(LAST_HEIGHT, validator_addr.clone()) - .sign(&protocol_key); + let ext = ethereum_events::Vext::empty( + LAST_HEIGHT, + validator_addr.clone(), + ) + .sign(&protocol_key); assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; @@ -483,7 +485,7 @@ mod test_process_proposal { }; let ext = { // generate a valid signature - let mut ext = EthEventsVext { + let mut ext = ethereum_events::Vext { validator_addr: addr.clone(), block_height: LAST_HEIGHT, ethereum_events: vec![event.clone()], @@ -530,7 +532,7 @@ mod test_process_proposal { transfers: vec![], }; let ext = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr: addr.clone(), block_height: PRED_LAST_HEIGHT, ethereum_events: vec![event.clone()], @@ -576,7 +578,7 @@ mod test_process_proposal { transfers: vec![], }; let ext = { - let ext = EthEventsVext { + let ext = ethereum_events::Vext { validator_addr: addr.clone(), block_height: LAST_HEIGHT, ethereum_events: vec![event.clone()], diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ec877cbef0..8cd86c1b78 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -2,17 +2,16 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; - use namada::types::vote_extensions::ethereum_events::{ - EthEventsVext, SignedEthEventsVext, - }; + use namada::proto::Signed; + use namada::types::vote_extensions::ethereum_events; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::queries::QueriesExt; use super::super::*; - /// The error yielded from [`Shell::validate_vote_ext_and_get_it_back`]. + /// The error yielded from validating faulty vote extensions in the shell #[derive(Error, Debug)] - pub enum EthEventsVextError { + pub enum VoteExtensionError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, #[error("The vote extension has an unexpected block height.")] @@ -46,7 +45,7 @@ mod extend_votes { .get_validator_address() .expect("only validators should receive this method call") .to_owned(); - let ext = EthEventsVext { + let ext = ethereum_events::Vext { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), validator_addr, @@ -73,9 +72,9 @@ mod extend_votes { // `namada::types::vote_extensions::VoteExtension`, // which contains an optional validator set update and // a set of ethereum events seen at the previous block height - if let Ok(signed) = - SignedEthEventsVext::try_from_slice(&req.vote_extension[..]) - { + if let Ok(signed) = Signed::::try_from_slice( + &req.vote_extension[..], + ) { response::VerifyVoteExtension { status: if self.validate_eth_events_vext( signed, @@ -118,7 +117,7 @@ mod extend_votes { #[inline] pub fn validate_eth_events_vext( &self, - ext: SignedEthEventsVext, + ext: Signed, last_height: BlockHeight, ) -> bool { self.validate_eth_events_vext_and_get_it_back(ext, last_height) @@ -130,11 +129,11 @@ mod extend_votes { /// is valid. pub fn validate_eth_events_vext_and_get_it_back( &self, - ext: SignedEthEventsVext, + ext: Signed, last_height: BlockHeight, ) -> std::result::Result< - (VotingPower, SignedEthEventsVext), - EthEventsVextError, + (VotingPower, Signed), + VoteExtensionError, > { if ext.data.block_height != last_height { let ext_height = ext.data.block_height; @@ -142,11 +141,11 @@ mod extend_votes { "Vote extension issued for a block height {ext_height} \ different from the expected height {last_height}" ); - return Err(EthEventsVextError::UnexpectedBlockHeight); + return Err(VoteExtensionError::UnexpectedBlockHeight); } if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); - return Err(EthEventsVextError::IssuedAtGenesis); + return Err(VoteExtensionError::IssuedAtGenesis); } // verify if we have any duplicate Ethereum events, // and if these are sorted in ascending order @@ -163,7 +162,7 @@ mod extend_votes { %validator, "Found duplicate or non-sorted Ethereum events in a vote extension from validator" ); - return Err(EthEventsVextError::HaveDupesOrNonSorted); + return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator let epoch = self.storage.block.pred_epochs.get_epoch(last_height); @@ -176,7 +175,7 @@ mod extend_votes { %validator, "Could not get public key from Storage for validator" ); - EthEventsVextError::PubKeyNotInStorage + VoteExtensionError::PubKeyNotInStorage })?; // verify the signature of the vote extension ext.verify(&pk) @@ -186,7 +185,7 @@ mod extend_votes { %validator, "Failed to verify the signature of a vote extension issued by validator" ); - EthEventsVextError::VerifySigFailed + VoteExtensionError::VerifySigFailed }) .map(|_| (voting_power, ext)) } @@ -209,27 +208,18 @@ mod extend_votes { /// Takes an iterator over vote extension instances, /// and returns another iterator. The latter yields /// valid vote extensions, or the reason why these - /// are invalid, in the form of a [`EthEventsVextError`]. - // TODO: the error type we return should be some kind of - // enum like: - // - // ```ignore - // enum VoteExtensionError { - // EthEventsVext(EthEventsVextError), - // ValidatorSetUpdateVext(ValidatorSetUpdateVextError), - // } - // ``` - // - // the `vote_extensions` iterator should be over `VoteExtension` - // instances + /// are invalid, in the form of a [`VoteExtensionError`]. + // TODO: the `vote_extensions` iterator should be over `VoteExtension` + // instances, I guess? to be determined in the next PR #[inline] pub fn validate_vote_extension_list( &self, - vote_extensions: impl IntoIterator + 'static, + vote_extensions: impl IntoIterator> + + 'static, ) -> impl Iterator< Item = std::result::Result< - (VotingPower, SignedEthEventsVext), - EthEventsVextError, + (VotingPower, Signed), + VoteExtensionError, >, > + '_ { vote_extensions.into_iter().map(|vote_extension| { @@ -243,12 +233,13 @@ mod extend_votes { /// Takes a list of signed vote extensions, /// and filters out invalid instances. // TODO: the `vote_extensions` iterator should be over `VoteExtension` - // instances + // instances, I guess? to be determined in the next PR #[inline] pub fn filter_invalid_vote_extensions( &self, - vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator + '_ + vote_extensions: impl IntoIterator> + + 'static, + ) -> impl Iterator)> + '_ { self.validate_vote_extension_list(vote_extensions) .filter_map(|ext| ext.ok()) @@ -256,25 +247,28 @@ mod extend_votes { } /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// ones we could deserialize to [`SignedEthEventsVext`] instances. + /// ones we could deserialize to [`Signed`] + /// instances. // TODO: we need to return an iterator over instances of `VoteExtension`, // which contain both the ethereum events vote extensions and validator // set update vote extensions pub fn deserialize_vote_extensions( vote_extensions: Vec, - ) -> impl Iterator + 'static { + ) -> impl Iterator> + 'static { vote_extensions.into_iter().filter_map(|vote| { - SignedEthEventsVext::try_from_slice(&vote.vote_extension[..]) - .map_err(|err| { - tracing::error!( - ?err, - // TODO: change this error message, probably, such that - // it mentions Ethereum events rather than vote - // extensions - "Failed to deserialize signed vote extension", - ); - }) - .ok() + Signed::::try_from_slice( + &vote.vote_extension[..], + ) + .map_err(|err| { + tracing::error!( + ?err, + // TODO: change this error message, probably, such that + // it mentions Ethereum events rather than vote + // extensions + "Failed to deserialize signed vote extension", + ); + }) + .ok() }) } @@ -285,16 +279,16 @@ mod extend_votes { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::proto::Signed; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; use namada::types::key::*; use namada::types::storage::{BlockHeight, Epoch}; - use namada::types::vote_extensions::ethereum_events::EthEventsVext; + use namada::types::vote_extensions::ethereum_events; use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; - use super::SignedEthEventsVext; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; @@ -372,7 +366,7 @@ mod extend_votes { oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); let vote_extension = - ::try_from_slice( + as BorshDeserialize>::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], ) .expect("Test failed"); @@ -413,7 +407,7 @@ mod extend_votes { .get_validator_address() .expect("Test failed") .clone(); - let vote_ext = EthEventsVext { + let vote_ext = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -459,7 +453,7 @@ mod extend_votes { .expect("Test failed") .clone(); let signed_height = shell.storage.last_height + 1; - let vote_ext = EthEventsVext { + let vote_ext = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -513,13 +507,13 @@ mod extend_votes { assert!(shell.validate_eth_events_vext(vote_ext, signed_height)); } - /// Test that an [`EthEventsVext`] that incorrectly labels what block it - /// was included on in a vote extension is rejected + /// Test that an [`ethereum_events::Vext`] that incorrectly labels what + /// block it was included on in a vote extension is rejected #[test] fn reject_incorrect_block_number() { let (shell, _, _) = setup(); let address = shell.mode.get_validator_address().unwrap().clone(); - let vote_ext = EthEventsVext { + let vote_ext = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 17dd08e496..fd7e9cb302 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -11,18 +11,15 @@ use crate::types::ethereum_events::EthereumEvent; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; -/// An [`EthEventsVext`] instance signed by a Namada validator. -pub type SignedEthEventsVext = Signed; - -/// Represents a set of `EthereumEvent` instances +/// Represents a set of [`EthereumEvent`] instances /// seen by some validator. /// /// This struct will be created and signed over by each /// active validator, to be included as a vote extension at the end of a /// Tendermint PreCommit phase. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct EthEventsVext { - /// The block height for which this [`EthEventsVext`] was made. +pub struct Vext { + /// The block height for which this [`Vext`] was made. pub block_height: BlockHeight, /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator @@ -33,8 +30,8 @@ pub struct EthEventsVext { pub ethereum_events: Vec, } -impl EthEventsVext { - /// Creates a [`EthEventsVext`] without any Ethereum events. +impl Vext { + /// Creates a [`Vext`] without any Ethereum events. pub fn empty(block_height: BlockHeight, validator_addr: Address) -> Self { Self { block_height, @@ -43,9 +40,9 @@ impl EthEventsVext { } } - /// Sign a [`EthEventsVext`] with a validator's `signing_key`, + /// Sign a [`Vext`] with a validator's `signing_key`, /// and return the signed data. - pub fn sign(self, signing_key: &common::SecretKey) -> SignedEthEventsVext { + pub fn sign(self, signing_key: &common::SecretKey) -> Signed { Signed::new(signing_key, self) } } @@ -62,30 +59,27 @@ pub struct MultiSignedEthEvent { pub signers: HashSet
, } -/// Compresses a set of signed [`EthEventsVext`] instances, to save +/// Compresses a set of signed [`Vext`] instances, to save /// space on a block. #[derive( Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct EthEventsVextDigest { - /// The signatures and signing address of each [`EthEventsVext`] + /// The signatures and signing address of each [`Vext`] pub signatures: HashMap, /// The events that were reported pub events: Vec, } impl EthEventsVextDigest { - /// Decompresses a set of signed [`EthEventsVext`] instances. - pub fn decompress( - self, - last_height: BlockHeight, - ) -> Vec { + /// Decompresses a set of signed [`Vext`] instances. + pub fn decompress(self, last_height: BlockHeight) -> Vec> { let EthEventsVextDigest { signatures, events } = self; let mut extensions = vec![]; for (addr, sig) in signatures.into_iter() { - let mut ext = EthEventsVext::empty(last_height, addr.clone()); + let mut ext = Vext::empty(last_height, addr.clone()); for event in events.iter() { if event.signers.contains(&addr) { @@ -142,7 +136,7 @@ mod tests { /// Test decompression of a set of Ethereum events #[test] fn test_decompress_ethereum_events() { - // we need to construct a `Vec` + // we need to construct a `Vec>` let sk_1 = key::testing::keypair_1(); let sk_2 = key::testing::keypair_2(); @@ -160,8 +154,8 @@ mod tests { let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); - let ext = |validator: Address| -> EthEventsVext { - let mut ext = EthEventsVext::empty(last_block_height, validator); + let ext = |validator: Address| -> Vext { + let mut ext = Vext::empty(last_block_height, validator); ext.ethereum_events.push(ev_1.clone()); ext.ethereum_events.push(ev_2.clone()); @@ -177,7 +171,7 @@ mod tests { let ext = vec![ext_1, ext_2]; - // we have the `SignedEthEventsVext` instances we need, + // we have the `Signed` instances we need, // let us now compress them into a single `EthEventsVextDigest` let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), @@ -205,13 +199,13 @@ mod tests { let digest = EthEventsVextDigest { events, signatures }; // finally, decompress the `EthEventsVextDigest` back into a - // `Vec` + // `Vec>` let mut decompressed = digest .decompress(last_block_height) .into_iter() - .collect::>(); + .collect::>>(); - // decompressing yields an arbitrary ordering of `EthEventsVext` + // decompressing yields an arbitrary ordering of `Vext` // instances, which is fine if decompressed[0].data.validator_addr != ext[0].data.validator_addr { decompressed.swap(0, 1); From 60fe2210de0a51a983afcc3a54aa92573f48a2ac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 10:07:10 +0100 Subject: [PATCH 0274/1995] Rename EthEventsVextDigest -> ethereum_events::VextDigest --- apps/src/lib/node/ledger/protocol/mod.rs | 7 ++++--- .../lib/node/ledger/shell/prepare_proposal.rs | 14 +++++++------- .../lib/node/ledger/shell/process_proposal.rs | 18 +++++++++--------- shared/src/types/transaction/protocol.rs | 4 ++-- .../types/vote_extensions/ethereum_events.rs | 12 ++++++------ 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e6ff3866f1..0732799c45 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -17,7 +17,7 @@ use namada::types::address::{Address, InternalAddress}; use namada::types::storage; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; -use namada::types::vote_extensions::ethereum_events::EthEventsVextDigest; +use namada::types::vote_extensions::ethereum_events; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; @@ -159,8 +159,9 @@ where } TxType::Protocol(ProtocolTx { tx: - ProtocolTxType::EthereumEvents(EthEventsVextDigest { - events, .. + ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { + events, + .. }), .. }) if !events.is_empty() => { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5ff2aa8df5..217e19fac9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,7 +6,7 @@ mod prepare_block { use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::ethereum_events::{ - EthEventsVextDigest, MultiSignedEthEvent, + self, MultiSignedEthEvent, }; use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::{ @@ -166,7 +166,7 @@ mod prepare_block { } /// Compresses a set of signed Ethereum events into a single - /// [`EthEventsVextDigest`], whilst filtering invalid + /// [`ethereum_events::VextDigest`], whilst filtering invalid /// [`Signed`] instances in the process // TODO: rename this as `compress_vote_extensions`, and return // a `VoteExtensionDigest`, which will contain both digests of @@ -174,7 +174,7 @@ mod prepare_block { fn compress_ethereum_events( &self, vote_extensions: Vec, - ) -> Option { + ) -> Option { let events_epoch = self .storage .block @@ -227,7 +227,7 @@ mod prepare_block { ?sig, ?validator_addr, "Overwrote old signature from validator while \ - constructing EthEventsVextDigest" + constructing ethereum_events::VextDigest" ); } } @@ -245,7 +245,7 @@ mod prepare_block { .map(|(event, signers)| MultiSignedEthEvent { event, signers }) .collect(); - Some(EthEventsVextDigest { events, signatures }) + Some(ethereum_events::VextDigest { events, signatures }) } } @@ -501,7 +501,7 @@ mod prepare_block { _protocol_key: &common::SecretKey, ext: Signed, last_height: BlockHeight, - ) -> EthEventsVextDigest { + ) -> ethereum_events::VextDigest { let events = vec![MultiSignedEthEvent { event: ext.data.ethereum_events[0].clone(), signers: { @@ -517,7 +517,7 @@ mod prepare_block { }; let vote_extension_digest = - EthEventsVextDigest { events, signatures }; + ethereum_events::VextDigest { events, signatures }; assert_eq!( vec![ext], diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5d91df6c20..9f6af6fd88 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -48,7 +48,7 @@ where }) .collect(); - // We should not have more than one `EthEventsVextDigest` in + // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. let too_many_vext_digests = vote_ext_digest_num > 1; @@ -382,7 +382,7 @@ mod test_process_proposal { use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; use namada::types::vote_extensions::ethereum_events::{ - self, EthEventsVextDigest, MultiSignedEthEvent, + self, MultiSignedEthEvent, }; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::RequestInitChain; @@ -401,8 +401,8 @@ mod test_process_proposal { }; use crate::wallet; - /// Test that if a proposal contains more than one `EthEventsVextDigest`, - /// we reject it. + /// Test that if a proposal contains more than one + /// `ethereum_events::VextDigest`, we reject it. #[test] fn test_more_than_one_vext_digest_rejected() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); @@ -421,7 +421,7 @@ mod test_process_proposal { ext }; // Ethereum events digest with no observed events - EthEventsVextDigest { + ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); s.insert(validator_addr, signed_vote_extension.sig); @@ -445,7 +445,7 @@ mod test_process_proposal { fn check_rejected_digest( shell: &mut TestShell, - vote_extension_digest: EthEventsVextDigest, + vote_extension_digest: ethereum_events::VextDigest, protocol_key: common::SecretKey, ) { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) @@ -497,7 +497,7 @@ mod test_process_proposal { ext.sig = test_utils::invalidate_signature(ext.sig); ext }; - EthEventsVextDigest { + ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); @@ -541,7 +541,7 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - EthEventsVextDigest { + ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); @@ -587,7 +587,7 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - EthEventsVextDigest { + ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr.clone(), ext.sig); diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index d09dfaec99..9c70b5b078 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -35,7 +35,7 @@ mod protocol_txs { use crate::proto::Tx; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; - use crate::types::vote_extensions::ethereum_events::EthEventsVextDigest; + use crate::types::vote_extensions::ethereum_events; const TX_NEW_DKG_KP_WASM: &str = "tx_update_dkg_session_keypair.wasm"; @@ -78,7 +78,7 @@ mod protocol_txs { /// Tx requesting a new DKG session keypair NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions - EthereumEvents(EthEventsVextDigest), + EthereumEvents(ethereum_events::VextDigest), } impl ProtocolTxType { diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index fd7e9cb302..b0edb0ec28 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -64,17 +64,17 @@ pub struct MultiSignedEthEvent { #[derive( Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] -pub struct EthEventsVextDigest { +pub struct VextDigest { /// The signatures and signing address of each [`Vext`] pub signatures: HashMap, /// The events that were reported pub events: Vec, } -impl EthEventsVextDigest { +impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, last_height: BlockHeight) -> Vec> { - let EthEventsVextDigest { signatures, events } = self; + let VextDigest { signatures, events } = self; let mut extensions = vec![]; @@ -172,7 +172,7 @@ mod tests { let ext = vec![ext_1, ext_2]; // we have the `Signed` instances we need, - // let us now compress them into a single `EthEventsVextDigest` + // let us now compress them into a single `VextDigest` let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), (validator_2.clone(), ext[1].sig.clone()), @@ -196,9 +196,9 @@ mod tests { }, ]; - let digest = EthEventsVextDigest { events, signatures }; + let digest = VextDigest { events, signatures }; - // finally, decompress the `EthEventsVextDigest` back into a + // finally, decompress the `VextDigest` back into a // `Vec>` let mut decompressed = digest .decompress(last_block_height) From 8eaa406d9d4b8a550707b55d61a00ea030a5f0da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 10:14:40 +0100 Subject: [PATCH 0275/1995] Fix types in the TODO items --- shared/src/types/vote_extensions.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 03a62ca754..7737017aee 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -6,8 +6,8 @@ pub mod ethereum_events; // // ```ignore // pub struct VoteExtension { -// pub ethereum_events: SignedEthEventsVext, -// pub validator_set_update: Option, +// pub ethereum_events: Signed, +// pub validator_set_update: Option, // } // ``` @@ -18,8 +18,8 @@ pub mod ethereum_events; // // ```ignore // pub struct VoteExtensionDigest { -// pub ethereum_events: EthEventsVextDigest, -// pub validator_set_update: Option, +// pub ethereum_events: ethereum_events::VextDigest, +// pub validator_set_update: Option, // } // ``` // From d6d04334c2827b10a5178b160102a4d0101c86fa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 15:40:32 +0100 Subject: [PATCH 0276/1995] Add validator set update vext mod --- shared/src/types/vote_extensions.rs | 1 + shared/src/types/vote_extensions/validator_set_update.rs | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 shared/src/types/vote_extensions/validator_set_update.rs diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 7737017aee..0637292478 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -1,6 +1,7 @@ //! This module contains types necessary for processing vote extensions. pub mod ethereum_events; +pub mod validator_set_update; // TODO: add a `VoteExtension` type // diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs new file mode 100644 index 0000000000..5a96ecde32 --- /dev/null +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -0,0 +1,2 @@ +//! Contains types necessary for processing validator set updates +//! in vote extensions. From 8eb6e03be8af2bd1a097b0b09817e67fef37f504 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 16:45:38 +0100 Subject: [PATCH 0277/1995] Add a TODO --- .../types/vote_extensions/validator_set_update.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 5a96ecde32..b373a83fc1 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,2 +1,16 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. + +// TODO: finish signed vote extension +// ```ignore +// struct Vext { +// ...? +// } +// struct SignedVext { +// signature: EthereumSignature, +// data: Vext, +// } +// ``` +// we derive a keccak hash from the `Vext` data +// in `SignedVext`, which we can sign with an +// Ethereum key. that is the content of `signature` From 2dec131a2ba26e3b74737043b498e40c594c48fe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 09:51:59 +0100 Subject: [PATCH 0278/1995] Add tiny-keccak as a dep --- Cargo.lock | 1 + shared/Cargo.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d822a7abd8..6a9b581657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4243,6 +4243,7 @@ dependencies = [ "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", + "tiny-keccak", "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b851d92413..ca4dfeb1f8 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -42,6 +42,7 @@ ABCI-plus-plus = [ "ibc-proto", "tendermint", "tendermint-proto", + "tiny-keccak", ] testing = [ "proptest", @@ -111,6 +112,7 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "9 tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} thiserror = "1.0.30" +tiny-keccak = {version = "2.0.2", optional = true} tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} From a30cf4f8c3da90a4ac3483584dc622443973c240 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 09:52:23 +0100 Subject: [PATCH 0279/1995] Validator set update vote extension --- .../vote_extensions/validator_set_update.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b373a83fc1..cecba21fbe 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,6 +1,14 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. +use std::collections::HashMap; + +use tiny_keccak::{Hasher, Keccak}; +use ethabi::ethereum_types as ethereum; + +use crate::types::ethereum_events::KeccakHash; +use crate::ledger::pos::types::{Epoch, VotingPower}; + // TODO: finish signed vote extension // ```ignore // struct Vext { @@ -14,3 +22,32 @@ // we derive a keccak hash from the `Vext` data // in `SignedVext`, which we can sign with an // Ethereum key. that is the content of `signature` + +/// Represents a validator set update, for some new [`Epoch`]. +pub struct Vext { + /// The addresses of the validators in the new [`Epoch`], + /// and their respective voting power. + /// + /// When signing a [`Vext`], this [`HashMap`] is converted + /// into two arrays: one for its keys, and another for its + /// values. The arrays are sorted in descending order based + /// on the voting power of each validator. + pub voting_powers: HashMap, + /// The new [`Epoch`]. + /// + /// Since this is a monotonically growing sequence number, + /// it is signed together with the rest of the data to + /// prevent replay attacks on validator set updates. + pub epoch: Epoch, +} + +impl Vext { + /// TODO + pub fn get_keccak_hash(&self) -> KeccakHash { + let state = Keccak::v256(); + let mut output = [0u8; 32]; + // state.update(&[...]); + state.finalize(&mut output); + todo!() + } +} From e0607844e1482d8c1003f4db0977c5d22bee09cd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 10:19:41 +0100 Subject: [PATCH 0280/1995] Return a list of validator addresses and voting powers --- .../vote_extensions/validator_set_update.rs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index cecba21fbe..4718d32547 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -3,11 +3,11 @@ use std::collections::HashMap; -use tiny_keccak::{Hasher, Keccak}; use ethabi::ethereum_types as ethereum; +use tiny_keccak::{Hasher, Keccak}; -use crate::types::ethereum_events::KeccakHash; use crate::ledger::pos::types::{Epoch, VotingPower}; +use crate::types::ethereum_events::KeccakHash; // TODO: finish signed vote extension // ```ignore @@ -50,4 +50,29 @@ impl Vext { state.finalize(&mut output); todo!() } + + /// Returns the list of Ethereum validator addresses and their respective + /// voting power. + #[allow(dead_code)] + fn get_validators_and_addresses( + &self, + ) -> (Vec, Vec) { + // get addresses and voting powers all into one vec + let mut unsorted: Vec<_> = self.voting_powers.iter().collect(); + + // sort it by voting power, in descending order + unsorted.sort_by(|&(_, ref power_1), &(_, ref power_2)| { + power_2.cmp(power_1) + }); + + // split the vec into two + unsorted + .into_iter() + .map(|(&addr, &voting_power)| { + let voting_power: u64 = voting_power.into(); + let voting_power: ethereum::U256 = voting_power.into(); + (addr, voting_power) + }) + .unzip() + } } From a46cce1e0171481d1566c918e1f88a66f34e291b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 11:00:41 +0100 Subject: [PATCH 0281/1995] Normalize the voting powers to 2^32 --- .../vote_extensions/validator_set_update.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 4718d32547..8ca470e395 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use ethabi::ethereum_types as ethereum; +use num_rational::Ratio; use tiny_keccak::{Hasher, Keccak}; use crate::ledger::pos::types::{Epoch, VotingPower}; @@ -65,11 +66,26 @@ impl Vext { power_2.cmp(power_1) }); + let sorted = unsorted; + let total_voting_power: u64 = sorted + .iter() + .map(|&(_, &voting_power)| u64::from(voting_power)) + .sum(); + // split the vec into two - unsorted + sorted .into_iter() .map(|(&addr, &voting_power)| { let voting_power: u64 = voting_power.into(); + + // normalize the voting power + // https://github.com/anoma/ethereum-bridge/blob/main/test/utils/utilities.js#L29 + const NORMALIZED_VOTING_POWER: u64 = 1 << 32; + + let voting_power = Ratio::new(voting_power, total_voting_power) + * NORMALIZED_VOTING_POWER; + let voting_power = voting_power.round().to_integer(); + let voting_power: ethereum::U256 = voting_power.into(); (addr, voting_power) }) From cba19d5be65b2586a7ee1d47838082196a243cf8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 14:48:26 +0100 Subject: [PATCH 0282/1995] Fix Ethereum oracle spawn --- apps/src/lib/node/ledger/mod.rs | 44 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d603847c4e..6e686b892a 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -315,17 +315,19 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { config.tendermint.tendermint_mode, TendermintMode::Validator ) { - let local = tokio::task::LocalSet::new(); // boot up the ethereum node process and wait for it to finish syncing let (eth_sender, eth_receiver) = unbounded_channel(); let url = ethereum_url.clone(); - let (ethereum_node, abort_sender) = local - .run_until(async move { - EthereumNode::new(&url) - .await - .expect("Unable to start the Ethereum fullnode") - }) - .await; + let (ethereum_node, abort_sender) = { + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + EthereumNode::new(&url) + .await + .expect("Unable to start the Ethereum fullnode") + }) + .await + }; // Start Ethereum fullnode // Channel for signalling shut down to Tendermint process @@ -350,9 +352,16 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { res }); - let oracle = local.spawn_local(async move { - ethereum_node::run_oracle(ðereum_url, eth_sender, abort_sender) + let oracle = tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async move { + ethereum_node::run_oracle( + ðereum_url, + eth_sender, + abort_sender, + ) .await + }); }); // Shutdown ethereum_node via a message to ensure that the child process @@ -372,7 +381,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { eth_abort_resp_send, eth_abort_resp_recv, )), - Some((local, oracle, eth_receiver)), + Some((oracle, eth_receiver)), Some(( tokio::spawn(async move { // Construct a service for broadcasting protocol txs from @@ -397,11 +406,11 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { (None, None, None) }; - let (rt, oracle_proc, eth_receiver) = match oracle { - Some((rt, oracle_proc, oracle_channel)) => { - (Some(rt), Some(oracle_proc), Some(oracle_channel)) + let (oracle_proc, eth_receiver) = match oracle { + Some((oracle_proc, oracle_channel)) => { + (Some(oracle_proc), Some(oracle_channel)) } - None => (None, None, None), + None => (None, None), }; // Channel for signalling shut down to Tendermint process @@ -501,10 +510,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { eth_abort_resp_send, eth_abort_resp_recv, )), - Some(local), Some(oracle), Some((broadcaster, bc_abort_send)), - ) = (ethereum_node, rt, oracle_proc, broadcaster) + ) = (ethereum_node, oracle_proc, broadcaster) { // Ask to shutdown tendermint node cleanly. Ignore error, which can // happen if the tendermint_node task has already finished. @@ -525,7 +533,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::try_join!( tendermint_node, ethereum_node, - local.run_until(async move { oracle.await }), + oracle, abci, broadcaster ) From 22c025c15b29afd53a5b02c95073c229aa922c2c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 3 Aug 2022 15:18:14 +0100 Subject: [PATCH 0283/1995] Fix LocalSet panic --- apps/src/lib/node/ledger/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 6e686b892a..b9ef6a658b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -355,12 +355,17 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let oracle = tokio::task::spawn_blocking(move || { let rt = tokio::runtime::Handle::current(); rt.block_on(async move { - ethereum_node::run_oracle( - ðereum_url, - eth_sender, - abort_sender, - ) - .await + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + ethereum_node::run_oracle( + ðereum_url, + eth_sender, + abort_sender, + ) + .await + }) + .await }); }); From 2273c687a6603a9f3a228de66d68899e6e407087 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 16:26:43 +0100 Subject: [PATCH 0284/1995] Put the LocalSet.run_until in EthereumNode::new() Add a comment indicating it's because we're using web30 --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 93 +++++++++++-------- apps/src/lib/node/ledger/mod.rs | 13 +-- 2 files changed, 57 insertions(+), 49 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 6b6c0df9c4..39b6d282af 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -68,6 +68,7 @@ pub mod eth_fullnode { use tokio::process::{Child, Command}; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{channel, Receiver, Sender}; + use tokio::task::LocalSet; use web30::client::Web3; use super::{Error, Result}; @@ -110,46 +111,60 @@ pub mod eth_fullnode { /// It then starts the process and waits for it to finish /// syncing. pub async fn new(url: &str) -> Result<(EthereumNode, Sender<()>)> { - // the geth fullnode process - let network = get_eth_network()?; - let args = match &network { - Some(network) => { - vec!["--syncmode", "snap", network.as_str(), "--http"] - } - None => vec!["--syncmode", "snap", "--http"], - }; - let ethereum_node = Command::new("geth") - .args(&args) - .kill_on_drop(true) - .spawn() - .map_err(Error::StartUp)?; - tracing::info!("Ethereum fullnode started"); - - // it takes a brief amount of time to open up the websocket on - // geth's end - const CLIENT_TIMEOUT: Duration = Duration::from_secs(5); - let client = Web3::new(url, CLIENT_TIMEOUT); - - const SLEEP_DUR: Duration = Duration::from_secs(1); - loop { - if let Ok(false) = client.eth_syncing().await { - tracing::info!("Finished syncing"); - break; - } - if let Err(error) = client.eth_syncing().await { - // This is very noisy and usually not interesting. - // Still can be very useful - tracing::debug!(?error, "Couldn't check Geth sync status"); - } - tokio::time::sleep(SLEEP_DUR).await; - } + // we have to start the node in a [`LocalSet`] due to the web30 + // crate + LocalSet::new() + .run_until(async move { + // the geth fullnode process + let network = get_eth_network()?; + let args = match &network { + Some(network) => { + vec![ + "--syncmode", + "snap", + network.as_str(), + "--http", + ] + } + None => vec!["--syncmode", "snap", "--http"], + }; + let ethereum_node = Command::new("geth") + .args(&args) + .kill_on_drop(true) + .spawn() + .map_err(Error::StartUp)?; + tracing::info!("Ethereum fullnode started"); + + // it takes a brief amount of time to open up the websocket + // on geth's end + const CLIENT_TIMEOUT: Duration = Duration::from_secs(5); + let client = Web3::new(url, CLIENT_TIMEOUT); + + const SLEEP_DUR: Duration = Duration::from_secs(1); + loop { + if let Ok(false) = client.eth_syncing().await { + tracing::info!("Finished syncing"); + break; + } + if let Err(error) = client.eth_syncing().await { + // This is very noisy and usually not interesting. + // Still can be very useful + tracing::debug!( + ?error, + "Couldn't check Geth sync status" + ); + } + tokio::time::sleep(SLEEP_DUR).await; + } - let (abort_sender, receiver) = channel(); - let node = Self { - process: ethereum_node, - abort_recv: receiver, - }; - Ok((node, abort_sender)) + let (abort_sender, receiver) = channel(); + let node = Self { + process: ethereum_node, + abort_recv: receiver, + }; + Ok((node, abort_sender)) + }) + .await } /// Wait for the process to finish or an abort message was diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b9ef6a658b..6ed9c4d5da 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -318,16 +318,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // boot up the ethereum node process and wait for it to finish syncing let (eth_sender, eth_receiver) = unbounded_channel(); let url = ethereum_url.clone(); - let (ethereum_node, abort_sender) = { - let local = tokio::task::LocalSet::new(); - local - .run_until(async move { - EthereumNode::new(&url) - .await - .expect("Unable to start the Ethereum fullnode") - }) - .await - }; + let (ethereum_node, abort_sender) = EthereumNode::new(&url) + .await + .expect("Unable to start the Ethereum fullnode"); // Start Ethereum fullnode // Channel for signalling shut down to Tendermint process From 78217e37a245872b47abde3229774467197472a4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 16:32:44 +0100 Subject: [PATCH 0285/1995] Log when Ethereum event oracle starts/stops --- apps/src/lib/node/ledger/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 6ed9c4d5da..9a887c8c29 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -351,12 +351,16 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let local = tokio::task::LocalSet::new(); local .run_until(async move { + tracing::info!("Ethereum event oracle is starting"); ethereum_node::run_oracle( ðereum_url, eth_sender, abort_sender, ) - .await + .await; + tracing::info!( + "Ethereum event oracle is no longer running" + ); }) .await }); From 211d21e42419438f8da371a935c55d4e2a0ba9d1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 16:46:06 +0100 Subject: [PATCH 0286/1995] Move tokio hackery to oracle.rs --- .../lib/node/ledger/ethereum_node/oracle.rs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index bbd28e7adb..68d014515c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -88,12 +88,29 @@ pub mod oracle_process { /// Set up an Oracle and run the process where the Oracle /// processes and forwards Ethereum events to the ledger pub async fn run_oracle( - url: &str, + url: impl AsRef, sender: UnboundedSender, abort_sender: Sender<()>, ) { - let oracle = Oracle::new(url, sender, abort_sender); - run_oracle_aux(oracle).await; + let url = url.as_ref().to_owned(); + tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async move { + let local = tokio::task::LocalSet::new(); + local + .run_until(async move { + tracing::info!("Ethereum event oracle is starting"); + + let oracle = Oracle::new(&url, sender, abort_sender); + run_oracle_aux(oracle).await; + + tracing::info!( + "Ethereum event oracle is no longer running" + ); + }) + .await + }); + }); } /// Given an oracle, watch for new Ethereum events, processing From 64299b1243eb5bd34dfe4d3d2affc8189ecaeac0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 16:59:18 +0100 Subject: [PATCH 0287/1995] Make run_oracle sync and use it only once --- .../lib/node/ledger/ethereum_node/oracle.rs | 6 ++--- apps/src/lib/node/ledger/mod.rs | 22 ++----------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 68d014515c..9a2cd2e1da 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -87,11 +87,11 @@ pub mod oracle_process { /// Set up an Oracle and run the process where the Oracle /// processes and forwards Ethereum events to the ledger - pub async fn run_oracle( + pub fn run_oracle( url: impl AsRef, sender: UnboundedSender, abort_sender: Sender<()>, - ) { + ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); tokio::task::spawn_blocking(move || { let rt = tokio::runtime::Handle::current(); @@ -110,7 +110,7 @@ pub mod oracle_process { }) .await }); - }); + }) } /// Given an oracle, watch for new Ethereum events, processing diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 9a887c8c29..d9f2e7321d 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -345,26 +345,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { res }); - let oracle = tokio::task::spawn_blocking(move || { - let rt = tokio::runtime::Handle::current(); - rt.block_on(async move { - let local = tokio::task::LocalSet::new(); - local - .run_until(async move { - tracing::info!("Ethereum event oracle is starting"); - ethereum_node::run_oracle( - ðereum_url, - eth_sender, - abort_sender, - ) - .await; - tracing::info!( - "Ethereum event oracle is no longer running" - ); - }) - .await - }); - }); + let oracle = + ethereum_node::run_oracle(ðereum_url, eth_sender, abort_sender); // Shutdown ethereum_node via a message to ensure that the child process // is properly cleaned-up. From 5c0cfb310a11abe7ecd87a0b645cf513d742b9c9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 17:02:23 +0100 Subject: [PATCH 0288/1995] Add comment re. web30 --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 9a2cd2e1da..144f89a7af 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,6 +7,7 @@ pub mod oracle_process { use num256::Uint256; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; + use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -93,11 +94,12 @@ pub mod oracle_process { abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); + // we have to run the oracle in a [`LocalSet`] due to the web30 + // crate tokio::task::spawn_blocking(move || { let rt = tokio::runtime::Handle::current(); rt.block_on(async move { - let local = tokio::task::LocalSet::new(); - local + LocalSet::new() .run_until(async move { tracing::info!("Ethereum event oracle is starting"); From 8b3092d3f9be82c42567ad997cd6ef174246cf68 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 17:10:26 +0100 Subject: [PATCH 0289/1995] Work when namada_apps/eth-fullnode is disabled --- .../node/ledger/ethereum_node/test_tools.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index ca7c61a300..d207591796 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -30,17 +30,35 @@ pub mod mock_oracle { use namada::types::ethereum_events::EthereumEvent; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; + use tokio::task::LocalSet; - pub async fn run_oracle( + pub fn run_oracle( _: &str, _: UnboundedSender, abort: Sender<()>, - ) { - loop { - if abort.is_closed() { - return; - } - } + ) -> tokio::task::JoinHandle<()> { + tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async move { + LocalSet::new() + .run_until(async move { + tracing::info!( + "Mock Ethereum event oracle is starting" + ); + + loop { + if abort.is_closed() { + break; + } + } + + tracing::info!( + "Mock Ethereum event oracle is no longer running" + ); + }) + .await + }); + }) } } From 1c3009295a44ec11adca8b8d4f9c9bb160433cf6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 3 Aug 2022 17:15:13 +0100 Subject: [PATCH 0290/1995] Make test run_oracle signature match real oracle --- apps/src/lib/node/ledger/ethereum_node/test_tools.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index d207591796..8c75e5b273 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -33,7 +33,7 @@ pub mod mock_oracle { use tokio::task::LocalSet; pub fn run_oracle( - _: &str, + _: impl AsRef, _: UnboundedSender, abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { From 1bc70a653e6b0263fd26da5c5a764378634be238 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 4 Aug 2022 09:48:35 +0100 Subject: [PATCH 0291/1995] Pass String at `ethereum_node::run_oracle` call site --- apps/src/lib/node/ledger/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d9f2e7321d..9d9cbbfe8e 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -346,7 +346,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }); let oracle = - ethereum_node::run_oracle(ðereum_url, eth_sender, abort_sender); + ethereum_node::run_oracle(ethereum_url, eth_sender, abort_sender); // Shutdown ethereum_node via a message to ensure that the child process // is properly cleaned-up. From dd49b6d3813fb92e7b940f03b2150b781a6c70ee Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 4 Aug 2022 10:28:30 +0100 Subject: [PATCH 0292/1995] Use poll_fn in mock oracle --- .../node/ledger/ethereum_node/test_tools.rs | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 8c75e5b273..3115e4cda6 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -28,36 +28,21 @@ pub mod mock_eth_fullnode { pub mod mock_oracle { use namada::types::ethereum_events::EthereumEvent; + use tokio::macros::support::poll_fn; use tokio::sync::mpsc::UnboundedSender; use tokio::sync::oneshot::Sender; - use tokio::task::LocalSet; pub fn run_oracle( _: impl AsRef, _: UnboundedSender, - abort: Sender<()>, + mut abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { - tokio::task::spawn_blocking(move || { - let rt = tokio::runtime::Handle::current(); - rt.block_on(async move { - LocalSet::new() - .run_until(async move { - tracing::info!( - "Mock Ethereum event oracle is starting" - ); - - loop { - if abort.is_closed() { - break; - } - } - - tracing::info!( - "Mock Ethereum event oracle is no longer running" - ); - }) - .await - }); + tokio::spawn(async move { + tracing::info!("Mock Ethereum event oracle is starting"); + + poll_fn(|cx| abort.poll_closed(cx)).await; + + tracing::info!("Mock Ethereum event oracle is no longer running"); }) } } From a400b8bf13ef0b70d53c02574dce9573ade2124c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 4 Aug 2022 10:34:52 +0100 Subject: [PATCH 0293/1995] let mut abort = abort; --- apps/src/lib/node/ledger/ethereum_node/test_tools.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 3115e4cda6..ed335ec597 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -35,11 +35,12 @@ pub mod mock_oracle { pub fn run_oracle( _: impl AsRef, _: UnboundedSender, - mut abort: Sender<()>, + abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { tracing::info!("Mock Ethereum event oracle is starting"); + let mut abort = abort; poll_fn(|cx| abort.poll_closed(cx)).await; tracing::info!("Mock Ethereum event oracle is no longer running"); From 2c662bc4ac3c388a587be551229816b92f09d19b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 4 Aug 2022 10:58:02 +0100 Subject: [PATCH 0294/1995] Revert "let mut abort = abort;" This reverts commit 27ecc4072c608c45ce26c1a5b08b1e7dcdf6eacf. --- apps/src/lib/node/ledger/ethereum_node/test_tools.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index ed335ec597..3115e4cda6 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -35,12 +35,11 @@ pub mod mock_oracle { pub fn run_oracle( _: impl AsRef, _: UnboundedSender, - abort: Sender<()>, + mut abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { tracing::info!("Mock Ethereum event oracle is starting"); - let mut abort = abort; poll_fn(|cx| abort.poll_closed(cx)).await; tracing::info!("Mock Ethereum event oracle is no longer running"); From 61030d8b3c998b11209c7fd8443f6ec15af827d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 13:48:32 +0100 Subject: [PATCH 0295/1995] WIP: Get keccak hash of validator set update vote extension --- .../vote_extensions/validator_set_update.rs | 25 ++++++--- .../validator_set_update/encoding.rs | 55 +++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 shared/src/types/vote_extensions/validator_set_update/encoding.rs diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 8ca470e395..7e001cd190 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,14 +1,15 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. +pub mod encoding; + use std::collections::HashMap; +use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; -use tiny_keccak::{Hasher, Keccak}; use crate::ledger::pos::types::{Epoch, VotingPower}; -use crate::types::ethereum_events::KeccakHash; // TODO: finish signed vote extension // ```ignore @@ -44,12 +45,20 @@ pub struct Vext { impl Vext { /// TODO - pub fn get_keccak_hash(&self) -> KeccakHash { - let state = Keccak::v256(); - let mut output = [0u8; 32]; - // state.update(&[...]); - state.finalize(&mut output); - todo!() + pub fn get_keccak_hash(&self) -> [u8; 32] { + // TODO: we need to get this value from `Storage` + // related issue: https://github.com/anoma/namada/issues/249 + const GOVERNANCE_CONTRACT_VERSION: u8 = 1; + + AbiEncode::keccak256(&[ + // the version of the governance smart contract + // + // TODO: in the Ethereum bridge smart contracts, the version + // fields are of type `uint8`. we might need to adjust this + // line accordingly + Token::Uint(ethereum::U256::from(GOVERNANCE_CONTRACT_VERSION)), + // TODO: add the other fields + ]) } /// Returns the list of Ethereum validator addresses and their respective diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs new file mode 100644 index 0000000000..d8fbd1a298 --- /dev/null +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -0,0 +1,55 @@ +//! This module defines encoding methods compatible with Ethereum +//! smart contracts. +// TODO: probably move this module elsewhere + +// TODO: I think we are missing `uint8` types +#[doc(inline)] +pub use ethabi::token::Token; +use tiny_keccak::{Hasher, Keccak}; + +/// Contains a method to encode data to a format compatible with Ethereum. +pub trait Encode { + /// The data type to be encoded to. Must deref to a hex string with + /// a `0x` prefix. + type EncodedData: AsRef; + + /// Returns the encoded [`Token`] instances. + fn encode(tokens: &[Token]) -> Self::EncodedData; + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string. + fn keccak256(tokens: &[Token]) -> [u8; 32] { + let mut output = [0; 32]; + + let mut state = Keccak::v256(); + state.update(Self::encode(tokens).as_ref().as_ref()); + state.finalize(&mut output); + + output + } +} + +/// Represents an Ethereum encoding method equivalent +/// to `abi.encode`. +pub struct AbiEncode; + +impl Encode for AbiEncode { + type EncodedData = String; + + fn encode(tokens: &[Token]) -> Self::EncodedData { + let encoded_data = hex::encode(ethabi::encode(tokens)); + format!("0x{encoded_data}") + } +} + +/// Represents an Ethereum encoding method equivalent +/// to `abi.encodePacked`. +pub struct AbiEncodePacked; + +impl Encode for AbiEncodePacked { + type EncodedData = String; + + fn encode(_tokens: &[Token]) -> Self::EncodedData { + todo!() + } +} From 26ab743661cde31adb924ed97c85534b0d781bbe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 14:10:42 +0100 Subject: [PATCH 0296/1995] Encode test --- .../validator_set_update/encoding.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index d8fbd1a298..c7a8daa9ed 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -53,3 +53,20 @@ impl Encode for AbiEncodePacked { todo!() } } + +#[cfg(test)] +mod tests { + use ethabi::ethereum_types::U256; + + use super::*; + + #[test] + fn test_abi_encode() { + let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + let got = AbiEncode::encode(&[ + Token::Uint(U256::from(42u64)), + Token::String("test".into()), + ]); + assert_eq!(expected, got); + } +} From 27016cdc05db592c8c9b2bea1b9723575c5eacb6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 14:45:29 +0100 Subject: [PATCH 0297/1995] Get keccak hash of validator set update vote extension --- .../vote_extensions/validator_set_update.rs | 73 +++++++++++++++---- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 7e001cd190..306cb95b5b 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -44,29 +44,76 @@ pub struct Vext { } impl Vext { - /// TODO - pub fn get_keccak_hash(&self) -> [u8; 32] { + /// Returns the keccak hash of this [`Vext`] to be signed + /// by an Ethereum validator key. + pub fn get_bridge_hash(&self) -> [u8; 32] { + // TODO: we need to get this value from `Storage` + // related issue: https://github.com/anoma/namada/issues/249 + const BRIDGE_CONTRACT_VERSION: u8 = 1; + + const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; + + let (validators, voting_powers) = + self.get_validators_and_voting_powers(); + + self.compute_hash( + BRIDGE_CONTRACT_VERSION, + BRIDGE_CONTRACT_NAMESPACE, + validators, + voting_powers, + ) + } + + /// Returns the keccak hash of this [`Vext`] to be signed + /// by an Ethereum governance key. + pub fn get_governance_hash(&self) -> [u8; 32] { // TODO: we need to get this value from `Storage` // related issue: https://github.com/anoma/namada/issues/249 const GOVERNANCE_CONTRACT_VERSION: u8 = 1; + const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; + + self.compute_hash( + GOVERNANCE_CONTRACT_VERSION, + GOVERNANCE_CONTRACT_NAMESPACE, + // TODO: get governance validators + vec![], + // TODO: get governance voting powers + vec![], + ) + } + + /// Compute the keccak hash of this [`Vext`]. + /// + /// For more information, check the Ethereum bridge smart contracts: + // - + // - + #[inline] + fn compute_hash( + &self, + version: u8, + namespace: &str, + validators: Vec, + voting_powers: Vec, + ) -> [u8; 32] { + let nonce = u64::from(self.epoch).into(); AbiEncode::keccak256(&[ - // the version of the governance smart contract - // // TODO: in the Ethereum bridge smart contracts, the version - // fields are of type `uint8`. we might need to adjust this - // line accordingly - Token::Uint(ethereum::U256::from(GOVERNANCE_CONTRACT_VERSION)), - // TODO: add the other fields + // fields are of type `uint8`. we need to adjust this + // line accordingly, since packed serialization yields + // a different result from `uint256` for `uint8` values + Token::Uint(ethereum::U256::from(version)), + Token::String(namespace.into()), + Token::Array(validators), + Token::Array(voting_powers), + Token::Uint(nonce), ]) } /// Returns the list of Ethereum validator addresses and their respective /// voting power. #[allow(dead_code)] - fn get_validators_and_addresses( - &self, - ) -> (Vec, Vec) { + fn get_validators_and_voting_powers(&self) -> (Vec, Vec) { // get addresses and voting powers all into one vec let mut unsorted: Vec<_> = self.voting_powers.iter().collect(); @@ -94,9 +141,9 @@ impl Vext { let voting_power = Ratio::new(voting_power, total_voting_power) * NORMALIZED_VOTING_POWER; let voting_power = voting_power.round().to_integer(); - let voting_power: ethereum::U256 = voting_power.into(); - (addr, voting_power) + + (Token::Address(addr), Token::Uint(voting_power)) }) .unzip() } From 16c4d4f8a3f8b95415ece4aa8de91add19e41e9e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 16:04:18 +0100 Subject: [PATCH 0298/1995] Remove AbiEncodePacked --- .../vote_extensions/validator_set_update/encoding.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index c7a8daa9ed..9e4bfe8f2d 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -42,18 +42,6 @@ impl Encode for AbiEncode { } } -/// Represents an Ethereum encoding method equivalent -/// to `abi.encodePacked`. -pub struct AbiEncodePacked; - -impl Encode for AbiEncodePacked { - type EncodedData = String; - - fn encode(_tokens: &[Token]) -> Self::EncodedData { - todo!() - } -} - #[cfg(test)] mod tests { use ethabi::ethereum_types::U256; From 32b002e0315ce0642032e72511209b77773626af Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 16:11:45 +0100 Subject: [PATCH 0299/1995] Remove TODO --- .../src/types/vote_extensions/validator_set_update/encoding.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 9e4bfe8f2d..1b9ffa06f3 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -2,7 +2,6 @@ //! smart contracts. // TODO: probably move this module elsewhere -// TODO: I think we are missing `uint8` types #[doc(inline)] pub use ethabi::token::Token; use tiny_keccak::{Hasher, Keccak}; From ce38f19249748c3a4f2a67fe53f6d22ee79f9127 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 16:24:42 +0100 Subject: [PATCH 0300/1995] Add VextDigest --- .../vote_extensions/validator_set_update.rs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 306cb95b5b..471c28e066 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -11,21 +11,23 @@ use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; -// TODO: finish signed vote extension -// ```ignore -// struct Vext { -// ...? -// } -// struct SignedVext { -// signature: EthereumSignature, -// data: Vext, -// } -// ``` -// we derive a keccak hash from the `Vext` data -// in `SignedVext`, which we can sign with an -// Ethereum key. that is the content of `signature` +/// Placeholder type for an Ethereum signature. +// TODO: remove this when the secp keys PR lands +pub type Signature = (); + +/// Contains the digest of all signatures from a quorum of +/// validators for a [`Vext`]. +#[derive(Debug)] +pub struct VextDigest { + /// A mapping from a validator Ethereum address to a [`Signature`]. + pub signatures: HashMap, + /// The validator set update vote extension, signed by the quorum + /// of validators. + pub validator_set_update: Vext, +} /// Represents a validator set update, for some new [`Epoch`]. +#[derive(Debug)] pub struct Vext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. From c14c8f24a3a503bc12e79ce47451a70c17a46581 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 4 Aug 2022 16:40:19 +0100 Subject: [PATCH 0301/1995] WIP: Ethereum address wrapper type --- shared/src/types/transaction/protocol.rs | 2 + .../vote_extensions/validator_set_update.rs | 58 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index 9c70b5b078..b3cb71d8b7 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -79,6 +79,8 @@ mod protocol_txs { NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions EthereumEvents(ethereum_events::VextDigest), + /// Validator set updates contained in vote extensions + ValidatorSetUpdate(validator_set_update::VextDigest), } impl ProtocolTxType { diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 471c28e066..1006285ee8 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -5,9 +5,10 @@ pub mod encoding; use std::collections::HashMap; +use num_rational::Ratio; use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; -use num_rational::Ratio; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use crate::ledger::pos::types::{Epoch, VotingPower}; @@ -15,19 +16,60 @@ use crate::ledger::pos::types::{Epoch, VotingPower}; // TODO: remove this when the secp keys PR lands pub type Signature = (); +/// Wrapper type for [`ethereum::Address`] +pub struct Validator(pub ethereum::Address); + +impl BorshSerialize for Validator { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + let (numer, denom): (u64, u64) = self.into(); + (numer, denom).serialize(writer) + } +} + +impl BorshDeserialize for Validator { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; + Ok(Validator(todo!())) + } +} + +impl BorshSchema for Validator { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + u64::declaration(), + u64::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "validator_set_update::Validator".into() + } +} + /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. -#[derive(Debug)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct VextDigest { /// A mapping from a validator Ethereum address to a [`Signature`]. - pub signatures: HashMap, + pub signatures: HashMap, /// The validator set update vote extension, signed by the quorum /// of validators. pub validator_set_update: Vext, } /// Represents a validator set update, for some new [`Epoch`]. -#[derive(Debug)] +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct Vext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. @@ -36,7 +78,7 @@ pub struct Vext { /// into two arrays: one for its keys, and another for its /// values. The arrays are sorted in descending order based /// on the voting power of each validator. - pub voting_powers: HashMap, + pub voting_powers: HashMap, /// The new [`Epoch`]. /// /// Since this is a monotonically growing sequence number, @@ -100,10 +142,6 @@ impl Vext { ) -> [u8; 32] { let nonce = u64::from(self.epoch).into(); AbiEncode::keccak256(&[ - // TODO: in the Ethereum bridge smart contracts, the version - // fields are of type `uint8`. we need to adjust this - // line accordingly, since packed serialization yields - // a different result from `uint256` for `uint8` values Token::Uint(ethereum::U256::from(version)), Token::String(namespace.into()), Token::Array(validators), @@ -133,7 +171,7 @@ impl Vext { // split the vec into two sorted .into_iter() - .map(|(&addr, &voting_power)| { + .map(|(&Validator(addr), &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power From 944f8c28d024e6a2ad8f2158fba7e6162593180b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 09:20:09 +0100 Subject: [PATCH 0302/1995] Add Borsh serialization for the Ethereum address wrapper --- shared/src/types/transaction/protocol.rs | 4 +++- .../vote_extensions/validator_set_update.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index b3cb71d8b7..a0b8a39bc8 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -35,7 +35,9 @@ mod protocol_txs { use crate::proto::Tx; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; - use crate::types::vote_extensions::ethereum_events; + use crate::types::vote_extensions::{ + ethereum_events, validator_set_update, + }; const TX_NEW_DKG_KP_WASM: &str = "tx_update_dkg_session_keypair.wasm"; diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 1006285ee8..cb34212725 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -5,10 +5,10 @@ pub mod encoding; use std::collections::HashMap; -use num_rational::Ratio; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; @@ -17,6 +17,7 @@ use crate::ledger::pos::types::{Epoch, VotingPower}; pub type Signature = (); /// Wrapper type for [`ethereum::Address`] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Validator(pub ethereum::Address); impl BorshSerialize for Validator { @@ -24,15 +25,15 @@ impl BorshSerialize for Validator { &self, writer: &mut W, ) -> std::io::Result<()> { - let (numer, denom): (u64, u64) = self.into(); - (numer, denom).serialize(writer) + let Validator(ethereum::H160(inner_array)) = self; + inner_array.serialize(writer) } } impl BorshDeserialize for Validator { fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let (numer, denom): (u64, u64) = BorshDeserialize::deserialize(buf)?; - Ok(Validator(todo!())) + let inner = <[u8; 20]>::deserialize(buf)?; + Ok(Validator(ethereum::H160(inner))) } } @@ -45,8 +46,7 @@ impl BorshSchema for Validator { ) { let fields = borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - u64::declaration(), - u64::declaration() + <[u8; 20]>::declaration() ]); let definition = borsh::schema::Definition::Struct { fields }; Self::add_definition(Self::declaration(), definition, definitions); From 791cce84a862b486504eacd869d358781b23c41e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 09:59:07 +0100 Subject: [PATCH 0303/1995] WIP: VextDigest decompress --- .../vote_extensions/validator_set_update.rs | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index cb34212725..393a946db9 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -10,34 +10,31 @@ use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; +use crate::types::key::common::Signature; use crate::ledger::pos::types::{Epoch, VotingPower}; -/// Placeholder type for an Ethereum signature. -// TODO: remove this when the secp keys PR lands -pub type Signature = (); - /// Wrapper type for [`ethereum::Address`] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Validator(pub ethereum::Address); +pub struct EthAddr(pub ethereum::Address); -impl BorshSerialize for Validator { +impl BorshSerialize for EthAddr { fn serialize( &self, writer: &mut W, ) -> std::io::Result<()> { - let Validator(ethereum::H160(inner_array)) = self; + let EthAddr(ethereum::H160(inner_array)) = self; inner_array.serialize(writer) } } -impl BorshDeserialize for Validator { +impl BorshDeserialize for EthAddr { fn deserialize(buf: &mut &[u8]) -> std::io::Result { let inner = <[u8; 20]>::deserialize(buf)?; - Ok(Validator(ethereum::H160(inner))) + Ok(EthAddr(ethereum::H160(inner))) } } -impl BorshSchema for Validator { +impl BorshSchema for EthAddr { fn add_definitions_recursively( definitions: &mut std::collections::HashMap< borsh::schema::Declaration, @@ -53,7 +50,7 @@ impl BorshSchema for Validator { } fn declaration() -> borsh::schema::Declaration { - "validator_set_update::Validator".into() + "validator_set_update::EthAddr".into() } } @@ -62,10 +59,38 @@ impl BorshSchema for Validator { #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct VextDigest { /// A mapping from a validator Ethereum address to a [`Signature`]. - pub signatures: HashMap, - /// The validator set update vote extension, signed by the quorum - /// of validators. + pub signatures: HashMap, + /// The addresses of the validators in the new [`Epoch`], + /// and their respective voting power. + pub voting_powers: HashMap, +} + +impl VextDigest { + /// Decompresses a set of signed [`Vext`] instances. + pub fn decompress(self, epoch: Epoch) -> Vec { + let VextDigest { signatures, validator_set_update } = self; + + let mut extensions = vec![]; + + for (_addr, signature) in signatures.into_iter() { + let validator_set_update = validator_set_update.clone(); + let signed = SignedVext { + signature, + validator_set_update, + }; + extensions.push(signed); + } + extensions + } +} + +/// Represents a [`Vext`] signed by some validator, with +/// an Ethereum key. +pub struct SignedVext { + /// The signed [`Vext`]. pub validator_set_update: Vext, + /// The signature of the signed [`Vext`]. + pub signature: Signature, } /// Represents a validator set update, for some new [`Epoch`]. @@ -78,7 +103,7 @@ pub struct Vext { /// into two arrays: one for its keys, and another for its /// values. The arrays are sorted in descending order based /// on the voting power of each validator. - pub voting_powers: HashMap, + pub voting_powers: HashMap, /// The new [`Epoch`]. /// /// Since this is a monotonically growing sequence number, @@ -171,7 +196,7 @@ impl Vext { // split the vec into two sorted .into_iter() - .map(|(&Validator(addr), &voting_power)| { + .map(|(&EthAddr(addr), &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power From 768eec14007ed5404385bafc9b6a988be66388e1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 10:17:40 +0100 Subject: [PATCH 0304/1995] Decompress validator set update vote extension digest --- .../vote_extensions/validator_set_update.rs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 393a946db9..095dd004d6 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -10,8 +10,9 @@ use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; -use crate::types::key::common::Signature; use crate::ledger::pos::types::{Epoch, VotingPower}; +use crate::types::address::Address; +use crate::types::key::common::Signature; /// Wrapper type for [`ethereum::Address`] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -58,8 +59,8 @@ impl BorshSchema for EthAddr { /// validators for a [`Vext`]. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct VextDigest { - /// A mapping from a validator Ethereum address to a [`Signature`]. - pub signatures: HashMap, + /// A mapping from a validator address to a [`Signature`]. + pub signatures: HashMap, /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. pub voting_powers: HashMap, @@ -68,12 +69,20 @@ pub struct VextDigest { impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, epoch: Epoch) -> Vec { - let VextDigest { signatures, validator_set_update } = self; + let VextDigest { + signatures, + voting_powers, + } = self; let mut extensions = vec![]; - for (_addr, signature) in signatures.into_iter() { - let validator_set_update = validator_set_update.clone(); + for (validator_addr, signature) in signatures.into_iter() { + let voting_powers = voting_powers.clone(); + let validator_set_update = Vext { + validator_addr, + voting_powers, + epoch, + }; let signed = SignedVext { signature, validator_set_update, @@ -104,6 +113,10 @@ pub struct Vext { /// values. The arrays are sorted in descending order based /// on the voting power of each validator. pub voting_powers: HashMap, + /// TODO: the validator's address is temporarily being included + /// until we're able to map a Tendermint address to a validator + /// address (see https://github.com/anoma/namada/issues/200) + pub validator_addr: Address, /// The new [`Epoch`]. /// /// Since this is a monotonically growing sequence number, From 8305590eff64bbed22279164bb1c269dca55d3f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 10:44:59 +0100 Subject: [PATCH 0305/1995] Fix make clippy-abci-plus-plus --- shared/Cargo.toml | 3 +-- wasm/tx_template/Cargo.lock | 1 + wasm/vp_template/Cargo.lock | 1 + wasm/wasm_source/Cargo.lock | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ca4dfeb1f8..9cba8d064f 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -42,7 +42,6 @@ ABCI-plus-plus = [ "ibc-proto", "tendermint", "tendermint-proto", - "tiny-keccak", ] testing = [ "proptest", @@ -112,7 +111,7 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "9 tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "murisi/jsfix", optional = true} thiserror = "1.0.30" -tiny-keccak = {version = "2.0.2", optional = true} +tiny-keccak = "2.0.2" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index c4b8e4b65c..2f0a7e676d 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 2bbac52319..c866d7ae02 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index d6e7e77bf7..eff69502d9 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", From b0ff8a5ce9a0aa8b72b416d0383798afc2425dfb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:12:36 +0100 Subject: [PATCH 0306/1995] Sign stub --- .../src/types/vote_extensions/validator_set_update.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 095dd004d6..ede62e0bfa 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -12,7 +12,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; use crate::types::address::Address; -use crate::types::key::common::Signature; +use crate::types::key::common::{self, Signature}; /// Wrapper type for [`ethereum::Address`] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] @@ -225,4 +225,12 @@ impl Vext { }) .unzip() } + + /// Sign this [`Vext`] with an Ethereum key. + /// + /// For more information, check the Ethereum bridge smart contract code: + /// - + pub fn sign(&self, _sk: &common::SecretKey) -> SignedVext { + todo!() + } } From 52b6d570bcbff2029dc92b021d39421efd11f895 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:15:27 +0100 Subject: [PATCH 0307/1995] Add VoteExtension type --- shared/src/types/vote_extensions.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 0637292478..2c401518bc 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -3,14 +3,14 @@ pub mod ethereum_events; pub mod validator_set_update; -// TODO: add a `VoteExtension` type -// -// ```ignore -// pub struct VoteExtension { -// pub ethereum_events: Signed, -// pub validator_set_update: Option, -// } -// ``` +/// This type represents the data we pass to the extension of +/// a vote at the PreCommit phase of Tendermint. +pub struct VoteExtension { + /// Vote extension data related with Ethereum events. + pub ethereum_events: Signed, + /// Vote extension data related with validator set updates. + pub validator_set_update: Option, +} // TODO: add a `VoteExtensionDigest` type; this will contain // the values to be proposed, for a quorum of Ethereum events From 5abd8e714aba14c53556d45a0654fe8c727347b8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:22:01 +0100 Subject: [PATCH 0308/1995] Add VoteExtensionDigest --- shared/src/types/vote_extensions.rs | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 2c401518bc..e47d74ecb1 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -3,6 +3,8 @@ pub mod ethereum_events; pub mod validator_set_update; +use crate::proto::Signed; + /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. pub struct VoteExtension { @@ -12,18 +14,16 @@ pub struct VoteExtension { pub validator_set_update: Option, } -// TODO: add a `VoteExtensionDigest` type; this will contain -// the values to be proposed, for a quorum of Ethereum events -// vote extensions, and a separate quorum of validator set update -// vote extensions -// -// ```ignore -// pub struct VoteExtensionDigest { -// pub ethereum_events: ethereum_events::VextDigest, -// pub validator_set_update: Option, -// } -// ``` -// -// from a `VoteExtensionDigest` we yield two signed `ProtocolTxType` values, -// one of `ProtocolTxType::EthereumEvents` and the other of -// `ProtocolTxType::ValidatorSetUpdate` +/// The digest of the signatures from different validators +/// in [`VoteExtension`] instances. +/// +/// From a [`VoteExtensionDigest`] we yield two signed +/// [`crate::types::transaction::protocol::ProtocolTxType`] transactions: +/// - A `ProtocolTxType::EthereumEvents` tx, and +/// - A `ProtocolTxType::ValidatorSetUpdate` tx +pub struct VoteExtensionDigest { + /// The digest of Ethereum events vote extension signatures. + pub ethereum_events: ethereum_events::VextDigest, + /// The digest of validator set updates vote extension signatures. + pub validator_set_update: Option, +} From dc0208548133bd9806527d35759694e75742a476 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:26:19 +0100 Subject: [PATCH 0309/1995] Rename EncodedData to HexString --- .../vote_extensions/validator_set_update/encoding.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 1b9ffa06f3..80b09f5c4a 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -10,10 +10,10 @@ use tiny_keccak::{Hasher, Keccak}; pub trait Encode { /// The data type to be encoded to. Must deref to a hex string with /// a `0x` prefix. - type EncodedData: AsRef; + type HexString: AsRef; /// Returns the encoded [`Token`] instances. - fn encode(tokens: &[Token]) -> Self::EncodedData; + fn encode(tokens: &[Token]) -> Self::HexString; /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. @@ -33,9 +33,9 @@ pub trait Encode { pub struct AbiEncode; impl Encode for AbiEncode { - type EncodedData = String; + type HexString = String; - fn encode(tokens: &[Token]) -> Self::EncodedData { + fn encode(tokens: &[Token]) -> Self::HexString { let encoded_data = hex::encode(ethabi::encode(tokens)); format!("0x{encoded_data}") } From 20499f54e2c449b8b7ae7083f89dd2d9bfb36d6b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:27:39 +0100 Subject: [PATCH 0310/1995] Add test docstring --- .../src/types/vote_extensions/validator_set_update/encoding.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 80b09f5c4a..0046cb86d3 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -47,6 +47,8 @@ mod tests { use super::*; + /// Checks if we get the same result as `abi.encode`, for some given + /// input data. #[test] fn test_abi_encode() { let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; From 4f205f5c897138bb0a9389835b26da295e4a5fb0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 11:42:24 +0100 Subject: [PATCH 0311/1995] Add abi_params() stub --- shared/src/types/vote_extensions/validator_set_update.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index ede62e0bfa..3f513e974c 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -91,6 +91,12 @@ impl VextDigest { } extensions } + + /// Returns an Ethereum ABI encoded string with the + /// params to feed to the Ethereum bridge smart contracts. + pub fn abi_params(&self) -> String { + todo!() + } } /// Represents a [`Vext`] signed by some validator, with From dfa21a959257a8913eb7562bd235163698deabfe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 17:13:01 +0100 Subject: [PATCH 0312/1995] Make Signed use a generic serialization method --- shared/src/proto/types.rs | 58 ++++++++++--------- .../types/vote_extensions/ethereum_events.rs | 2 +- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index ace163e99f..8ea5617911 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; use borsh::schema::{Declaration, Definition}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -51,54 +52,45 @@ pub struct SignedTxData { pub sig: common::Signature, } -/// A generic signed data wrapper for Borsh encode-able data. +/// Tag type that indicates we should use [`BorshSerialize`] +/// to sign data in a [`Signed`] wrapper. +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum SerializeWithBorsch {} + +/// A generic signed data wrapper for serialize-able types. #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Eq, Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] -pub struct Signed { +pub struct Signed { /// Arbitrary data to be signed pub data: T, /// The signature of the data pub sig: common::Signature, + /// The method to serialize the data with, + /// before it being signed + _serialization: PhantomData, } -impl PartialEq for Signed -where - T: BorshSerialize + BorshDeserialize + PartialEq, -{ +impl PartialEq for Signed { fn eq(&self, other: &Self) -> bool { self.data == other.data && self.sig == other.sig } } -impl Eq for Signed where - T: BorshSerialize + BorshDeserialize + Eq + PartialEq -{ -} - -impl Hash for Signed -where - T: BorshSerialize + BorshDeserialize + Hash, -{ +impl Hash for Signed { fn hash(&self, state: &mut H) { self.data.hash(state); self.sig.hash(state); } } -impl PartialOrd for Signed -where - T: BorshSerialize + BorshDeserialize + PartialOrd, -{ +impl PartialOrd for Signed { fn partial_cmp(&self, other: &Self) -> Option { self.data.partial_cmp(&other.data) } } -impl BorshSchema for Signed -where - T: BorshSerialize + BorshDeserialize + BorshSchema, -{ +impl BorshSchema for Signed { fn add_definitions_recursively( definitions: &mut HashMap, ) { @@ -117,17 +109,29 @@ where } } -impl Signed +impl Signed { + /// Initialize a new [`Signed`] instance from an existing signature. + #[inline] + pub fn new_from(data: T, sig: common::Signature) -> Self { + Self { + data, + sig, + _serialization: PhantomData, + } + } +} + +impl Signed where T: BorshSerialize + BorshDeserialize, { - /// Initialize a new signed data. + /// Initialize a new [`Signed`] instance. pub fn new(keypair: &common::SecretKey, data: T) -> Self { let to_sign = data .try_to_vec() .expect("Encoding data for signing shouldn't fail"); let sig = common::SigScheme::sign(keypair, &to_sign); - Self { data, sig } + Self::new_from(data, sig) } /// Verify that the data has been signed by the secret key diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index b0edb0ec28..524d03aa52 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -93,7 +93,7 @@ impl VextDigest { // of crate versions changing and such ext.ethereum_events.sort(); - let signed = Signed { data: ext, sig }; + let signed = Signed::new_from(ext, sig); extensions.push(signed); } extensions From 9e1e32d1f044472e725f560827ec50bc8bd26533 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 17:28:20 +0100 Subject: [PATCH 0313/1995] WIP: Sign Vext --- .../vote_extensions/validator_set_update.rs | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 3f513e974c..12849db3d7 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -11,9 +11,19 @@ use ethabi::ethereum_types as ethereum; use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; +use crate::proto::Signed; use crate::types::address::Address; use crate::types::key::common::{self, Signature}; +// TODO: we need to get these values from `Storage` +// related issue: https://github.com/anoma/namada/issues/249 +const BRIDGE_CONTRACT_VERSION: u8 = 1; +const GOVERNANCE_CONTRACT_VERSION: u8 = 1; + +// the namespace strings plugged into validator set hashes +const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; +const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; + /// Wrapper type for [`ethereum::Address`] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct EthAddr(pub ethereum::Address); @@ -78,16 +88,12 @@ impl VextDigest { for (validator_addr, signature) in signatures.into_iter() { let voting_powers = voting_powers.clone(); - let validator_set_update = Vext { + let data = Vext { validator_addr, voting_powers, epoch, }; - let signed = SignedVext { - signature, - validator_set_update, - }; - extensions.push(signed); + extensions.push(SignedVext::new_from(data, signature)); } extensions } @@ -101,15 +107,27 @@ impl VextDigest { /// Represents a [`Vext`] signed by some validator, with /// an Ethereum key. -pub struct SignedVext { - /// The signed [`Vext`]. - pub validator_set_update: Vext, - /// The signature of the signed [`Vext`]. - pub signature: Signature, +pub type SignedVext = Signed; + +impl SignedVext { + /// Sign this [`Vext`] with an Ethereum key. + /// + /// For more information, check the Ethereum bridge smart contract code: + /// - + pub fn new_abi_encoded( + _keypair: &common::SecretKey, + _vote_extension: Vext, + ) -> Self { + todo!() + } + + // TODO: verify } /// Represents a validator set update, for some new [`Epoch`]. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct Vext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. @@ -134,13 +152,8 @@ pub struct Vext { impl Vext { /// Returns the keccak hash of this [`Vext`] to be signed /// by an Ethereum validator key. + #[inline] pub fn get_bridge_hash(&self) -> [u8; 32] { - // TODO: we need to get this value from `Storage` - // related issue: https://github.com/anoma/namada/issues/249 - const BRIDGE_CONTRACT_VERSION: u8 = 1; - - const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; - let (validators, voting_powers) = self.get_validators_and_voting_powers(); @@ -154,13 +167,8 @@ impl Vext { /// Returns the keccak hash of this [`Vext`] to be signed /// by an Ethereum governance key. + #[inline] pub fn get_governance_hash(&self) -> [u8; 32] { - // TODO: we need to get this value from `Storage` - // related issue: https://github.com/anoma/namada/issues/249 - const GOVERNANCE_CONTRACT_VERSION: u8 = 1; - - const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; - self.compute_hash( GOVERNANCE_CONTRACT_VERSION, GOVERNANCE_CONTRACT_NAMESPACE, @@ -196,7 +204,6 @@ impl Vext { /// Returns the list of Ethereum validator addresses and their respective /// voting power. - #[allow(dead_code)] fn get_validators_and_voting_powers(&self) -> (Vec, Vec) { // get addresses and voting powers all into one vec let mut unsorted: Vec<_> = self.voting_powers.iter().collect(); @@ -232,11 +239,23 @@ impl Vext { .unzip() } - /// Sign this [`Vext`] with an Ethereum key. + /// Creates a new signed [`Vext`]. /// - /// For more information, check the Ethereum bridge smart contract code: - /// - - pub fn sign(&self, _sk: &common::SecretKey) -> SignedVext { - todo!() + /// For more information, read the docs of [`SignedVext::new`]. + #[inline] + pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { + SignedVext::new_abi_encoded(sk, self.clone()) } } + +pub mod tag { + //! This module holds [`SerializeWithAbiEncode`], which is a tag enum + //! to be passed to `Signed` instances. + + use serde::{Deserialize, Serialize}; + + /// Tag type that indicates we should use [`AbiEncode::encode`] + /// to sign data in a [`Signed`] wrapper. + #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] + pub enum SerializeWithAbiEncode {} +} From d90423843946095b3ff9e5413303499a7ddebf30 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 17:34:06 +0100 Subject: [PATCH 0314/1995] Make tag mod private --- .../types/vote_extensions/validator_set_update.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 12849db3d7..605be2398b 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -107,7 +107,7 @@ impl VextDigest { /// Represents a [`Vext`] signed by some validator, with /// an Ethereum key. -pub type SignedVext = Signed; +pub type SignedVext = Signed; impl SignedVext { /// Sign this [`Vext`] with an Ethereum key. @@ -248,10 +248,9 @@ impl Vext { } } -pub mod tag { - //! This module holds [`SerializeWithAbiEncode`], which is a tag enum - //! to be passed to `Signed` instances. - +// this is only here so we don't pollute the +// outer namespace with serde traits +mod tag { use serde::{Deserialize, Serialize}; /// Tag type that indicates we should use [`AbiEncode::encode`] @@ -259,3 +258,6 @@ pub mod tag { #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum SerializeWithAbiEncode {} } + +#[doc(inline)] +pub use tag::SerializeWithAbiEncode; From a8ae4286c9125523dafb2e860b557c4bcd40b802 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 17:36:17 +0100 Subject: [PATCH 0315/1995] Fix docstrings --- shared/src/types/vote_extensions/validator_set_update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 605be2398b..4e59ebabcb 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -253,8 +253,8 @@ impl Vext { mod tag { use serde::{Deserialize, Serialize}; - /// Tag type that indicates we should use [`AbiEncode::encode`] - /// to sign data in a [`Signed`] wrapper. + /// Tag type that indicates we should use [`super::encoding::AbiEncode`] + /// to sign data in a [`crate::proto::Signed`] wrapper. #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum SerializeWithAbiEncode {} } From 9e473efe7ea36b29dbd386f7100689c0c97a4aa6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 20:25:24 +0100 Subject: [PATCH 0316/1995] Tighten trait constraints on Signed --- shared/src/proto/types.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 8ea5617911..0d0d3d06b1 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -121,10 +121,7 @@ impl Signed { } } -impl Signed -where - T: BorshSerialize + BorshDeserialize, -{ +impl Signed { /// Initialize a new [`Signed`] instance. pub fn new(keypair: &common::SecretKey, data: T) -> Self { let to_sign = data From e09944cce82082ce9a78bf9bb09f75f74a695291 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 20:29:07 +0100 Subject: [PATCH 0317/1995] Improve Signed docstring --- shared/src/proto/types.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 0d0d3d06b1..46c6a6a1f8 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -58,6 +58,8 @@ pub struct SignedTxData { pub enum SerializeWithBorsch {} /// A generic signed data wrapper for serialize-able types. +/// +/// The default serialization method is [`BorshSerialize`]. #[derive( Eq, Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] From 20cba916f845bcc9f0d9efed1ae4a87a2dcff803 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 5 Aug 2022 20:31:55 +0100 Subject: [PATCH 0318/1995] Fix typo in tag enum --- shared/src/proto/types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 46c6a6a1f8..754361b051 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -55,7 +55,7 @@ pub struct SignedTxData { /// Tag type that indicates we should use [`BorshSerialize`] /// to sign data in a [`Signed`] wrapper. #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum SerializeWithBorsch {} +pub enum SerializeWithBorsh {} /// A generic signed data wrapper for serialize-able types. /// @@ -63,7 +63,7 @@ pub enum SerializeWithBorsch {} #[derive( Eq, Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] -pub struct Signed { +pub struct Signed { /// Arbitrary data to be signed pub data: T, /// The signature of the data @@ -123,7 +123,7 @@ impl Signed { } } -impl Signed { +impl Signed { /// Initialize a new [`Signed`] instance. pub fn new(keypair: &common::SecretKey, data: T) -> Self { let to_sign = data From 6bb7b4170fbaca044c818790b969e20e12abeb1b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 09:39:09 +0100 Subject: [PATCH 0319/1995] WIP: Verify validator set update vote extension signature --- .../src/types/vote_extensions/validator_set_update.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 4e59ebabcb..dc2d0d9100 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -14,6 +14,7 @@ use crate::ledger::pos::types::{Epoch, VotingPower}; use crate::proto::Signed; use crate::types::address::Address; use crate::types::key::common::{self, Signature}; +use crate::types::key::VerifySigError; // TODO: we need to get these values from `Storage` // related issue: https://github.com/anoma/namada/issues/249 @@ -121,7 +122,14 @@ impl SignedVext { todo!() } - // TODO: verify + /// Verify the signature of a [`Vext`], signed by some + /// Ethereum key. + pub fn verify_abi_encoded( + &self, + _pk: &common::PublicKey, + ) -> Result<(), VerifySigError> { + todo!() + } } /// Represents a validator set update, for some new [`Epoch`]. From 4a78619e1f29c66ea9f37979f2bd05b15e03fd91 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 10:01:02 +0100 Subject: [PATCH 0320/1995] Refactor voting powers --- .../vote_extensions/validator_set_update.rs | 110 ++++++++++-------- 1 file changed, 63 insertions(+), 47 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index dc2d0d9100..dfd243ae33 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -74,7 +74,7 @@ pub struct VextDigest { pub signatures: HashMap, /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. - pub voting_powers: HashMap, + pub voting_powers: VotingPowersMap, } impl VextDigest { @@ -140,11 +140,11 @@ pub struct Vext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. /// - /// When signing a [`Vext`], this [`HashMap`] is converted + /// When signing a [`Vext`], this [`VotingPowersMap`] is converted /// into two arrays: one for its keys, and another for its /// values. The arrays are sorted in descending order based /// on the voting power of each validator. - pub voting_powers: HashMap, + pub voting_powers: VotingPowersMap, /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator /// address (see https://github.com/anoma/namada/issues/200) @@ -158,14 +158,41 @@ pub struct Vext { } impl Vext { - /// Returns the keccak hash of this [`Vext`] to be signed - /// by an Ethereum validator key. + /// Creates a new signed [`Vext`]. + /// + /// For more information, read the docs of [`SignedVext::new`]. + #[inline] + pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { + SignedVext::new_abi_encoded(sk, self.clone()) + } +} + +/// Provides a mapping between [`EthAddr`] and [`VotingPower`] instances. +pub type VotingPowersMap = HashMap; + +/// This trait contains additional methods for a [`HashMap`], related +/// with validator set update vote extensions logic. +pub trait VotingPowersMapExt { + /// Returns the keccak hash of this [`VotingPowersMap`] + /// to be signed by an Ethereum validator key. + fn get_bridge_hash(&self, epoch: Epoch) -> [u8; 32]; + + /// Returns the keccak hash of this [`VotingPowersMap`] + /// to be signed by an Ethereum governance key. + fn get_governance_hash(&self, epoch: Epoch) -> [u8; 32]; + + /// Returns the list of Ethereum validator addresses and their respective + /// voting power (in this order), with an Ethereum ABI compatible encoding. + fn get_abi_encoded(&self) -> (Vec, Vec); +} + +impl VotingPowersMapExt for VotingPowersMap { #[inline] - pub fn get_bridge_hash(&self) -> [u8; 32] { - let (validators, voting_powers) = - self.get_validators_and_voting_powers(); + fn get_bridge_hash(&self, epoch: Epoch) -> [u8; 32] { + let (validators, voting_powers) = self.get_abi_encoded(); - self.compute_hash( + compute_hash( + epoch, BRIDGE_CONTRACT_VERSION, BRIDGE_CONTRACT_NAMESPACE, validators, @@ -173,11 +200,10 @@ impl Vext { ) } - /// Returns the keccak hash of this [`Vext`] to be signed - /// by an Ethereum governance key. #[inline] - pub fn get_governance_hash(&self) -> [u8; 32] { - self.compute_hash( + fn get_governance_hash(&self, epoch: Epoch) -> [u8; 32] { + compute_hash( + epoch, GOVERNANCE_CONTRACT_VERSION, GOVERNANCE_CONTRACT_NAMESPACE, // TODO: get governance validators @@ -187,34 +213,9 @@ impl Vext { ) } - /// Compute the keccak hash of this [`Vext`]. - /// - /// For more information, check the Ethereum bridge smart contracts: - // - - // - - #[inline] - fn compute_hash( - &self, - version: u8, - namespace: &str, - validators: Vec, - voting_powers: Vec, - ) -> [u8; 32] { - let nonce = u64::from(self.epoch).into(); - AbiEncode::keccak256(&[ - Token::Uint(ethereum::U256::from(version)), - Token::String(namespace.into()), - Token::Array(validators), - Token::Array(voting_powers), - Token::Uint(nonce), - ]) - } - - /// Returns the list of Ethereum validator addresses and their respective - /// voting power. - fn get_validators_and_voting_powers(&self) -> (Vec, Vec) { + fn get_abi_encoded(&self) -> (Vec, Vec) { // get addresses and voting powers all into one vec - let mut unsorted: Vec<_> = self.voting_powers.iter().collect(); + let mut unsorted: Vec<_> = self.iter().collect(); // sort it by voting power, in descending order unsorted.sort_by(|&(_, ref power_1), &(_, ref power_2)| { @@ -246,14 +247,29 @@ impl Vext { }) .unzip() } +} - /// Creates a new signed [`Vext`]. - /// - /// For more information, read the docs of [`SignedVext::new`]. - #[inline] - pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { - SignedVext::new_abi_encoded(sk, self.clone()) - } +/// Compute the keccak hash of a validator set update. +/// +/// For more information, check the Ethereum bridge smart contracts: +// - +// - +#[inline] +fn compute_hash( + epoch: Epoch, + version: u8, + namespace: &str, + validators: Vec, + voting_powers: Vec, +) -> [u8; 32] { + let nonce = u64::from(epoch).into(); + AbiEncode::keccak256(&[ + Token::Uint(ethereum::U256::from(version)), + Token::String(namespace.into()), + Token::Array(validators), + Token::Array(voting_powers), + Token::Uint(nonce), + ]) } // this is only here so we don't pollute the From adb9c8edf9bafc91382d65a999d97312882b4852 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 13:12:44 +0100 Subject: [PATCH 0321/1995] Ethereum keccak hash of signed message --- .../validator_set_update/encoding.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 0046cb86d3..6266550d06 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -26,6 +26,30 @@ pub trait Encode { output } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string appended to an Ethereum + /// signature header. + fn signed_keccak256(tokens: &[Token]) -> [u8; 32] { + let mut output = [0; 32]; + + let eth_message = { + let encoded = Self::encode(tokens); + let message: &[u8] = encoded.as_ref().as_ref(); + + let mut eth_message = + format!("\x19Ethereum Signed Message:\n{}", message.len()) + .into_bytes(); + eth_message.extend_from_slice(message); + eth_message + }; + + let mut state = Keccak::v256(); + state.update(ð_message); + state.finalize(&mut output); + + output + } } /// Represents an Ethereum encoding method equivalent From d0381ab20dc42058026aea1040017895020ae57e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 13:44:15 +0100 Subject: [PATCH 0322/1995] Remove smart contract version from the hash calc --- shared/src/types/vote_extensions/validator_set_update.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index dfd243ae33..84fa55866f 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -16,11 +16,6 @@ use crate::types::address::Address; use crate::types::key::common::{self, Signature}; use crate::types::key::VerifySigError; -// TODO: we need to get these values from `Storage` -// related issue: https://github.com/anoma/namada/issues/249 -const BRIDGE_CONTRACT_VERSION: u8 = 1; -const GOVERNANCE_CONTRACT_VERSION: u8 = 1; - // the namespace strings plugged into validator set hashes const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; @@ -193,7 +188,6 @@ impl VotingPowersMapExt for VotingPowersMap { compute_hash( epoch, - BRIDGE_CONTRACT_VERSION, BRIDGE_CONTRACT_NAMESPACE, validators, voting_powers, @@ -204,7 +198,6 @@ impl VotingPowersMapExt for VotingPowersMap { fn get_governance_hash(&self, epoch: Epoch) -> [u8; 32] { compute_hash( epoch, - GOVERNANCE_CONTRACT_VERSION, GOVERNANCE_CONTRACT_NAMESPACE, // TODO: get governance validators vec![], @@ -257,14 +250,12 @@ impl VotingPowersMapExt for VotingPowersMap { #[inline] fn compute_hash( epoch: Epoch, - version: u8, namespace: &str, validators: Vec, voting_powers: Vec, ) -> [u8; 32] { let nonce = u64::from(epoch).into(); AbiEncode::keccak256(&[ - Token::Uint(ethereum::U256::from(version)), Token::String(namespace.into()), Token::Array(validators), Token::Array(voting_powers), From afc35d7196ec3f2764a4157342593bedce08f19f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 13:57:19 +0100 Subject: [PATCH 0323/1995] Sign validator set update --- .../vote_extensions/validator_set_update.rs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 84fa55866f..9f81cd216b 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -14,7 +14,7 @@ use crate::ledger::pos::types::{Epoch, VotingPower}; use crate::proto::Signed; use crate::types::address::Address; use crate::types::key::common::{self, Signature}; -use crate::types::key::VerifySigError; +use crate::types::key::{SigScheme, VerifySigError}; // the namespace strings plugged into validator set hashes const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; @@ -110,11 +110,19 @@ impl SignedVext { /// /// For more information, check the Ethereum bridge smart contract code: /// - - pub fn new_abi_encoded( - _keypair: &common::SecretKey, - _vote_extension: Vext, - ) -> Self { - todo!() + pub fn new_abi_encoded(keypair: &common::SecretKey, ext: Vext) -> Self { + let to_sign = AbiEncode::signed_keccak256(&[ + Token::String("updateValidatorsSet".into()), + Token::FixedBytes( + ext.voting_powers.get_bridge_hash(ext.epoch).to_vec(), + ), + Token::FixedBytes( + ext.voting_powers.get_governance_hash(ext.epoch).to_vec(), + ), + epoch_to_token(ext.epoch), + ]); + let sig = common::SigScheme::sign(keypair, &to_sign); + Self::new_from(ext, sig) } /// Verify the signature of a [`Vext`], signed by some @@ -242,6 +250,12 @@ impl VotingPowersMapExt for VotingPowersMap { } } +/// Convert an [`Epoch`] to a [`Token`]. +#[inline] +fn epoch_to_token(epoch: Epoch) -> Token { + Token::Uint(u64::from(epoch).into()) +} + /// Compute the keccak hash of a validator set update. /// /// For more information, check the Ethereum bridge smart contracts: @@ -254,12 +268,11 @@ fn compute_hash( validators: Vec, voting_powers: Vec, ) -> [u8; 32] { - let nonce = u64::from(epoch).into(); AbiEncode::keccak256(&[ Token::String(namespace.into()), Token::Array(validators), Token::Array(voting_powers), - Token::Uint(nonce), + epoch_to_token(epoch), ]) } From 69c4fb5b452253ac7400e11a496ddc48058ba6d7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 14:02:51 +0100 Subject: [PATCH 0324/1995] Verify signature of validator set update --- .../vote_extensions/validator_set_update.rs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 9f81cd216b..abb68f34a5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -106,12 +106,9 @@ impl VextDigest { pub type SignedVext = Signed; impl SignedVext { - /// Sign this [`Vext`] with an Ethereum key. - /// - /// For more information, check the Ethereum bridge smart contract code: - /// - - pub fn new_abi_encoded(keypair: &common::SecretKey, ext: Vext) -> Self { - let to_sign = AbiEncode::signed_keccak256(&[ + /// Serialize a [`Vext`] to be signed. + fn serialize_vext(ext: &Vext) -> [u8; 32] { + AbiEncode::signed_keccak256(&[ Token::String("updateValidatorsSet".into()), Token::FixedBytes( ext.voting_powers.get_bridge_hash(ext.epoch).to_vec(), @@ -120,7 +117,15 @@ impl SignedVext { ext.voting_powers.get_governance_hash(ext.epoch).to_vec(), ), epoch_to_token(ext.epoch), - ]); + ]) + } + + /// Sign this [`Vext`] with an Ethereum key. + /// + /// For more information, check the Ethereum bridge smart contract code: + /// - + pub fn new_abi_encoded(keypair: &common::SecretKey, ext: Vext) -> Self { + let to_sign = Self::serialize_vext(&ext); let sig = common::SigScheme::sign(keypair, &to_sign); Self::new_from(ext, sig) } @@ -129,9 +134,10 @@ impl SignedVext { /// Ethereum key. pub fn verify_abi_encoded( &self, - _pk: &common::PublicKey, + pk: &common::PublicKey, ) -> Result<(), VerifySigError> { - todo!() + let bytes = Self::serialize_vext(&self.data); + common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) } } From a5a2cdea6b6a73fb7e89b581b6e9f260dfaca6a4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 14:22:24 +0100 Subject: [PATCH 0325/1995] Add VoteExtensionDigest::get_protocol_txs() --- shared/src/types/vote_extensions.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index e47d74ecb1..a5566ee7a0 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -4,6 +4,7 @@ pub mod ethereum_events; pub mod validator_set_update; use crate::proto::Signed; +use crate::types::transaction::protocol::ProtocolTxType; /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. @@ -18,7 +19,7 @@ pub struct VoteExtension { /// in [`VoteExtension`] instances. /// /// From a [`VoteExtensionDigest`] we yield two signed -/// [`crate::types::transaction::protocol::ProtocolTxType`] transactions: +/// [`ProtocolTxType`] transactions: /// - A `ProtocolTxType::EthereumEvents` tx, and /// - A `ProtocolTxType::ValidatorSetUpdate` tx pub struct VoteExtensionDigest { @@ -27,3 +28,17 @@ pub struct VoteExtensionDigest { /// The digest of validator set updates vote extension signatures. pub validator_set_update: Option, } + +impl VoteExtensionDigest { + /// Yields an iterator over the [`ProtocolTxType`] transactions + /// in this [`VoteExtensionDigest`]. + pub fn get_protocol_txs(self) -> impl Iterator { + [ + Some(ProtocolTxType::EthereumEvents(self.ethereum_events)), + self.validator_set_update + .map(ProtocolTxType::ValidatorSetUpdate), + ] + .into_iter() + .flat_map(|tx| tx) + } +} From d72e25e4626ffad034ebb66fc5a3608b04763b08 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 14:31:09 +0100 Subject: [PATCH 0326/1995] Rename get_protocol_txs to into_protocol_txs --- shared/src/types/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index a5566ee7a0..361d2303d2 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -32,7 +32,7 @@ pub struct VoteExtensionDigest { impl VoteExtensionDigest { /// Yields an iterator over the [`ProtocolTxType`] transactions /// in this [`VoteExtensionDigest`]. - pub fn get_protocol_txs(self) -> impl Iterator { + pub fn into_protocol_txs(self) -> impl Iterator { [ Some(ProtocolTxType::EthereumEvents(self.ethereum_events)), self.validator_set_update From 6e4a4379a69efb17fdf57c97a5894ca741fad4e8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 14:34:06 +0100 Subject: [PATCH 0327/1995] Add a TODO --- .../src/types/vote_extensions/validator_set_update/encoding.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 6266550d06..215fa455e7 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -65,6 +65,7 @@ impl Encode for AbiEncode { } } +// TODO: test signatures here once we merge secp keys #[cfg(test)] mod tests { use ethabi::ethereum_types::U256; From f4a724e5fefc559dce866b9ed877c02d4f2b1983 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:23:17 +0100 Subject: [PATCH 0328/1995] prepare_proposal.rs: log number of txs received from mempool --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 217e19fac9..eded60c1ad 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -48,8 +48,14 @@ mod prepare_block { self.build_vote_extensions_txs(req.local_last_commit); // add mempool txs - let mut mempool_txs = self.build_mempool_txs(req.txs); - txs.append(&mut mempool_txs); + if !req.txs.is_empty() { + tracing::info!( + n = req.txs.len(), + "Received transactions from mempool" + ); + let mut mempool_txs = self.build_mempool_txs(req.txs); + txs.append(&mut mempool_txs); + } // decrypt the wrapper txs included in the previous block let mut decrypted_txs = self.build_decrypted_txs(); From a8bd6fd515d26bc83fac04c653815216a20ca390 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:25:14 +0100 Subject: [PATCH 0329/1995] process_proposal.rs: log when a proposal will be rejected --- apps/src/lib/node/ledger/shell/process_proposal.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9f6af6fd88..42fe2e8f09 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -51,6 +51,13 @@ where // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. let too_many_vext_digests = vote_ext_digest_num > 1; + if too_many_vext_digests { + tracing::warn!( + vote_ext_digest_num, + "found too many vote extension transactions, proposed block \ + will be rejected" + ); + } // Erroneous transactions were detected when processing // the leader's proposal. We allow txs that do not @@ -62,6 +69,11 @@ where ); !error.is_recoverable() }); + if invalid_txs { + tracing::warn!( + "found invalid transactions, proposed block will be rejected" + ); + } ResponseProcessProposal { status: if too_many_vext_digests || invalid_txs { From 4aad7bf95f5dab477fbafa6648a7df4bce7a2aa4 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:28:33 +0100 Subject: [PATCH 0330/1995] process_proposal.rs: log number of txs in proposed block --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 42fe2e8f09..da02744b13 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -47,6 +47,10 @@ where ) }) .collect(); + tracing::info!( + n = req.txs.len(), + "Processed transactions in proposed block", + ); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. From e11ea37085deafd4094ecf9c05be80cb0b04e372 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:30:38 +0100 Subject: [PATCH 0331/1995] protocol/mod.rs: log attempts to apply unsupported transactions --- apps/src/lib/node/ledger/protocol/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 0732799c45..13e9495db8 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -174,7 +174,11 @@ where ..Default::default() }) } - _ => { + tx_type @ _ => { + tracing::error!( + "Attempt made to apply an unsupported transaction! - {:#?}", + tx_type + ); let gas_used = block_gas_meter .finalize_transaction() .map_err(Error::GasError)?; From 01e069ab3509ba472bc6ecd2946c6dd464776449 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:32:21 +0100 Subject: [PATCH 0332/1995] protocol/mod.rs: catch empty Ethereum event transactions --- apps/src/lib/node/ledger/protocol/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 13e9495db8..5d3cddeee7 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -164,8 +164,10 @@ where .. }), .. - }) if !events.is_empty() => { - tracing::debug!("Ethereum events received"); + }) => { + if !events.is_empty() { + tracing::debug!("Ethereum events received"); + } let gas_used = block_gas_meter .finalize_transaction() .map_err(Error::GasError)?; From f73d78749507011d2c6711aef69ef59f5b810b79 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:35:44 +0100 Subject: [PATCH 0333/1995] prepare_proposal.rs: log when proposing block --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index eded60c1ad..32b8f5dc9e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -36,6 +36,7 @@ mod prepare_block { &mut self, req: RequestPrepareProposal, ) -> response::PrepareProposal { + tracing::info!("Preparing block proposal"); // We can safely reset meter, because if the block is rejected, // we'll reset again on the next proposal, until the // proposal is accepted @@ -66,6 +67,7 @@ mod prepare_block { vec![] }; + tracing::info!(n_transactions = txs.len(), "Proposing block"); response::PrepareProposal { tx_records: txs, ..Default::default() From 1fe80b8e73a0e73b77365f57095ef88034ab33f5 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:42:18 +0100 Subject: [PATCH 0334/1995] process_proposal.rs: more detailed logging --- .../lib/node/ledger/shell/process_proposal.rs | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index da02744b13..ed99ffa70f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -36,6 +36,13 @@ where &mut self, req: RequestProcessProposal, ) -> ResponseProcessProposal { + tracing::info!( + ?req.proposer_address, + req.height, + ?req.hash, + n_transactions = req.txs.len(), + "Received block proposal", + ); // the number of vote extension digests included in the block proposal let mut vote_ext_digest_num = 0; let tx_results: Vec = req @@ -47,16 +54,15 @@ where ) }) .collect(); - tracing::info!( - n = req.txs.len(), - "Processed transactions in proposed block", - ); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. let too_many_vext_digests = vote_ext_digest_num > 1; if too_many_vext_digests { tracing::warn!( + ?req.proposer_address, + req.height, + ?req.hash, vote_ext_digest_num, "found too many vote extension transactions, proposed block \ will be rejected" @@ -75,16 +81,27 @@ where }); if invalid_txs { tracing::warn!( + ?req.proposer_address, + req.height, + ?req.hash, "found invalid transactions, proposed block will be rejected" ); } + let status = if too_many_vext_digests || invalid_txs { + ProposalStatus::Reject as i32 + } else { + ProposalStatus::Accept as i32 + }; + tracing::info!( + ?req.proposer_address, + req.height, + ?req.hash, + %status, + "Processed block proposal", + ); ResponseProcessProposal { - status: if too_many_vext_digests || invalid_txs { - ProposalStatus::Reject as i32 - } else { - ProposalStatus::Accept as i32 - }, + status, tx_results, ..Default::default() } From 58c2d088932b2b76519ff4e70fbc8c77ffddd509 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:46:16 +0100 Subject: [PATCH 0335/1995] prepare_proposal.rs: better logging --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 32b8f5dc9e..576a179c8e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -36,7 +36,11 @@ mod prepare_block { &mut self, req: RequestPrepareProposal, ) -> response::PrepareProposal { - tracing::info!("Preparing block proposal"); + tracing::info!( + height = req.height, + req.txs.len = req.txs.len(), + "Preparing block proposal" + ); // We can safely reset meter, because if the block is rejected, // we'll reset again on the next proposal, until the // proposal is accepted @@ -49,14 +53,8 @@ mod prepare_block { self.build_vote_extensions_txs(req.local_last_commit); // add mempool txs - if !req.txs.is_empty() { - tracing::info!( - n = req.txs.len(), - "Received transactions from mempool" - ); - let mut mempool_txs = self.build_mempool_txs(req.txs); - txs.append(&mut mempool_txs); - } + let mut mempool_txs = self.build_mempool_txs(req.txs); + txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block let mut decrypted_txs = self.build_decrypted_txs(); @@ -67,7 +65,11 @@ mod prepare_block { vec![] }; - tracing::info!(n_transactions = txs.len(), "Proposing block"); + tracing::info!( + height = req.height, + tx_records = txs.len(), + "Proposing block" + ); response::PrepareProposal { tx_records: txs, ..Default::default() From 2b42595fad4f3aa297fd5de14893d7feb96be9cb Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:47:01 +0100 Subject: [PATCH 0336/1995] process_proposal.rs: better logging --- .../lib/node/ledger/shell/process_proposal.rs | 26 +++++++++---------- 1 file changed, 13 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 ed99ffa70f..192fc437dd 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -37,10 +37,10 @@ where req: RequestProcessProposal, ) -> ResponseProcessProposal { tracing::info!( - ?req.proposer_address, - req.height, - ?req.hash, - n_transactions = req.txs.len(), + proposer = ?req.proposer_address, + height = req.height, + hash = ?req.hash, + n_txs = req.txs.len(), "Received block proposal", ); // the number of vote extension digests included in the block proposal @@ -60,9 +60,9 @@ where let too_many_vext_digests = vote_ext_digest_num > 1; if too_many_vext_digests { tracing::warn!( - ?req.proposer_address, - req.height, - ?req.hash, + proposer = ?req.proposer_address, + height = req.height, + hash = ?req.hash, vote_ext_digest_num, "found too many vote extension transactions, proposed block \ will be rejected" @@ -81,9 +81,9 @@ where }); if invalid_txs { tracing::warn!( - ?req.proposer_address, - req.height, - ?req.hash, + proposer = ?req.proposer_address, + height = req.height, + hash = ?req.hash, "found invalid transactions, proposed block will be rejected" ); } @@ -94,9 +94,9 @@ where ProposalStatus::Accept as i32 }; tracing::info!( - ?req.proposer_address, - req.height, - ?req.hash, + proposer = ?req.proposer_address, + height = req.height, + hash = ?req.hash, %status, "Processed block proposal", ); From 353ea1e1d472fe103b32f0a2966eb2fbb5570a63 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:53:45 +0100 Subject: [PATCH 0337/1995] process_proposal.rs: clearly log if accepted or rejected --- .../lib/node/ledger/shell/process_proposal.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 192fc437dd..f40eb6446d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -37,9 +37,9 @@ where req: RequestProcessProposal, ) -> ResponseProcessProposal { tracing::info!( - proposer = ?req.proposer_address, + proposer = ?hex::encode(&req.proposer_address), height = req.height, - hash = ?req.hash, + hash = ?hex::encode(&req.hash), n_txs = req.txs.len(), "Received block proposal", ); @@ -60,9 +60,9 @@ where let too_many_vext_digests = vote_ext_digest_num > 1; if too_many_vext_digests { tracing::warn!( - proposer = ?req.proposer_address, + proposer = ?hex::encode(&req.proposer_address), height = req.height, - hash = ?req.hash, + hash = ?hex::encode(&req.hash), vote_ext_digest_num, "found too many vote extension transactions, proposed block \ will be rejected" @@ -81,27 +81,27 @@ where }); if invalid_txs { tracing::warn!( - proposer = ?req.proposer_address, + proposer = ?hex::encode(&req.proposer_address), height = req.height, - hash = ?req.hash, + hash = ?hex::encode(&req.hash), "found invalid transactions, proposed block will be rejected" ); } let status = if too_many_vext_digests || invalid_txs { - ProposalStatus::Reject as i32 + ProposalStatus::Reject } else { - ProposalStatus::Accept as i32 + ProposalStatus::Accept }; tracing::info!( - proposer = ?req.proposer_address, + proposer = ?hex::encode(&req.proposer_address), height = req.height, - hash = ?req.hash, - %status, + hash = ?hex::encode(&req.hash), + ?status, "Processed block proposal", ); ResponseProcessProposal { - status, + status: status as i32, tx_results, ..Default::default() } From 2fae2a9a8bbaa650bf7e9e381ef110cbcc5a4d26 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:54:47 +0100 Subject: [PATCH 0338/1995] prepare_proposal.rs: clearer field name when logging --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 576a179c8e..591a6579cb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -38,7 +38,7 @@ mod prepare_block { ) -> response::PrepareProposal { tracing::info!( height = req.height, - req.txs.len = req.txs.len(), + txs_from_tendermint.len = req.txs.len(), "Preparing block proposal" ); // We can safely reset meter, because if the block is rejected, From 50b2ec11888c6aa5bf79e3b89b27812ca8c031c2 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 15:57:59 +0100 Subject: [PATCH 0339/1995] process_proposal.rs: logging capitalization --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f40eb6446d..49d8463b97 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -64,7 +64,7 @@ where height = req.height, hash = ?hex::encode(&req.hash), vote_ext_digest_num, - "found too many vote extension transactions, proposed block \ + "Found too many vote extension transactions, proposed block \ will be rejected" ); } @@ -84,7 +84,7 @@ where proposer = ?hex::encode(&req.proposer_address), height = req.height, hash = ?hex::encode(&req.hash), - "found invalid transactions, proposed block will be rejected" + "Found invalid transactions, proposed block will be rejected" ); } From c7f940fa7e79a7c374a021fd647690a68499f601 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 09:51:02 +0100 Subject: [PATCH 0340/1995] Revert "Add VoteExtensionDigest::get_protocol_txs()" This reverts commit a5a2cdea6b6a73fb7e89b581b6e9f260dfaca6a4. --- shared/src/types/vote_extensions.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 361d2303d2..e47d74ecb1 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -4,7 +4,6 @@ pub mod ethereum_events; pub mod validator_set_update; use crate::proto::Signed; -use crate::types::transaction::protocol::ProtocolTxType; /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. @@ -19,7 +18,7 @@ pub struct VoteExtension { /// in [`VoteExtension`] instances. /// /// From a [`VoteExtensionDigest`] we yield two signed -/// [`ProtocolTxType`] transactions: +/// [`crate::types::transaction::protocol::ProtocolTxType`] transactions: /// - A `ProtocolTxType::EthereumEvents` tx, and /// - A `ProtocolTxType::ValidatorSetUpdate` tx pub struct VoteExtensionDigest { @@ -28,17 +27,3 @@ pub struct VoteExtensionDigest { /// The digest of validator set updates vote extension signatures. pub validator_set_update: Option, } - -impl VoteExtensionDigest { - /// Yields an iterator over the [`ProtocolTxType`] transactions - /// in this [`VoteExtensionDigest`]. - pub fn into_protocol_txs(self) -> impl Iterator { - [ - Some(ProtocolTxType::EthereumEvents(self.ethereum_events)), - self.validator_set_update - .map(ProtocolTxType::ValidatorSetUpdate), - ] - .into_iter() - .flat_map(|tx| tx) - } -} From 0e13722be96ce35e42583372e8e8c209a642839c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 09:52:34 +0100 Subject: [PATCH 0341/1995] Update Cargo.lock --- wasm_for_tests/wasm_source/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2b641df776..243993548a 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1523,6 +1523,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", From 3ee213d3aeda515d0a3c3ed5fd4ac266ac6983d1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 10:40:38 +0100 Subject: [PATCH 0342/1995] Manually implement Eq for Signed --- shared/src/proto/types.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 754361b051..157bc416a9 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -61,7 +61,7 @@ pub enum SerializeWithBorsh {} /// /// The default serialization method is [`BorshSerialize`]. #[derive( - Eq, Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] pub struct Signed { /// Arbitrary data to be signed @@ -73,6 +73,8 @@ pub struct Signed { _serialization: PhantomData, } +impl Eq for Signed {} + impl PartialEq for Signed { fn eq(&self, other: &Self) -> bool { self.data == other.data && self.sig == other.sig From 7abd271634c30a9604f6a4fff3c4aaf603c55e87 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 9 Aug 2022 12:35:11 +0100 Subject: [PATCH 0343/1995] prepare_proposal.rs: remove initial log --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 591a6579cb..1764b7f4b1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -36,11 +36,6 @@ mod prepare_block { &mut self, req: RequestPrepareProposal, ) -> response::PrepareProposal { - tracing::info!( - height = req.height, - txs_from_tendermint.len = req.txs.len(), - "Preparing block proposal" - ); // We can safely reset meter, because if the block is rejected, // we'll reset again on the next proposal, until the // proposal is accepted From 01d04238b2cb28e48edbd1ec633aa132c44f1812 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 13:52:07 +0100 Subject: [PATCH 0344/1995] Update shared/src/types/vote_extensions/validator_set_update.rs Co-authored-by: James --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index abb68f34a5..de83c35fc8 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -25,7 +25,7 @@ const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; pub struct EthAddr(pub ethereum::Address); impl BorshSerialize for EthAddr { - fn serialize( + fn serialize( &self, writer: &mut W, ) -> std::io::Result<()> { From 445bbfd31e2757f67b35ece31b4c4763dab068ba Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 14:03:33 +0100 Subject: [PATCH 0345/1995] Remove duplicated type def --- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 53 +++---------------- 2 files changed, 9 insertions(+), 45 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ce12e6704b..df4df44e8c 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -51,6 +51,7 @@ impl From for Uint { Eq, PartialOrd, Ord, + Hash, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index de83c35fc8..b8323e3d15 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -13,6 +13,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; use crate::proto::Signed; use crate::types::address::Address; +use crate::types::ethereum_events::EthAddress; use crate::types::key::common::{self, Signature}; use crate::types::key::{SigScheme, VerifySigError}; @@ -20,47 +21,6 @@ use crate::types::key::{SigScheme, VerifySigError}; const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; -/// Wrapper type for [`ethereum::Address`] -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct EthAddr(pub ethereum::Address); - -impl BorshSerialize for EthAddr { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let EthAddr(ethereum::H160(inner_array)) = self; - inner_array.serialize(writer) - } -} - -impl BorshDeserialize for EthAddr { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - let inner = <[u8; 20]>::deserialize(buf)?; - Ok(EthAddr(ethereum::H160(inner))) - } -} - -impl BorshSchema for EthAddr { - fn add_definitions_recursively( - definitions: &mut std::collections::HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - <[u8; 20]>::declaration() - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "validator_set_update::EthAddr".into() - } -} - /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] @@ -176,8 +136,8 @@ impl Vext { } } -/// Provides a mapping between [`EthAddr`] and [`VotingPower`] instances. -pub type VotingPowersMap = HashMap; +/// Provides a mapping between [`EthAddress`] and [`VotingPower`] instances. +pub type VotingPowersMap = HashMap; /// This trait contains additional methods for a [`HashMap`], related /// with validator set update vote extensions logic. @@ -238,7 +198,7 @@ impl VotingPowersMapExt for VotingPowersMap { // split the vec into two sorted .into_iter() - .map(|(&EthAddr(addr), &voting_power)| { + .map(|(&EthAddress(addr), &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power @@ -250,7 +210,10 @@ impl VotingPowersMapExt for VotingPowersMap { let voting_power = voting_power.round().to_integer(); let voting_power: ethereum::U256 = voting_power.into(); - (Token::Address(addr), Token::Uint(voting_power)) + ( + Token::Address(ethereum::H160(addr)), + Token::Uint(voting_power), + ) }) .unzip() } From 3f440f9b986b8cfef83bd2deff77f68831bc4041 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 14:12:50 +0100 Subject: [PATCH 0346/1995] Use KeccakHash wrapper type instead of raw array --- .../vote_extensions/validator_set_update.rs | 22 +++++++++---------- .../validator_set_update/encoding.rs | 10 +++++---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b8323e3d15..87c6668b36 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -13,7 +13,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::{Epoch, VotingPower}; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, KeccakHash}; use crate::types::key::common::{self, Signature}; use crate::types::key::{SigScheme, VerifySigError}; @@ -67,14 +67,14 @@ pub type SignedVext = Signed; impl SignedVext { /// Serialize a [`Vext`] to be signed. - fn serialize_vext(ext: &Vext) -> [u8; 32] { + fn serialize_vext(ext: &Vext) -> KeccakHash { AbiEncode::signed_keccak256(&[ Token::String("updateValidatorsSet".into()), Token::FixedBytes( - ext.voting_powers.get_bridge_hash(ext.epoch).to_vec(), + ext.voting_powers.get_bridge_hash(ext.epoch).0.to_vec(), ), Token::FixedBytes( - ext.voting_powers.get_governance_hash(ext.epoch).to_vec(), + ext.voting_powers.get_governance_hash(ext.epoch).0.to_vec(), ), epoch_to_token(ext.epoch), ]) @@ -85,7 +85,7 @@ impl SignedVext { /// For more information, check the Ethereum bridge smart contract code: /// - pub fn new_abi_encoded(keypair: &common::SecretKey, ext: Vext) -> Self { - let to_sign = Self::serialize_vext(&ext); + let KeccakHash(to_sign) = Self::serialize_vext(&ext); let sig = common::SigScheme::sign(keypair, &to_sign); Self::new_from(ext, sig) } @@ -96,7 +96,7 @@ impl SignedVext { &self, pk: &common::PublicKey, ) -> Result<(), VerifySigError> { - let bytes = Self::serialize_vext(&self.data); + let KeccakHash(bytes) = Self::serialize_vext(&self.data); common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) } } @@ -144,11 +144,11 @@ pub type VotingPowersMap = HashMap; pub trait VotingPowersMapExt { /// Returns the keccak hash of this [`VotingPowersMap`] /// to be signed by an Ethereum validator key. - fn get_bridge_hash(&self, epoch: Epoch) -> [u8; 32]; + fn get_bridge_hash(&self, epoch: Epoch) -> KeccakHash; /// Returns the keccak hash of this [`VotingPowersMap`] /// to be signed by an Ethereum governance key. - fn get_governance_hash(&self, epoch: Epoch) -> [u8; 32]; + fn get_governance_hash(&self, epoch: Epoch) -> KeccakHash; /// Returns the list of Ethereum validator addresses and their respective /// voting power (in this order), with an Ethereum ABI compatible encoding. @@ -157,7 +157,7 @@ pub trait VotingPowersMapExt { impl VotingPowersMapExt for VotingPowersMap { #[inline] - fn get_bridge_hash(&self, epoch: Epoch) -> [u8; 32] { + fn get_bridge_hash(&self, epoch: Epoch) -> KeccakHash { let (validators, voting_powers) = self.get_abi_encoded(); compute_hash( @@ -169,7 +169,7 @@ impl VotingPowersMapExt for VotingPowersMap { } #[inline] - fn get_governance_hash(&self, epoch: Epoch) -> [u8; 32] { + fn get_governance_hash(&self, epoch: Epoch) -> KeccakHash { compute_hash( epoch, GOVERNANCE_CONTRACT_NAMESPACE, @@ -236,7 +236,7 @@ fn compute_hash( namespace: &str, validators: Vec, voting_powers: Vec, -) -> [u8; 32] { +) -> KeccakHash { AbiEncode::keccak256(&[ Token::String(namespace.into()), Token::Array(validators), diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 215fa455e7..7f9e9ff0aa 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -6,6 +6,8 @@ pub use ethabi::token::Token; use tiny_keccak::{Hasher, Keccak}; +use crate::types::ethereum_events::KeccakHash; + /// Contains a method to encode data to a format compatible with Ethereum. pub trait Encode { /// The data type to be encoded to. Must deref to a hex string with @@ -17,20 +19,20 @@ pub trait Encode { /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. - fn keccak256(tokens: &[Token]) -> [u8; 32] { + fn keccak256(tokens: &[Token]) -> KeccakHash { let mut output = [0; 32]; let mut state = Keccak::v256(); state.update(Self::encode(tokens).as_ref().as_ref()); state.finalize(&mut output); - output + KeccakHash(output) } /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string appended to an Ethereum /// signature header. - fn signed_keccak256(tokens: &[Token]) -> [u8; 32] { + fn signed_keccak256(tokens: &[Token]) -> KeccakHash { let mut output = [0; 32]; let eth_message = { @@ -48,7 +50,7 @@ pub trait Encode { state.update(ð_message); state.finalize(&mut output); - output + KeccakHash(output) } } From 97021f854b0aa749bc878b2aab908bfcf134a11b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 15:06:45 +0100 Subject: [PATCH 0347/1995] Improve Signed API --- shared/src/proto/mod.rs | 3 +- shared/src/proto/types.rs | 37 +++++++---- .../vote_extensions/validator_set_update.rs | 66 ++++++++----------- 3 files changed, 54 insertions(+), 52 deletions(-) diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..7c092fd5e8 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -4,7 +4,8 @@ pub mod generated; mod types; pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, + Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedSerialize, + SignedTxData, Tx, }; #[cfg(test)] diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 157bc416a9..1e9a3ca861 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -52,10 +52,30 @@ pub struct SignedTxData { pub sig: common::Signature, } +/// A serialization method to provide to [`Signed`], such +/// that we may sign serialized data. +pub trait SignedSerialize { + /// A byte vector containing the serialized data. + type Output: AsRef<[u8]>; + + /// Encodes `data` as a byte vector, + /// with some arbitrary serialization method. + fn serialize(data: &T) -> Self::Output; +} + /// Tag type that indicates we should use [`BorshSerialize`] /// to sign data in a [`Signed`] wrapper. #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum SerializeWithBorsh {} +pub struct SerializeWithBorsh; + +impl SignedSerialize for SerializeWithBorsh { + type Output = Vec; + + fn serialize(data: &T) -> Vec { + data.try_to_vec() + .expect("Encoding data for signing shouldn't fail") + } +} /// A generic signed data wrapper for serialize-able types. /// @@ -125,13 +145,11 @@ impl Signed { } } -impl Signed { +impl> Signed { /// Initialize a new [`Signed`] instance. pub fn new(keypair: &common::SecretKey, data: T) -> Self { - let to_sign = data - .try_to_vec() - .expect("Encoding data for signing shouldn't fail"); - let sig = common::SigScheme::sign(keypair, &to_sign); + let to_sign = S::serialize(&data); + let sig = common::SigScheme::sign(keypair, to_sign.as_ref()); Self::new_from(data, sig) } @@ -141,11 +159,8 @@ impl Signed { &self, pk: &common::PublicKey, ) -> std::result::Result<(), VerifySigError> { - let bytes = self - .data - .try_to_vec() - .expect("Encoding data for verifying signature shouldn't fail"); - common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) + let bytes = S::serialize(&self.data); + common::SigScheme::verify_signature_raw(pk, bytes.as_ref(), &self.sig) } } diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 87c6668b36..ceee0a81f9 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -15,7 +15,6 @@ use crate::proto::Signed; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, KeccakHash}; use crate::types::key::common::{self, Signature}; -use crate::types::key::{SigScheme, VerifySigError}; // the namespace strings plugged into validator set hashes const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; @@ -65,42 +64,6 @@ impl VextDigest { /// an Ethereum key. pub type SignedVext = Signed; -impl SignedVext { - /// Serialize a [`Vext`] to be signed. - fn serialize_vext(ext: &Vext) -> KeccakHash { - AbiEncode::signed_keccak256(&[ - Token::String("updateValidatorsSet".into()), - Token::FixedBytes( - ext.voting_powers.get_bridge_hash(ext.epoch).0.to_vec(), - ), - Token::FixedBytes( - ext.voting_powers.get_governance_hash(ext.epoch).0.to_vec(), - ), - epoch_to_token(ext.epoch), - ]) - } - - /// Sign this [`Vext`] with an Ethereum key. - /// - /// For more information, check the Ethereum bridge smart contract code: - /// - - pub fn new_abi_encoded(keypair: &common::SecretKey, ext: Vext) -> Self { - let KeccakHash(to_sign) = Self::serialize_vext(&ext); - let sig = common::SigScheme::sign(keypair, &to_sign); - Self::new_from(ext, sig) - } - - /// Verify the signature of a [`Vext`], signed by some - /// Ethereum key. - pub fn verify_abi_encoded( - &self, - pk: &common::PublicKey, - ) -> Result<(), VerifySigError> { - let KeccakHash(bytes) = Self::serialize_vext(&self.data); - common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) - } -} - /// Represents a validator set update, for some new [`Epoch`]. #[derive( Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, @@ -132,7 +95,7 @@ impl Vext { /// For more information, read the docs of [`SignedVext::new`]. #[inline] pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { - SignedVext::new_abi_encoded(sk, self.clone()) + SignedVext::new(sk, self.clone()) } } @@ -250,10 +213,33 @@ fn compute_hash( mod tag { use serde::{Deserialize, Serialize}; - /// Tag type that indicates we should use [`super::encoding::AbiEncode`] + use super::encoding::{AbiEncode, Encode, Token}; + use super::{epoch_to_token, Vext, VotingPowersMapExt}; + use crate::proto::SignedSerialize; + use crate::types::ethereum_events::KeccakHash; + + /// Tag type that indicates we should use [`AbiEncode`] /// to sign data in a [`crate::proto::Signed`] wrapper. #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] - pub enum SerializeWithAbiEncode {} + pub struct SerializeWithAbiEncode; + + impl SignedSerialize for SerializeWithAbiEncode { + type Output = [u8; 32]; + + fn serialize(ext: &Vext) -> Self::Output { + let KeccakHash(output) = AbiEncode::signed_keccak256(&[ + Token::String("updateValidatorsSet".into()), + Token::FixedBytes( + ext.voting_powers.get_bridge_hash(ext.epoch).0.to_vec(), + ), + Token::FixedBytes( + ext.voting_powers.get_governance_hash(ext.epoch).0.to_vec(), + ), + epoch_to_token(ext.epoch), + ]); + output + } + } } #[doc(inline)] From 6b442cace834607473070e17db7445cb5539f0b9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 15:10:14 +0100 Subject: [PATCH 0348/1995] Improve docstring of a Vext field --- shared/src/types/vote_extensions/validator_set_update.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index ceee0a81f9..2e26d6369b 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -86,6 +86,9 @@ pub struct Vext { /// Since this is a monotonically growing sequence number, /// it is signed together with the rest of the data to /// prevent replay attacks on validator set updates. + /// + /// Additionally, we can use this [`Epoch`] value to query the appropriate + /// validator set to verify signatures with. pub epoch: Epoch, } From ff7390d8a775b68340b5e1e7a45a9f64cd6a6ec7 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 10 Aug 2022 10:27:24 +0200 Subject: [PATCH 0349/1995] [chore]: Synced up the documentation from main --- documentation/docs/book.toml | 2 +- documentation/docs/src/chapter_1.md | 1 + .../docs/src/user-guide/ledger/masp.md | 124 +++++++++- .../docs/src/user-interfaces/explorer.md | 9 + .../user-interfaces/external-integrations.md | 17 ++ .../user-interfaces/stakingAndGovernance.png | Bin 0 -> 598812 bytes .../user-interfaces/web-explorer-interface.md | 9 + .../user-interfaces/web-wallet-interface.md | 50 ++++ .../docs/src/user-interfaces/web-wallet.md | 208 +++++++++++++++++ .../web-wallet/client-application.md | 66 ++++++ .../user-interfaces/web-wallet/features.md | 51 +++++ .../src/user-interfaces/web-wallet/ibc.md | 105 +++++++++ .../interface-technical-specifications.md | 66 ++++++ .../user-interfaces/web-wallet/interface.md | 68 ++++++ .../web-wallet/key-derivation.md | 36 +++ .../user-interfaces/web-wallet/persistence.md | 42 ++++ .../src/user-interfaces/web-wallet/rpc.md | 79 +++++++ .../web-wallet/stakingAndGovernance.png | Bin 0 -> 598812 bytes .../web-wallet/transparent-transactions.md | 213 ++++++++++++++++++ .../web-wallet/user-interfaces.md | 208 +++++++++++++++++ documentation/specs/Makefile | 2 +- documentation/specs/src/SUMMARY.md | 11 +- .../specs/src/base-ledger/default-account.md | 3 + .../specs/src/base-ledger/execution.md | 7 +- .../specs/src/base-ledger/fungible-token.md | 3 + .../specs/src/base-ledger/governance.md | 135 +++++------ .../specs/src/base-ledger/multisignature.md | 3 + documentation/specs/src/economics.md | 2 +- .../specs/src/economics/fee-system.md | 19 +- .../specs/src/economics/inflation-system.md | 156 +++++-------- .../specs/src/economics/proof-of-stake.md | 18 +- .../proof-of-stake/bonding-mechanism.md | 3 +- .../src/economics/public-goods-funding.md | 16 +- .../src/economics/shielded-pool-incentives.md | 18 +- .../src/interoperability/ethereum-bridge.md | 22 +- .../specs/src/interoperability/ibc.md | 14 +- documentation/specs/src/introduction.md | 56 +++++ documentation/specs/src/masp/burn-and-mint.md | 2 +- .../specs/src/masp/convert-circuit.md | 36 ++- .../specs/src/masp/ledger-integration.md | 204 ++++++++--------- .../trusted-setup-assets/namada-ts-arch.png | Bin 0 -> 434042 bytes .../namda-ts-swimlane.png | Bin 0 -> 84319 bytes documentation/specs/src/masp/trusted-setup.md | 51 +++-- 43 files changed, 1755 insertions(+), 380 deletions(-) create mode 100644 documentation/docs/src/chapter_1.md create mode 100644 documentation/docs/src/user-interfaces/explorer.md create mode 100644 documentation/docs/src/user-interfaces/external-integrations.md create mode 100644 documentation/docs/src/user-interfaces/stakingAndGovernance.png create mode 100644 documentation/docs/src/user-interfaces/web-explorer-interface.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet-interface.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/client-application.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/features.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/ibc.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/interface-technical-specifications.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/interface.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/key-derivation.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/persistence.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/rpc.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/stakingAndGovernance.png create mode 100644 documentation/docs/src/user-interfaces/web-wallet/transparent-transactions.md create mode 100644 documentation/docs/src/user-interfaces/web-wallet/user-interfaces.md create mode 100644 documentation/specs/src/base-ledger/default-account.md create mode 100644 documentation/specs/src/base-ledger/fungible-token.md create mode 100644 documentation/specs/src/base-ledger/multisignature.md create mode 100644 documentation/specs/src/introduction.md create mode 100644 documentation/specs/src/masp/trusted-setup-assets/namada-ts-arch.png create mode 100644 documentation/specs/src/masp/trusted-setup-assets/namda-ts-swimlane.png diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index b4d124ee1b..d1a55fe006 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -1,5 +1,5 @@ [book] -authors = ["Raymond E. Pasco", "Heliax R&D Team"] +authors = ["Heliax R&D Team"] language = "en" multilingual = false site-url = "https://docs.namada.net/" diff --git a/documentation/docs/src/chapter_1.md b/documentation/docs/src/chapter_1.md new file mode 100644 index 0000000000..b743fda354 --- /dev/null +++ b/documentation/docs/src/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/documentation/docs/src/user-guide/ledger/masp.md b/documentation/docs/src/user-guide/ledger/masp.md index f658b74bec..fb866dcf49 100644 --- a/documentation/docs/src/user-guide/ledger/masp.md +++ b/documentation/docs/src/user-guide/ledger/masp.md @@ -22,11 +22,11 @@ transparent account with some token balance. Generate an implicit account: ```shell -anomaw address gen --alias [your-implicit-account-alias] +namadaw address gen --alias [your-implicit-account-alias] ``` Then, create an established account on chain using the implicit account you've just generated: ```shell -anomac init-account \ +namadac init-account \ --source [your-implicit-account-alias] \ --public-key [your-implicit-account-alias] \ --alias [your-established-account-alias] @@ -40,7 +40,7 @@ will transfer these in increments of 1000 maximum at a time. ``` ```shell -anomac transfer \ +namadac transfer \ --token btc \ --amount 1000 \ --source faucet \ @@ -54,7 +54,7 @@ Now that you have a transparent account with some tokens, you can generate a Spe You can randomly generate a new Spending Key with: ```shell -anomaw masp gen-key --alias [your-spending-key-alias] +namadaw masp gen-key --alias [your-spending-key-alias] ``` ```admonish info @@ -67,7 +67,7 @@ the same alias. To create a payment address from your Spending key, use: ```shell -anomaw masp gen-addr \ +namadaw masp gen-addr \ --key [your-spending-key-alias] \ --alias [your-payment-address-alias] ``` @@ -84,7 +84,7 @@ Once you have a payment address, transfer a balance from your transparent account to your shielded account with something like: ```shell -anomac transfer \ +namadac transfer \ --source [your-established-account-alias] \ --target [your-payment-address-alias] \ --token btc \ @@ -97,7 +97,7 @@ Once this transfer goes through, you can view your Spending Key's balance: ```shell -anomac balance --owner [your-spending-key-alias] +namadac balance --owner [your-spending-key-alias] ``` ### Shielded transfers @@ -106,7 +106,7 @@ Now that you have a shielded balance, it can be transferred to a another shielded address: ```shell -anomac transfer \ +namadac transfer \ --source [your-spending-key-alias] \ --target [some-payment-address] \ --token btc \ @@ -119,10 +119,114 @@ anomac transfer \ You can also transfer back your balance to some transparent account: ```shell -anomac transfer \ +namadac transfer \ --source [your-spending-key-alias] \ --target [some-transparent-address] \ --token btc \ --amount 50 \ --signer [your-established-account-alias] -``` \ No newline at end of file +``` + +### 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 +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 +the specified amount from the source address, and a corresponding +increase in the balance of the MASP validity predicate's address. The +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 +specified amount from the MASP validity predicate's address and a +corresponding increase in the transparent target address. The gas fee +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 +address. The gas fee is charged to the signer's address. Once the +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 +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-interfaces/explorer.md b/documentation/docs/src/user-interfaces/explorer.md new file mode 100644 index 0000000000..9c6225d674 --- /dev/null +++ b/documentation/docs/src/user-interfaces/explorer.md @@ -0,0 +1,9 @@ +# Web explorer interface + +* Block explorer + * Display PoS state + * Display governance state + * Display transparent transfers + * Display transfers in and out of the MASP + * Display total values for the MASP + * Allows tx hashes of shielded transfers to be looked up for confirmation diff --git a/documentation/docs/src/user-interfaces/external-integrations.md b/documentation/docs/src/user-interfaces/external-integrations.md new file mode 100644 index 0000000000..f6fd4855b6 --- /dev/null +++ b/documentation/docs/src/user-interfaces/external-integrations.md @@ -0,0 +1,17 @@ +# External integrations + +## Integrations + +* Custodians + * Aegies +* Indexer for Namada to integrate with block explorers +* Validator tooling +* Stakeholder support + * Rosetta integration + * Datahub integration +* WalletConnect integration +* Ledger integration +* External integrations + * Figment + * P2P + * Indexers & Explorers diff --git a/documentation/docs/src/user-interfaces/stakingAndGovernance.png b/documentation/docs/src/user-interfaces/stakingAndGovernance.png new file mode 100644 index 0000000000000000000000000000000000000000..65c990beedc57898c72b1062c27ed854b5a0371c GIT binary patch literal 598812 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

5%s5?6>?^=Sy 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

8VD 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

lDwG=if>KZz`H98n^) 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

aMLy@WzeOtL%EM}t4aK8{_aYx@q3 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 IoxAZr07&ip?*IS* literal 0 HcmV?d00001 diff --git a/documentation/docs/src/user-interfaces/web-explorer-interface.md b/documentation/docs/src/user-interfaces/web-explorer-interface.md new file mode 100644 index 0000000000..9c6225d674 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-explorer-interface.md @@ -0,0 +1,9 @@ +# Web explorer interface + +* Block explorer + * Display PoS state + * Display governance state + * Display transparent transfers + * Display transfers in and out of the MASP + * Display total values for the MASP + * Allows tx hashes of shielded transfers to be looked up for confirmation diff --git a/documentation/docs/src/user-interfaces/web-wallet-interface.md b/documentation/docs/src/user-interfaces/web-wallet-interface.md new file mode 100644 index 0000000000..615a74178f --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet-interface.md @@ -0,0 +1,50 @@ +# Web wallet interface + +## Application Features + +The application consist of the an UI that allows the user to perform the following actions in an easy to use and consistent web application: + +### Seed Phrase +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=4610%3A5890) +* Can setup a new seed phrase and derive accounts on it [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +* When creating the seed phrase, the user can export it copied to the clipboard, user has to confirm the saving of the seed phrase [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +* Restore accounts from a seed phrase +* Can retrieve a forgotten seed phrase, this requires user to enter the main password + +### User accounts +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=5165%3A8862) +* When entering the app, the user is being prompted for a password to decrypt the key pair to be able to perform actions [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +* Can create accounts derived from the master key pair +* Can delete accounts +* User can integrated with Ledger hardware wallet + * Set up flow TBD + * Managing TBD +* Can see an overview of the assets in the account and all derived accounts [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +* Can see the details of a single asset, containing the following information [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) + * Balance + * All past transactions for the current account and asset + * Button to initiate a new transfer using this asset + +### Transfers +[TBD]() +* Can create transparent transfers +* Can create shielded transfers +* Bi-directional transfer between Namada and ETH + * Supports approving transactions with MetaMask +* Bi-directional transfer between Namada and IBC supported chains + * Supports approving transactions with Keplr + +### Staking & Governance +[TBD]() +* Can bond funds to a list of validators +* Can un-bond funds to a list of validators +* Can submit proposals +* Can vote on proposals +* Can follow up with the current and past proposals and their vote results + +## Tech Stack +### Core Application +* Core application is built on React/TypeScript +* State management with Redux +* Application styling is accomplished with styled-components +* Extensive usage of WASM compiled Rust code from the common Anoma code base is encouraged where ever feasible. diff --git a/documentation/docs/src/user-interfaces/web-wallet.md b/documentation/docs/src/user-interfaces/web-wallet.md new file mode 100644 index 0000000000..370a0a5397 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet.md @@ -0,0 +1,208 @@ +

Web Wallet UI and Features

+ +- [LockScreen](#lockscreen) + - [LockScreen](#lockscreen-1) +- [AccountOverview](#accountoverview) + - [AccountOverview](#accountoverview-1) + - [AccountOverview/TokenDetails](#accountoverviewtokendetails) + - [AccountOverview/TokenDetails/Receive](#accountoverviewtokendetailsreceive) + - [AccountOverview/TokenDetails/Send](#accountoverviewtokendetailssend) +- [StakingAndGovernance](#stakingandgovernance) + - [StakingAndGovernance](#stakingandgovernance-1) + - [StakingAndGovernance/Staking](#stakingandgovernancestaking) + - [StakingAndGovernance/ValidatorDetails](#stakingandgovernancevalidatordetails) + - [StakingAndGovernance/Proposals](#stakingandgovernanceproposals) + - [StakingAndGovernance/Proposals/AddProposal](#stakingandgovernanceproposalsaddproposal) +- [Settings](#settings) + - [Settings](#settings-1) + - [Settings/WalletSettings](#settingswalletsettings) + - [Settings/Accounts](#settingsaccounts) + - [Settings/Accounts/NewAccount](#settingsaccountsnewaccount) + - [Settings/AccountSettings](#settingsaccountsettings) + +The application is divided to 4 main sections: +* LockScreen +* AccountOverview +* StakingAndGovernance +* Settings + +These are further divided to individual screens or flows (comprising several screens) grouping activities that belong together. For example, under **StakingAndGovernance** we have: + +* **StakingAndGovernance/Staking** - which gives the user the possibility to see all the validators and navigate to a screen where the actual staking is performed. + +Each screen listed below is associated with a high level wireframe design to give a visual presentation of the user interface. Each view is named and being referred with that name through out all communication and in the codebase. + + + +*This screen represents StakingAndGovernance/Staking view* + + +## LockScreen +When the user accesses the wallet for the first time there is a need to create a new account. This screen gives the user to possibility to do so or unlock the wallet by using an existing account. + +### LockScreen +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +User can: +* can to unlock the wallet by entering the master password +* can to start a flow to create a new account + +## AccountOverview +This is the most important part of the application and the part where the user spends the most time. Here the user performs the most common tasks such as creating transactions. Only one account is selected as a time and the selected account is indicated here. + +### AccountOverview +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +User can: +* see the aggregated balance in fiat currency +* can see the currently selected account address +* can navigate to **Settings/Accounts** for changing the account +* can see a listing of all hold tokens and their logos, balances, names + + +### AccountOverview/TokenDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) +User can: +* can see the balance of token in native and fiat currency +* can navigate to **AccountOverview/TokenDetails/Receive** for receiving tokens +* can navigate to **AccountOverview/TokenDetails/Send** for sending tokens +* can see a listing of past transaction of the current account and selected token + +### AccountOverview/TokenDetails/Receive +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A6476) +User can: +* see QR code of the address +* see address as a string and copy it by clicking button + +### AccountOverview/TokenDetails/Send +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9579) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9715) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9797) +User can: +view 1: +* see the balance of the token in current account +* enter details: transfer amount, recipient address, memo +* can select to perform the transaction as shielded + +view 2: +* see a summary of the transaction details +* clear indication whether the transaction is transparent of shielded +* select a gas fee +* see an option in gas fees that is specific for shielded transactions +* see a transaction summary including gas fee + +view 3: +* see a confirmation once the transaction is confirmed +* be abel to navigate to see the new transaction in the block explorer +* be able to navigate back to **AccountOverview/TokenDetails** + + + +## StakingAndGovernance +Aside of **AccountOverview** this is a part that the user is likely visiting quite frequently. All staking and governance related activities are performed here. + +### StakingAndGovernance +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6316) +User can: +* see a dashboard with the most interesting information regarding staking +* see a dashboard with the most interesting information regarding governance +* can navigate to **StakingAndGovernance/Staking** for performing staking actions +* can navigate to **StakingAndGovernance/Proposals** for performing governance actions + +### StakingAndGovernance/Staking +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6377) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14001) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14101) +User can: +view 1: +* view a listing of validators +* be able to navigate to aaa for seeing further details about the validator +* select to stake with one of them + +view 2: +* select an amount to stake +* see a summary of the staking transaction + +view 3: +* see a confirmation of a successful staking with the selected validator + +### StakingAndGovernance/ValidatorDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13919) +User can: +* can see all relevant details of the validator + +### StakingAndGovernance/Proposals +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14167) +User can: +* see a listing of all open proposals +* be able to vote for yes, no, no with veto and abstain +* see the current vote share per proposal +* navigate to **StakingAndGovernance/Proposals/AddProposal** for adding a new proposal + +### StakingAndGovernance/Proposals/AddProposal +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14286) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14405) +User can: +view 1: +* enter the details (TBD) of the proposal +* see a summary of the proposal +* submit the proposal + +view 2: +* see a confirmation of successfully submitted proposal + +## Settings +This is a part of the application that is visited less often. This is where the user can change settings of select the active account. + +### Settings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13327) +User can: +* Navigate to **Settings/Accounts** +* Navigate to **Settings/WalletSettings** + +### Settings/WalletSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6235) +User can: +* see and change the fiat currency to display in various locations in the app where amounts are being displayed in fiat currency +* Default fiat currency is USD + +### Settings/Accounts +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9901) +User can: +* select an account by clicking it, when it becomes visibly selected +* can navigate to **Settings/AccountSettings** for changing the settings of certain account +* can navigate to Settings/Accounts/NewAccount/Start for adding a new account to the wallet + + +### Settings/Accounts/NewAccount +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5956) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) +[Wireframe 4](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +[Wireframe 5](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6190) +User can: + +view 1: +* see a welcome screen that explain the flow + +view 2: +* enter an alias to the account +* enter and confirm a password +* select the length of the seed phrase (12 or 24 words) + +view 3: +* see a seed phrase that was generated +* copy the seed phrase to clipboard + +view 4: +* enter a randomly requested word from the set of words. ("please enter word #5") + +view 5: +* see a confirmation that the account was created +* navigate to **AccountOverview** and so that the newly created account becomes the selected account + +### Settings/AccountSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A10076) +User can: +* Rename the selected account +* display the seed phrase, user is being prompted for a password +* delete account, user is prompted to input a security text to prevent an accidental deletion +* select the network \ No newline at end of file diff --git a/documentation/docs/src/user-interfaces/web-wallet/client-application.md b/documentation/docs/src/user-interfaces/web-wallet/client-application.md new file mode 100644 index 0000000000..d586fef8e4 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/client-application.md @@ -0,0 +1,66 @@ +# Client Application + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web + +``` +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` diff --git a/documentation/docs/src/user-interfaces/web-wallet/features.md b/documentation/docs/src/user-interfaces/web-wallet/features.md new file mode 100644 index 0000000000..45f9a8f120 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/features.md @@ -0,0 +1,51 @@ +# Web Wallet + +## Application Features + +The application consist of the an UI that allows the user to perform the following actions in an easy to use and consistent web application: + +### Seed Phrase + +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=4610%3A5890) + +- Can setup a new seed phrase and derive accounts on it [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +- When creating the seed phrase, the user can export it copied to the clipboard, user has to confirm the saving of the seed phrase [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +- Restore accounts from a seed phrase +- Can retrieve a forgotten seed phrase, this requires user to enter the main password + +### User accounts + +[hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=5165%3A8862) + +- When entering the app, the user is being prompted for a password to decrypt the key pair to be able to perform actions [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +- Can create accounts derived from the master key pair +- Can delete accounts +- User can integrated with Ledger hardware wallet + - Set up flow TBD + - Managing TBD +- Can see an overview of the assets in the account and all derived accounts [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +- Can see the details of a single asset, containing the following information [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) + - Balance + - All past transactions for the current account and asset + - Button to initiate a new transfer using this asset + +### Transfers + +[TBD]() + +- Can create transparent transfers +- Can create shielded transfers +- Bi-directional transfer between Namada and ETH + - Supports approving transactions with MetaMask +- Bi-directional transfer between Namada and IBC supported chains + - Supports approving transactions with Keplr + +### Staking & Governance + +[TBD]() + +- Can bond funds to a list of validators +- Can un-bond funds to a list of validators +- Can submit proposals +- Can vote on proposals +- Can follow up with the current and past proposals and their vote results diff --git a/documentation/docs/src/user-interfaces/web-wallet/ibc.md b/documentation/docs/src/user-interfaces/web-wallet/ibc.md new file mode 100644 index 0000000000..00364674d8 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/ibc.md @@ -0,0 +1,105 @@ +## IBC Protocol + +The web wallet must be able to transfer token amounts to other chains via the Inter-Blockchain Communication Protocol (IBC). + +We need to be able to support the following: + +- Fungible token transfer (ICS020) from Namada to other Anoma chains +- Fungible token transfer (ICS020) from Namada to Cosmos + +What the UI will need to display to the user: + +- Select a chain (chain ID) as destination +- Enter a channel ID for destination (e.g., `channel-0`) +- Specify a receiver address +- Specify a token +- Specify an amount to transfer + +The web wallet will need to construct a `MsgTransfer` struct, which will get wrapped in a normal, signed transaction and broadcasted to the source ledger (this struct is passed into the `Tx` `data`): + +```rust +MsgTransfer { + source_port: String, + source_channel: String, + token: Option, + sender: Signer, + receiver: Signer, + timeout_height: Height, + timeout_timestamp: Timestamp +} +``` + +A populated `MsgTransfer` with a disabled block-height timeout (instead using a timestamp timeout), may look like the following: + +```rust +MsgTransfer { + source_port: PortId("transfer"), + source_channel: ChannelId("channel-0"), + token: Some(Coin { + denom: "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5", + amount: "1.23456" + }), + sender: Signer( "atest1v4ehgw36xvmrgdfsg9rrwdzxgfprq32yxvensdjxgcurxwpeg5mrxdpjxfp5gdp3xqu5gs2xd8k4aj" + ), + receiver: Signer( "atest1d9khqw36xu6njwp4x5eyz334g4zrjvz9gyungv6p8yurys3jxymrxvzy89pyzv2pxaprzsfedvglv2" + ), + timeout_height: Height { + revision: 0, + height: 0 + }, + timeout_timestamp: Timestamp { + time: Some(Time(PrimitiveDateTime { + date: Date { + year: 2022, + ordinal: 124 + }, + time: Time { + hour: 14, + minute: 15, + second: 33, + nanosecond: 0 + } + })) + } +} +``` + +**NOTE** Unlike with `tx_transfer`, the amount we pass with the Token is _not_ submitted in micro-units, but as a regular `f32` value. No conversion is needed in the web wallet. + +Once this transaction is unwrapped and validated, `apply_tx` will invoke `IBC.dispatch()` (see: ). + +When this is executed on the source chain, the balance will be deducted on the source account, so we need to reflect this in the interface. If the transaction succeeds, query +the balance for that token and display to the user. + +## Testing + +Instructions for setting up local Namada chains, along with the Hermes relatyer (`ibc-rs`) can be found here: + + + +The wallet UI will need to be configured to connect to the source chain from which you want to transfer tokens. The user will have to enter a valid channel ID +in the interface, in addition to an established address on the destination chain (the receiver). + +## Configuration + +The wallet web app should accept a configuration per-environment that will contain not only the default network, but the possible destination networks that the user can transfer tokens to. We need the following information for each, at a minimum: + +- A user-friendly alias naming the network +- Destination URL +- Destination Port +- A non-default `portId`, if necessary, though in most cases, the default of `transfer` would likely be used. + +## Resources + +- [Anoma Ledger IBC Rust Docs](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/) +- [HackMD IBC Summary](https://hackmd.io/H2yGO3IQRLiWCPWwQQdVow) +- [ibc-rs](https://github.com/informalsystems/ibc-rs/) +- [ICS020 - Fungible Token Transfers](https://github.com/cosmos/ibc/blob/master/spec/app/ics-020-fungible-token-transfer/README.md) +- +- +- + +Cosmos relayers: + +- +- diff --git a/documentation/docs/src/user-interfaces/web-wallet/interface-technical-specifications.md b/documentation/docs/src/user-interfaces/web-wallet/interface-technical-specifications.md new file mode 100644 index 0000000000..cef534ccb3 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/interface-technical-specifications.md @@ -0,0 +1,66 @@ +# Interface Technical Specifications + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web + +``` +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` \ No newline at end of file diff --git a/documentation/docs/src/user-interfaces/web-wallet/interface.md b/documentation/docs/src/user-interfaces/web-wallet/interface.md new file mode 100644 index 0000000000..0d0a2f773c --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/interface.md @@ -0,0 +1,68 @@ +# Web Wallet + +## Interface Technical Specifications + +### React Web Application + +- Built with TypeScript +- State-management with Redux Toolkit (`@reduxjs/toolkit`) +- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management) +- `wasm-react-scripts` - enabling WebAssembly files into the Webpack pipeline +- Styled-Componenents for all application/component styling + +## WebAssembly Library + +Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript. + +The primary functionality that we currently pull from `anoma` involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created `anoma-lib`, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript. + +The Rust source code `anoma-lib` is structured as follows: + +```bash +. +├── types +│ ├── address.rs +│ ├── keypair.rs +│ ├── mod.rs +│ ├── transaction.rs +│ ├── tx.rs +│ └── wrapper.rs +├── account.rs +├── lib.rs +├── transfer.rs +├── utils.rs +``` + +Here, we have several types that are essentially built on top of `anoma` types, allowing us to interface easily from the client app, such as `address`, `keypair`, `tx`, and `wrapper`, then a generic `transaction` type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the `anoma` types require entirely within the wasm, then later translate the results into something the client can understand. + +Outside of types, we have an `account.rs` file that allows us to call account functions, such as `initialize` (to construct an "init-account" transaction), from the client app. `transfer.rs` is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by `types/transaction`, and any types that need extra work in order to be useful to the client being added as well to `types`. + +## Interfacing between the Client and WebAssembly + +When compiling the `wasm` utilizing `wasm-pack`, we get the associated JavaScript source to interact with the WebAssembly output, as well as a TypeScript type definition file. When we set the `wasm-pack` target to `web`, we get an additional exported `init` function, which is a promise that resolves when the wasm is fully loaded, exposing the `memory` variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the `init()` call, we can immediately execute any of the wasm methods. + +In the case of `anoma-lib`, there is a corresponding class that initializes and exposes the features of the wasm in `anoma-wallet`, called `AnomaClient`. (**NOTE**: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well. + +The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app. + +### Dealing with Rust types in TypeScript + +One of the challenges of working with WebAssembly is how we might go about handling types from Rust code. We are limited to what JavaScript can handle, and often when serializing output from the wasm, we'll choose a simple type like `string` or `number`, or send the data as a byte array (very common, especially when dealing with numbers larger than JavaScript can handle by default). Sending raw data to the client is often a decent solution, then any encoding we prefer we can enact on the client-side (hexadecimal, base58, base64, etc), and choosing a Rust type like `Vec` makes this straight-forward. _(More to come on this topic in the future)_ + +There is much more nuance to handling types from Rust wasm in TypeScript when working with `wasm-bindgen`, and more information can be found at the following URL: + +https://rustwasm.github.io/wasm-bindgen/reference/types.html + +## Testing with WebAssembly + +The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our `wasm-pack` target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web +``` + +We would issue this in order to support Jest in NodeJS: + +```bash +wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target nodejs +``` diff --git a/documentation/docs/src/user-interfaces/web-wallet/key-derivation.md b/documentation/docs/src/user-interfaces/web-wallet/key-derivation.md new file mode 100644 index 0000000000..c478319713 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/key-derivation.md @@ -0,0 +1,36 @@ +## Key Derivation (transparent addresses) + +Given a master seed (a 12 or 24 word `bip39` mnemonic), the user should be able to derive additional accounts deterministically. + +The wallet currently implements functionality to derive `bip32` addresses following `bip44` paths for [slip-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) registered coin types, using hardened addresses. + +The bulk of this funcionality resides in `anoma-apps/anoma-lib/lib/src/wallet.rs` (https://github.com/heliaxdev/anoma-apps/blob/main/packages/anoma-lib/lib/src/wallet.rs). Creating a new `Wallet` struct with a provided mnemonic generates a seed byte vector and establishes a root extended key. Calling the `derive` method on that `Wallet` providing a derivation path will give us the following struct: + +```rust +pub struct DerivedAccount { + address: String, // p2pkh address + wif: String, // Address in Wallet Import Format (WIF) + private_key: Vec, // Extended Private key + public_key: Vec, // Extended Public key + secret: Vec, // ed25519 secret key + public: Vec, // ed25519 public key +} +``` + +The ed25519 keys can then be used to initialize an account on the ledger to receive an Established Address. + +## Deriving Shielded Addresses + +_TBD_ + +## Resources + +- [BIP32 spec for hierarchical deterministric wallets](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) +- [BIP39 spec for mnemonic seeds](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) +- [BIP44 spec for hierarchical deterministic wallets](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) +- [LedgerHQ - BIP44](https://github.com/LedgerHQ/ledger-live-common/blob/master/docs/derivation.md) +- [SLIP-0044 Registered Coin Types](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) +- [Mnemonic Code Converter](https://iancoleman.io/bip39/) - Useful online utilities to verify derived addresses and keys from specified mnemonic +- [Rust bip32](https://docs.rs/bip32/latest/bip32/) +- [Rust bip0039](https://github.com/koushiro/bip0039) +- [Rust bitcoin](https://github.com/rust-bitcoin/rust-bitcoin) diff --git a/documentation/docs/src/user-interfaces/web-wallet/persistence.md b/documentation/docs/src/user-interfaces/web-wallet/persistence.md new file mode 100644 index 0000000000..eb9c84c903 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/persistence.md @@ -0,0 +1,42 @@ +## Persistence of User Wallet + +The state of the user's wallet, consisting of their master seed, along with any accounts derived from that seed, should be stored locally in a safe manner. As this requires the use of `localStorage`, all data should be encrypted. + +Presently, this challenge is being addressed by using the user's password (specified when creating their master seed) to encrypt/decrypt the mnemonic seed, as well as unlocking the state of their wallet. The accounts in the state are being persisted via [redux-persist](https://github.com/rt2zz/redux-persist), with an [ecryption transform](https://github.com/maxdeviant/redux-persist-transform-encrypt) that handles the encrypting and decrypting of all data stored in `localStorage`. + +The mnemonic is stored separately from the accounts data. In `anoma-apps/packages/anoma-lib/lib/types/mnemonic.rs` implementation of `Mnemonic`, we provide the ability to specify a password allowing us to retrieve a storage value of the mnemonic, which is encrypted before saving to `localStorage`. When the wallet is locked, the user must provide a password, which is validated by attempting to decrypt the stored mnemonic. If successful, the password is used to either generate an encrypted Redux persistence layer, or decrypt the existing one, restoring the user's wallet state. + +`redux-persist` gives us the ability to specify which sub-sections of the state should be persisted. Presently, this is only enabled for any derived account data. From the persisted store, we can establish a `persistor`, which can be passed into a `PersistGate` component that will only display its children once the state is retrieved and decrypted from storage. + +If we wanted to export the state of the user's accounts, this would be trivial, and simply a matter of exporting a JSON file containing the `JSON.stringify`ed version of their accounts state. Some work would need to be done in order to restore the data into Redux, however. + +The `localStorage` state is stored in one of three places, depending on your environment: + +- `persist:anoma-wallet` - Production +- `persist:anoma-wallet-dev` - Devnet +- `persist:anoma-wallet-local` - Local ledger + +This allows us to keep our wallet state in sync with multiple ledgers while testing. + +## Restoring the accounts state from file + +The user should have the ability to save the state of their accounts in their wallet to a JSON file. It is relatively trivial to take a snapshot of the accounts state once the user is authenticated. + +Technically, this will likely involve a process by which, following the upload of the file and successful parsing, the existing `persist:anoma-wallet` storage is cleared, and when the store is initialized, we pass the parsed accounts state in to `configureStore` by way of the `preloadedState` parameter. This will only happen once, and on subsequent calls to the `makeStore` function, it should hydrate from the encrypted value in local storage. + +Refer to the following to see how our present `makeStore` Redux store factory functions: + +https://github.com/heliaxdev/anoma-apps/blob/9551d9d0f20b291214357bc7f4a5ddc46bdc8ee0/packages/anoma-wallet/src/store/store.ts#L18-L50 + +This method currently accepts a `secretKey` as required by the `encryptTransform`, and checks the environment variables `REACT_APP_LOCAL` and `NODE_ENV` to determine where the store gets saved in `localStorage`. This is mostly useful for local testing where you may want to switch between connecting to a local ledger or a testnet, and want to keep your local stores in sync with both. + +## Challenges + +As a secret is required to unlock the persisted store, this store must be instantiated dynamically once a password is entered and validated. In the current implementation of the wallet, any routes that will make use of the Redux store are loaded asynchronously. When they are loaded, the store is initialized with the user's password (which is passed in through the Context API in React, separate from the Redux state). + +## Resources + +- [redux-persist](https://github.com/rt2zz/redux-persist) - Redux store persistence +- [redux-persist-transform-encrypt](https://github.com/maxdeviant/redux-persist-transform-encrypt) - Transform to encrypt persisted state +- [Notes on initial data in Redux](https://dev.to/lawrence_eagles/how-to-properly-set-initial-state-in-redux-78m) +- [Notes on clearing persisted Redux state](https://bionicjulia.com/blog/clear-redux-toolkit-state-with-redux-persist-and-typescript) diff --git a/documentation/docs/src/user-interfaces/web-wallet/rpc.md b/documentation/docs/src/user-interfaces/web-wallet/rpc.md new file mode 100644 index 0000000000..b5f2e8b5e3 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/rpc.md @@ -0,0 +1,79 @@ +## Using JSON RPC to Communicate with Ledger + +To query values from the ledger, the web-wallet must issue JSON RPC calls to the **Tendermint** `abci_query` endpoint over HTTP, which if running the ledger locally, would look like: + +``` +http://localhost:26657/abci_query/ +``` + +Similarly, when broadcasting transactions, we must communicate with the ledger over websockets to an endpoint such as: + +``` +ws://localhost:26657/websocket/ +``` + +To handle this in the wallet, we can make use of existing functionality from `cosmjs`, namely, the `RpcClient` and `WebsocketClient`. + +### RPC HTTP Client + +Over HTTP, using the `abci_query` endpoint, we can query the ledger by providing a `path` to the storage value we wish to query. Here are some examples: + +- Query balance: `value/#{token_address}/balance/#{owner_address}` +- Query epoch: `epoch` +- Is known address?: `has_key/#{address}/?` + +There are many other types of queries in addition to `abci_query` that can be issued to Tendermint. See [https://docs.tendermint.com/master/rpc/](https://docs.tendermint.com/master/rpc/) for more information. + +### WebSocket Client + +The most interesting type of interaction with the ledger thus far is via WebSockets. The goal of the implementation in `anoma-wallet` is to allow us to provide listeners so that we can update the React app according to activity on the ledger. The core functionality of the implementation on the client is as follows: + +```ts +public async broadcastTx( + hash: string, + tx: Uint8Array, + { onBroadcast, onNext, onError, onComplete }: SubscriptionParams +): Promise { + if (!this._client) { + this.connect(); + } + + try { + const queries = [`tm.event='NewBlock'`, `${TxResponse.Hash}='${hash}'`]; + this.client + ?.execute( + createJsonRpcRequest("broadcast_tx_sync", { tx: toBase64(tx) }) + ) + .then(onBroadcast) + .catch(onError); + + this.client + ?.listen( + createJsonRpcRequest("subscribe", { + query: queries.join(" AND "), + }) + ) + .addListener({ + next: onNext, + error: onError, + complete: onComplete, + }); + + return Promise.resolve(this); + } catch (e) { + return Promise.reject(e); + } +} +``` + +There are a few key things happening here. Once we have constructed a transaction, we receive a transaction `hash` and a `Uint8Array` containing the bytes of the wrapped and signed transaction. We first execute the request to `broadcast_tx_sync`, which can take an `onBroadcast` callback from the client to listen to the initial response from the ledger. We provide the `tx` data in `base64` format as an argument. + +Following that, we subcribe to events on the ledger using a query containing `tm.event='NewBlock' AND applied.hash='transaction_hash_value'`, then then register the following listeners so that we may trigger activity in the front-end app: + +- `onNext` - called when we receive a `NewBlock` event that matches our `hash` +- `onError` - called in the event of an error +- `onComplete` - called when the websocket closes + +The way this library in `anoma-wallet/src/lib/` is implemented, we can also determine when we want to disconnect the WebSocket. For instance, if for some reason we want to issue a series of transactions in succession, we could feasibly leave the connection open, then close after the final transaction is complete. Alternatively, and in most cases, we would simply close the connection when we are finished with a single transaction, which would then trigger the `onComplete` callback. + +See [Transparent Transactions](./transparent-transactions.md) for more information on how the transactions are initially constructed. diff --git a/documentation/docs/src/user-interfaces/web-wallet/stakingAndGovernance.png b/documentation/docs/src/user-interfaces/web-wallet/stakingAndGovernance.png new file mode 100644 index 0000000000000000000000000000000000000000..65c990beedc57898c72b1062c27ed854b5a0371c GIT binary patch literal 598812 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

5%s5?6>?^=Sy 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

lDwG=if>KZz`H98n^) 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

aMLy@WzeOtL%EM}t4aK8{_aYx@q3 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 IoxAZr07&ip?*IS* literal 0 HcmV?d00001 diff --git a/documentation/docs/src/user-interfaces/web-wallet/transparent-transactions.md b/documentation/docs/src/user-interfaces/web-wallet/transparent-transactions.md new file mode 100644 index 0000000000..6f65c5be63 --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/transparent-transactions.md @@ -0,0 +1,213 @@ +# Transparent Transactions + +#### Table of Contents + +- [Transfer Transactions](#part-1---token-transfer-transactions) +- [Initialize Account Transactions](#part-2---initialize-account-transaction) +- [Submitting Transactions](#submitting-transparent-transactions) + +## Constructing Transparent Transactions + +The web-wallet will need to support many transactions. As the data that gets submitted to the ledger is most easily constructed from `anoma` types, we perform the assembly of the transaction with in WebAssembly using Rust so that we may natively interact with `anoma`. The role of wasm in this scenario is to provide two pieces of data to the client (which will handle the broadcasting of the transaction), which are: + +1. `hash` - the hash of the transaction +2. `data` - A byte array of the final wrapped and signed transaction + +The following outlines how we can construct these transactions before returning them to the client. + +## Part 1 - Token Transfer Transactions + +There are a few steps involved in creating and signing a transaction: + +1. Create an `anoma::proto::Tx struct` and sign it with a keypair +2. Wrap Tx with a `anoma::types::transaction::WrapperTx` struct which encrypts the transaction +3. Create a new `anoma::proto::Tx` with the new `WrapperTx` as data, and sign it with a keypair (this will be broadcast to the ledger) + +### 1.1 - Creating the `anoma::proto::Tx` struct + +The requirements for creating this struct are as follow: + +- A pre-built wasm in the form of a byte array (this is loaded in the client as a `Uint8Array` type to pass to the wasm) +- A serialized `anoma::types::token::Transfer` object which contains the following: + - `source` - source address derived from keypair + - `target` - target address + - `token` - token address + - `amount` - amount to transfer +- A UTC timestamp. _NOTE_ this is created when calling `proto::Tx::new()`, however, this is incompatible with the wasm in runtime (`time` is undefined). Therefore, we need to get a valid timestamp from `js_sys`: + +```rust +// anoma-lib/src/util.rs + +pub fn get_timestamp() -> DateTimeUtc { + let now = js_sys::Date::new_0(); + + let year = now.get_utc_full_year() as i32; + let month: u32 = now.get_utc_month() + 1; + let day: u32 = now.get_utc_date(); + let hour: u32 = now.get_utc_hours(); + let min: u32 = now.get_utc_minutes(); + let sec: u32 = now.get_utc_seconds(); + + let utc = Utc.ymd(year, month, day).and_hms(hour, min, sec); + DateTimeUtc(utc) +} +``` + +#### Creating the `types::token::Transfer` struct to pass in as data: + +_In wasm:_ + +```rust +// anoma-lib/src/transfer.rs + +let transfer = token::Transfer { + source: source.0, + target: target.0, + token: token.0.clone(), + amount, +}; + +// The data we pass to proto::Tx::new +let data = transfer + .try_to_vec() + .expect("Encoding unsigned transfer shouldn't fail"); +``` + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L406-L411 + + +#### Creating and signing the `proto::Tx` struct + +_In wasm:_ + +```rust +// anoma-lib/src/types/tx.rs + +impl Tx { + pub fn new(tx_code: Vec, data: Vec) -> proto::Tx { + proto::Tx { + code: tx_code, + data: Some(data), + timestamp: utils::get_timestamp(), + } + } +} +``` + +**NOTE** Here we provide a work around to an issue with `proto::Tx::new()` in wasm - instead of calling the method directly on `Tx`, we create a new implementation that returns a `proto::Tx`, with the timestamp being set using `js_sys` in order to make this wasm-compatible. + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L417-L419 + + +### 1.2 - Creating the `anoma::types::transaction::WrapperTx` struct + +The requirements for creating this struct are as follows: + +- A `transaction::Fee` type, which contains: + - `amount` - the Fee amount + - `token` - the address of the token +- `epoch` - The ID of the epoch from query +- `gas_limit` - This contains a `u64` value representing the gas limit +- `tx` - the `proto::Tx` type we created earlier. + +_In wasm:_ + +```rust +// anoma-lib/src/types/wrapper.rs + +transaction::WrapperTx::new( + transaction::Fee { + amount, + token: token.0, + }, + &keypair, + storage::Epoch(u64::from(epoch)), + transaction::GasLimit::from(gas_limit), + tx, +) +``` + +**NOTE** Here we can directly invoke `WrapperTx::new`, so we only need to concern ourselves with convering the JavaScript-provided values into the appropriate types. + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L687-L696 + +#### 1.3 - Create a new `Tx` with `WrapperTx` and sign it + +Here we create a `WrapperTx` type, and with that we create a new `Tx` type (our _wrapped_ `Tx` type) with the `WrapperTx` as the `data`, and empty `vec![]` for `code`, and a new `timestamp`, and then we sign it. + +_In wasm:_ + +```rust +// anoma-lib/src/types/wrapper.rs -> sign() + +(Tx::new( + vec![], + transaction::TxType::Wrapper(wrapper_tx) + .clone() + .try_to_vec().expect("Could not serialize WrapperTx") +)).sign(&keypair) +``` + +We can summarize a high-level overview of the entire process from the `anoma-lib/src/types/transaction.rs` implementation: + +```rust +let source_keypair = Keypair::deserialize(serialized_keypair)?; +let keypair = key::ed25519::Keypair::from_bytes(&source_keypair.to_bytes()) + .expect("Could not create keypair from bytes"); + +let tx = Tx::new( + tx_code, + data, +).sign(&keypair); + +let wrapper_tx = WrapperTx::new( + token, + fee_amount, + &keypair, + epoch, + gas_limit, + tx, +); + +let hash = wrapper_tx.tx_hash.to_string(); +let wrapper_tx = WrapperTx::sign(wrapper_tx, &keypair); +let bytes = wrapper_tx.to_bytes(); + +// Return serialized wrapped & signed transaction as bytes with hash +// in a tuple: +Ok(Transaction { + hash, + bytes, +}) +``` + +_In Anoma CLI:_ +https://github.com/anoma/anoma/blob/f6e78278608aaef253617885bb7ef95a50057268/apps/src/lib/client/tx.rs#L810-L814 + + +## Part 2 - Initialize Account Transaction + +Constructing an Initialize Account transaction follows a similar process to a transfer, however, in addition to providing a `tx_init_account` wasm, we need to provide the `vp_user` wasm as well, as this is required when constructing the transaction: + +```rust +// anoma-lib/src/account.rs + +let vp_code: Vec = vp_code.to_vec(); +let keypair = &Keypair::deserialize(serialized_keypair.clone()) + .expect("Keypair could not be deserialized"); +let public_key = PublicKey::from(keypair.0.public.clone()); + +let data = InitAccount { + public_key, + vp_code: vp_code.clone(), +}; +``` + +Following this, we will pass `data` into to our new transaction as before, along with `tx_code` and required values for `WrapperTx`, returning the final result in a `JsValue` containing the transaction hash and returned byte array. + +## Submitting Transparent Transactions + +See [RPC](./rpc.md) for more information on HTTP and WebSocket RPC interaction with ledger. diff --git a/documentation/docs/src/user-interfaces/web-wallet/user-interfaces.md b/documentation/docs/src/user-interfaces/web-wallet/user-interfaces.md new file mode 100644 index 0000000000..d4047fe34a --- /dev/null +++ b/documentation/docs/src/user-interfaces/web-wallet/user-interfaces.md @@ -0,0 +1,208 @@ +

Namada wallet user interface

+ +- [LockScreen](#lockscreen) + - [LockScreen](#lockscreen-1) +- [AccountOverview](#accountoverview) + - [AccountOverview](#accountoverview-1) + - [AccountOverview/TokenDetails](#accountoverviewtokendetails) + - [AccountOverview/TokenDetails/Receive](#accountoverviewtokendetailsreceive) + - [AccountOverview/TokenDetails/Send](#accountoverviewtokendetailssend) +- [StakingAndGovernance](#stakingandgovernance) + - [StakingAndGovernance](#stakingandgovernance-1) + - [StakingAndGovernance/Staking](#stakingandgovernancestaking) + - [StakingAndGovernance/ValidatorDetails](#stakingandgovernancevalidatordetails) + - [StakingAndGovernance/Proposals](#stakingandgovernanceproposals) + - [StakingAndGovernance/Proposals/AddProposal](#stakingandgovernanceproposalsaddproposal) +- [Settings](#settings) + - [Settings](#settings-1) + - [Settings/WalletSettings](#settingswalletsettings) + - [Settings/Accounts](#settingsaccounts) + - [Settings/Accounts/NewAccount](#settingsaccountsnewaccount) + - [Settings/AccountSettings](#settingsaccountsettings) + +The application is divided to 4 main sections: +* LockScreen +* AccountOverview +* StakingAndGovernance +* Settings + +These are further divided to individual screens or flows (comprising several screens) grouping activities that belong together. For example, under **StakingAndGovernance** we have: + +* **StakingAndGovernance/Staking** - which gives the user the possibility to see all the validators and navigate to a screen where the actual staking is performed. + +Each screen listed below is associated with a high level wireframe design to give a visual presentation of the user interface. Each view is named and being referred with that name through out all communication and in the codebase. + + + +*This screen represents StakingAndGovernance/Staking view* + + +## LockScreen +When the user accesses the wallet for the first time there is a need to create a new account. This screen gives the user to possibility to do so or unlock the wallet by using an existing account. + +### LockScreen +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) +User can: +* can to unlock the wallet by entering the master password +* can to start a flow to create a new account + +## AccountOverview +This is the most important part of the application and the part where the user spends the most time. Here the user performs the most common tasks such as creating transactions. Only one account is selected as a time and the selected account is indicated here. + +### AccountOverview +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) +User can: +* see the aggregated balance in fiat currency +* can see the currently selected account address +* can navigate to **Settings/Accounts** for changing the account +* can see a listing of all hold tokens and their logos, balances, names + + +### AccountOverview/TokenDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) +User can: +* can see the balance of token in native and fiat currency +* can navigate to **AccountOverview/TokenDetails/Receive** for receiving tokens +* can navigate to **AccountOverview/TokenDetails/Send** for sending tokens +* can see a listing of past transaction of the current account and selected token + +### AccountOverview/TokenDetails/Receive +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A6476) +User can: +* see QR code of the address +* see address as a string and copy it by clicking button + +### AccountOverview/TokenDetails/Send +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9579) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9715) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9797) +User can: +view 1: +* see the balance of the token in current account +* enter details: transfer amount, recipient address, memo +* can select to perform the transaction as shielded + +view 2: +* see a summary of the transaction details +* clear indication whether the transaction is transparent of shielded +* select a gas fee +* see an option in gas fees that is specific for shielded transactions +* see a transaction summary including gas fee + +view 3: +* see a confirmation once the transaction is confirmed +* be abel to navigate to see the new transaction in the block explorer +* be able to navigate back to **AccountOverview/TokenDetails** + + + +## StakingAndGovernance +Aside of **AccountOverview** this is a part that the user is likely visiting quite frequently. All staking and governance related activities are performed here. + +### StakingAndGovernance +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6316) +User can: +* see a dashboard with the most interesting information regarding staking +* see a dashboard with the most interesting information regarding governance +* can navigate to **StakingAndGovernance/Staking** for performing staking actions +* can navigate to **StakingAndGovernance/Proposals** for performing governance actions + +### StakingAndGovernance/Staking +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6377) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14001) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14101) +User can: +view 1: +* view a listing of validators +* be able to navigate to aaa for seeing further details about the validator +* select to stake with one of them + +view 2: +* select an amount to stake +* see a summary of the staking transaction + +view 3: +* see a confirmation of a successful staking with the selected validator + +### StakingAndGovernance/ValidatorDetails +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13919) +User can: +* can see all relevant details of the validator + +### StakingAndGovernance/Proposals +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14167) +User can: +* see a listing of all open proposals +* be able to vote for yes, no, no with veto and abstain +* see the current vote share per proposal +* navigate to **StakingAndGovernance/Proposals/AddProposal** for adding a new proposal + +### StakingAndGovernance/Proposals/AddProposal +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14286) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14405) +User can: +view 1: +* enter the details (TBD) of the proposal +* see a summary of the proposal +* submit the proposal + +view 2: +* see a confirmation of successfully submitted proposal + +## Settings +This is a part of the application that is visited less often. This is where the user can change settings of select the active account. + +### Settings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A13327) +User can: +* Navigate to **Settings/Accounts** +* Navigate to **Settings/WalletSettings** + +### Settings/WalletSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A6235) +User can: +* see and change the fiat currency to display in various locations in the app where amounts are being displayed in fiat currency +* Default fiat currency is USD + +### Settings/Accounts +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A9901) +User can: +* select an account by clicking it, when it becomes visibly selected +* can navigate to **Settings/AccountSettings** for changing the settings of certain account +* can navigate to Settings/Accounts/NewAccount/Start for adding a new account to the wallet + + +### Settings/Accounts/NewAccount +[Wireframe 1](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5866) +[Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5956) +[Wireframe 3](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6015) +[Wireframe 4](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6104) +[Wireframe 5](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A6190) +User can: + +view 1: +* see a welcome screen that explain the flow + +view 2: +* enter an alias to the account +* enter and confirm a password +* select the length of the seed phrase (12 or 24 words) + +view 3: +* see a seed phrase that was generated +* copy the seed phrase to clipboard + +view 4: +* enter a randomly requested word from the set of words. ("please enter word #5") + +view 5: +* see a confirmation that the account was created +* navigate to **AccountOverview** and so that the newly created account becomes the selected account + +### Settings/AccountSettings +[Wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6472%3A10076) +User can: +* Rename the selected account +* display the seed phrase, user is being prompted for a password +* delete account, user is prompted to input a security text to prevent an accidental deletion +* select the network \ No newline at end of file diff --git a/documentation/specs/Makefile b/documentation/specs/Makefile index c67fb98176..dbdee3c475 100644 --- a/documentation/specs/Makefile +++ b/documentation/specs/Makefile @@ -10,7 +10,7 @@ dev-deps: $(cargo) install mdbook $(cargo) install mdbook-mermaid $(cargo) install mdbook-linkcheck - $(cargo) install --git https://github.com/heliaxdev/mdbook-katex.git --rev 2b37a542808a0b3cc8e799851514e145990f1e3a + $(cargo) install mdbook-katex $(cargo) install mdbook-open-on-gh $(cargo) install mdbook-admonish diff --git a/documentation/specs/src/SUMMARY.md b/documentation/specs/src/SUMMARY.md index df68ce25b3..65e2de1ddf 100644 --- a/documentation/specs/src/SUMMARY.md +++ b/documentation/specs/src/SUMMARY.md @@ -1,10 +1,13 @@ # Summary -- [Index](./index.md) +- [Introduction](./introduction.md) - [Base ledger](./base-ledger.md) - [Consensus](./base-ledger/consensus.md) - [Execution](./base-ledger/execution.md) - [Governance](./base-ledger/governance.md) + - [Default account](./base-ledger/default-account.md) + - [Multisignature account](./base-ledger/multisignature.md) + - [Fungible token](./base-ledger/fungible-token.md) - [Multi-asset shielded pool](./masp.md) - [Ledger integration](./masp/ledger-integration.md) - [Asset type](./masp/asset-type.md) @@ -23,8 +26,4 @@ - [Reward distribution](./economics/proof-of-stake/reward-distribution.md) - [Shielded pool incentives](./economics/shielded-pool-incentives.md) - [Public goods funding](./economics/public-goods-funding.md) -- [User interfaces](./user-interfaces.md) - - [Web wallet](./user-interfaces/web-wallet-interface.md) - - [Web explorer](./user-interfaces/web-explorer-interface.md) - - [External integrations](./user-interfaces/external-integrations.md) -- [Further readhing](./further-reading.md) \ No newline at end of file +- [Further reading](./further-reading.md) diff --git a/documentation/specs/src/base-ledger/default-account.md b/documentation/specs/src/base-ledger/default-account.md new file mode 100644 index 0000000000..fc40d1d241 --- /dev/null +++ b/documentation/specs/src/base-ledger/default-account.md @@ -0,0 +1,3 @@ +## Default account + +The default account validity predicate authorises transactions on the basis of a cryptographic signature. \ No newline at end of file diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index 16e7c25257..26cfe65f22 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -21,9 +21,10 @@ Supported validity predicates for Namada: - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) - IBC & IbcToken (see [spec](../interoperability/ibc.md)) - Governance (see [spec](./governance.md)) + - Treasury (see [spec](./governance.md#TreasuryAddress)) - Protocol parameters - WASM - - Fungible token + - Fungible token (see [spec](./fungible-token.md)) - MASP (see [spec](../masp.md)) - - Implicit account VP (allows cryptographic signature authorization) - - k-of-n multisignature VP \ No newline at end of file + - Implicit account VP (see [spec](./default-account.md)) + - k-of-n multisignature VP (see [spec](./multisignature.md)) diff --git a/documentation/specs/src/base-ledger/fungible-token.md b/documentation/specs/src/base-ledger/fungible-token.md new file mode 100644 index 0000000000..3fba3fbde2 --- /dev/null +++ b/documentation/specs/src/base-ledger/fungible-token.md @@ -0,0 +1,3 @@ +## Fungible token + +The fungible token validity predicate authorises token balance changes on the basis of conservation-of-supply and approval-by-sender. \ No newline at end of file diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index c6ca1691c7..45b0aa0cb1 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -1,33 +1,34 @@ # Namada Governance -Namada introduces a governance mechanism to propose and apply protocol changes with or without the need for an hard fork. Anyone holding some `NAM` will be able to propose some changes to which delegators and validators will cast their `yay` or `nay` votes. Governance on Namada supports both `signaling` and `voting` mechanisms. 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 on [off chain](#off-chain-protocol) signaling to agree on a common move. +Namada introduces a governance mechanism to propose and apply protocol changes with or without the need for a hard fork. Anyone holding some `NAM` will be able to propose some changes to which delegators and validators will cast their `yay` or `nay` votes. Governance on Namada supports both `signaling` and `voting` mechanisms. The difference between the the two is that the former is needed when the changes require a hard fork. In cases where the chain is not able to produce blocks anymore, Namada relies on [off chain](#off-chain-protocol) signaling to agree on a common move. ## On-chain protocol ### Governance Address Governance adds 2 internal addresses: -- GovernanceAddress -- TreasuryAddress +- `GovernanceAddress` +- `TreasuryAddress` -The first address contains all the proposals under its address space. -The second address holds the funds of rejected proposals. +The first internal address contains all the proposals under its address space. +The second internal address holds the funds of rejected proposals. ### Governance storage Each proposal will be stored in a sub-key under the internal proposal address. The storage keys involved are: ``` -/$GovernanceAddress/proposal/$id/content : Vec -/$GovernanceAddress/proposal/$id/author : Address -/$GovernanceAddress/proposal/$id/start_epoch: Epoch -/$GovernanceAddress/proposal/$id/end_epoch: Epoch -/$GovernanceAddress/proposal/$id/grace_epoch: Epoch -/$GovernanceAddress/proposal/$id/proposal_code: Option> -/$GovernanceAddress/proposal/$id/funds: u64 +/\$GovernanceAddress/proposal/$id/content: Vec +/\$GovernanceAddress/proposal/$id/author: Address +/\$GovernanceAddress/proposal/$id/start_epoch: Epoch +/\$GovernanceAddress/proposal/$id/end_epoch: Epoch +/\$GovernanceAddress/proposal/$id/grace_epoch: Epoch +/\$GovernanceAddress/proposal/$id/proposal_code: Option> +/\$GovernanceAddress/proposal/$id/funds: u64 +/\$GovernanceAddress/proposal/epoch/$id: u64 ``` -`Author` address field will be used to credit the locked funds if the proposal is approved. - -The `content` value should follow a standard format. We leverage something similar to what is described in the [BIP2](https://github.com/bitcoin/bips/blob/master/bip-0002.mediawiki#bip-format-and-structure) document: +- `Author` address field will be used to credit the locked funds if the proposal is approved. +- `/$GovernanceAddress/proposal/$epoch/$id` is used for easing the ledger governance execution. `$epoch` refers to the same value as the on specific in the `grace_epoch` field. +- The `content` value should follow a standard format. We leverage a similar format to what is described in the [BIP2](https://github.com/bitcoin/bips/blob/master/bip-0002.mediawiki#bip-format-and-structure) document: ```json { @@ -46,14 +47,13 @@ The `content` value should follow a standard format. We leverage something simil `GovernanceAddress` parameters and global storage keys are: ``` -/$GovernanceAddress/?: Vec -/$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/pending/$proposal_id: u64 +/\$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/pending/\$proposal_id: u64 ``` @@ -63,19 +63,19 @@ The `content` value should follow a standard format. We leverage something simil `min_proposal_period` sets the minimum voting time window (in `Epoch`).\ `max_proposal_content_size` tells the maximum number of characters allowed in the proposal content.\ `min_proposal_grace_epochs` is the minimum required time window (in `Epoch`) between `end_epoch` and the epoch in which the proposal has to be executed. -`/$GovernanceAddress/pending/$proposal_id` this storage key is written only before the execution of the the code defined in `/$GovernanceAddress/proposal/$id/proposal_code` and deleted afterwards. Since this storage key can be written only by the protocol itself (and by no other means), VPs can check for the presence of this storage key to be sure that a a proposal_code has been executed by the protocol and not by a transaction. +`/$GovernanceAddress/pending/$proposal_id` this storage key is written only before the execution of the code defined in `/$GovernanceAddress/proposal/$id/proposal_code` and deleted afterwards. Since this storage key can be written only by the protocol itself (and by no other means), VPs can check for the presence of this storage key to be sure that a proposal_code has been executed by the protocol and not by a transaction. The governance machinery also relies on a subkey stored under the `NAM` token address: ``` -/$NAMAddress/balance/$GovernanceAddress: u64 +/\$NAMAddress/balance/\$GovernanceAddress: u64 ``` This is to leverage the `NAM` VP to check that the funds were correctly locked. -The governance subkey, `/$GovernanceAddress/proposal/$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. +The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. ### GovernanceAddress VP -Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the followings: +Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the following: - Mandatory storage writes are: - counter - author @@ -83,20 +83,20 @@ Just like Pos, also governance has his own storage space. The `GovernanceAddress - voting_start epoch - voting_end epoch - grace_epoch -- Lock some funds >= `MIN_PROPOSAL_FUND` +- Lock some funds >= `min_proposal_fund` - Contains a unique ID - Contains a start, end and grace Epoch -- The difference between StartEpoch and EndEpoch should be >= `MIN_PROPOSAL_PERIOD * constant`. -- Should contain a text describing the proposal with length < `MAX_PROPOSAL_CONTENT_SIZE` characters. +- The difference between StartEpoch and EndEpoch should be >= `min_proposal_period`. +- Should contain a text describing the proposal with length < `max_proposal_content_size` characters. - Vote can be done only by a delegator or validator -- Validator can vote only in the initial 2/3 of the whole proposal duration (`EndEpoch` - `StartEpoch`) +- Validator can vote only in the initial 2/3 of the whole proposal duration (`end_epoch` - `start_epoch`) - Due to the previous requirement, the following must be true,`(EndEpoch - StartEpoch) % 3 == 0` -- If defined, `proposalCode` should be the wasm bytecode rappresentation of the changes. This code is triggered in case the proposal has a position outcome. -- `GraceEpoch` should be greater than `EndEpoch` of at least `MIN_PROPOSAL_GRACE_EPOCHS` +- If defined, `proposalCode` should be the wasm bytecode representation of + the changes. This code is triggered in case the proposal has a position outcome. +- The difference between `grace_epoch` and `end_epoch` should be of at least `min_proposal_grace_epochs` -`MIN_PROPOSAL_FUND`, `MAX_PROPOSAL_CODE_SIZE`, `MIN_PROPOSAL_GRACE_EPOCHS`, `MAX_PROPOSAL_CONTENT_SIZE` and `MIN_PROPOSAL_PERIOD` are parameters of the protocol. Once a proposal has been created, nobody can modify any of its fields. -If `proposalCode` is `Empty` or `None` , the proposal upgrade will need to be done via hard fork. +If `proposal_code` is `Empty` or `None` , the proposal upgrade will need to be done via hard fork. It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/mod.rs#L69). @@ -135,63 +135,67 @@ struct OnChainVote { } ``` -Vote transaction creates or modify the following storage key: +Vote transaction creates or modifies the following storage key: ``` -/$GovernanceAddress/proposal/$id/vote/$delegation_address/$voter_address: Enum(yay|nay) +/\$GovernanceAddress/proposal/\$id/vote/\$delegation_address/\$voter_address: Enum(yay|nay) ``` -The storage key will only be created if the transaction is signed either by a validator or a delagator. +The storage key will only be created if the transaction is signed either by +a validator or a delegator. Validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. -If a delegator votes opposite to its validator, this will *override* the corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the votig power of the involved validator). +If a delegator votes opposite to its validator, this will *override* the +corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the voting power of the involved validator). -As a small form of space optimization, if a delegator votes accordingly to its validator, the vote will not actually be submitted to the chain. This logic is applied only if the following conditions are satisfied: +As a small form of space/gas optimization, if a delegator votes accordingly to its validator, the vote will not actually be submitted to the chain. This logic is applied only if the following conditions are satisfied: - The transaction is not being forced - The vote is submitted in the last third of the voting period (the one exclusive to delegators). This second condition is necessary to prevent a validator from changing its vote after a delegator vote has been submitted, effectively stealing the delegator's vote. ### Tally -At the beginning of each new epoch (and only then), in the `FinalizeBlock` event, tallying will occur for all the proposals ending at this epoch (specified via the `endEpoch` field). +At the beginning of each new epoch (and only then), in the `FinalizeBlock` event, tallying will occur for all the proposals ending at this epoch (specified via the `grace_epoch` field of the proposal). The proposal has a positive outcome if 2/3 of the staked `NAM` total is voting `yay`. Tallying is computed with the following rules: - Sum all the voting power of validators that voted `yay` - For any validator that voted `yay`, subtract the voting power of any delegation that voted `nay` - Add voting power for any delegation that voted `yay` (whose corresponding validator didn't vote `yay`) -- If the aformentioned sum divided by the total voting power is >= 0.66, the proposal outcome is positive otherwise negative. +- If the aformentioned sum divided by the total voting power is >= `2/3`, the proposal outcome is positive otherwise negative. -All the computation above must be made at the epoch specified in the `end_epoch` field of the proposal. +All the computation above must be made at the epoch specified in the `start_epoch` field of the proposal. It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/utils.rs#L68). ### Refund and Proposal Execution mechanism -Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `TreasuryAddress`. Moreover, if the proposal had a positive outcome and `proposalCode` is defined, these changes will be executed right away. +Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `TreasuryAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. +To summarize the execution of governance in the `FinalizeBlock` event: If the proposal outcome is positive and current epoch is equal to the proposal `grace_epoch`, in the `FinalizeBlock` event: -- transfer the locked funds to the proposal author -- execute any changes to storage specified by `proposalCode` +- transfer the locked funds to the proposal `author` +- execute any changes specified by `proposal_code` In case the proposal was rejected or if any error, in the `FinalizeBlock` event: - transfer the locked funds to `TreasuryAddress` -**NOTE**: we need a way to signal the fulfillment of an accepted proposal inside the block in which it is applied to the state. We could do that by using `Events` https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events (see https://github.com/anoma/namada/issues/930). +The result is then signaled by creating and inserting a [`Tendermint Event`](https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events. + ## TreasuryAddress Funds locked in `TreasuryAddress` address should be spendable only by proposals. ### TreasuryAddress storage ``` -/$TreasuryAddress/max_transferable_fund: u64 -/$TreasuryAddress/?: Vec +/\$TreasuryAddress/max_transferable_fund: u64 +/\$TreasuryAddress/?: Vec ``` The funds will be stored under: ``` -/$NAMAddress/balance/$TreasuryAddress: u64 +/\$NAMAddress/balance/\$TreasuryAddress: u64 ``` ### TreasuryAddress VP -The treasury validity predicate will approve a trasfer only if: +The treasury validity predicate will approve a transfer only if: - the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) - the transfered amount is <= `MAX_SPENDABLE_SUM` @@ -199,33 +203,11 @@ The treasury validity predicate will approve a trasfer only if: It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/treasury/mod.rs#L55). - -## ParameterAddress -Protocol parameter are described under the `$ParameterAddress` internal address. - -### ParameterAddress storage -``` -/$ParamaterAddress/: String -/$ParamaterAddress/?: Vec -``` - -At the moment there are 5 parameters: -- `max_expected_time_per_block` -- `vp_whitelist` -- `tx_whitelist` -- `epoch_duration` - -### ParameterAddress VP -The parameter validity predicate will approve changes to the protocol parameter only if: -- the changes have been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) - -It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/parameters/mod.rs#L53). - - ## Off-chain protocol ### Create proposal -A CLI command to create a signed JSON rappresentation of the proposal. The JSON will have the following structure: +A CLI command to create a signed JSON representation of the proposal. The +JSON will have the following structure: ``` { content: Base64>, @@ -240,7 +222,8 @@ The signature is produced over the hash of the concatenation of: `content`, `aut ### Create vote -A CLI command to create a signed JSON rappresentation of a vote. The JSON will have the following structure: +A CLI command to create a signed JSON representation of a vote. The JSON +will have the following structure: ``` { proposalHash: Base64>, diff --git a/documentation/specs/src/base-ledger/multisignature.md b/documentation/specs/src/base-ledger/multisignature.md new file mode 100644 index 0000000000..a32b8da81e --- /dev/null +++ b/documentation/specs/src/base-ledger/multisignature.md @@ -0,0 +1,3 @@ +## k-of-n multisignature + +The k-of-n multisignature validity predicate authorises transactions on the basis of k out of n parties approving them. \ No newline at end of file diff --git a/documentation/specs/src/economics.md b/documentation/specs/src/economics.md index 0af4869840..bf7346b63a 100644 --- a/documentation/specs/src/economics.md +++ b/documentation/specs/src/economics.md @@ -1,3 +1,3 @@ ## Economics -Namada's economic model is based around a single native token, NAM, which is controlled by the protocol. Users pay transaction fees in NAM, with the gas price adjusted on a sliding P control (aka EIP 1559, described further in [fee system](./economics/fee-system.md), so demand for NAM can be expected to track demand for block space. Half of fees are paid to block producers and half are burnt. On the supply side, the protocol mints NAM at a fixed maximum per-annum rate based on a fraction of the current supply (see [inflation system](./economics/inflation-system.md)), which is directed to three areas of protocol subsidy: [proof-of-stake](./economics/proof-of-stake.md), [shielded pool incentives](./economics/shielded-pool-incentives.md), and [public-goods funding](./economics/public-goods-funding.md). Inflation rates for these three areas are adjusted independently (the first two on PD controllers and the third based on funding decisions) and excess tokens are slowly burned. \ No newline at end of file +Namada's economic model is based around a single native token, NAM, which is controlled by the protocol. Users pay transaction fees in NAM and other tokens (see [fee system](./economics/fee-system.md)), so demand for NAM can be expected to track demand for block space. On the supply side, the protocol mints NAM at a fixed maximum per-annum rate based on a fraction of the current supply (see [inflation system](./economics/inflation-system.md)), which is directed to three areas of protocol subsidy: [proof-of-stake](./economics/proof-of-stake.md), [shielded pool incentives](./economics/shielded-pool-incentives.md), and [public-goods funding](./economics/public-goods-funding.md). Inflation rates for these three areas are adjusted independently (the first two on PD controllers and the third based on funding decisions) and excess tokens are slowly burned. \ No newline at end of file diff --git a/documentation/specs/src/economics/fee-system.md b/documentation/specs/src/economics/fee-system.md index 2c7b4758fb..976bcb1264 100644 --- a/documentation/specs/src/economics/fee-system.md +++ b/documentation/specs/src/economics/fee-system.md @@ -1,20 +1,7 @@ ## Fee system -In order to be accepted by the Namada ledger, transactions must pay fees in NAM. Transaction fees serve two purposes: first, the efficient allocation of block space given permissionless transaction submission and varying demand, and second, incentive-compatibility to encourage block producers to add transactions to the blocks which they create and publish. +In order to be accepted by the Namada ledger, transactions must pay fees. Transaction fees serve two purposes: first, the efficient allocation of block space given permissionless transaction submission and varying demand, and second, incentive-compatibility to encourage block producers to add transactions to the blocks which they create and publish. -Namada follows a [tipless version](https://arxiv.org/pdf/2106.01340.pdf) of the EIP 1559 scheme. In contrast with the original EIP 1559, the transaction fee of this tipless version consists solely of a base fee, with no tip. The base fee increases whenever blocks are fuller than the desired capacity and decreases when the blocks haven't reached this capacity (i.e. a P-controller). Namada uses a target block fullness of 0.5 (adjustable by governance). +Namada transaction fees can be paid in any fungible token which is a member of a whitelist controlled by Namada governance. Governance also sets minimum fee rates (which can be periodically updated so that they are usually sufficient) which transactions must pay in order to be accepted (but they can always pay more to encourage the proposer to prioritise them). When using the shielded pool, transactions can also unshield tokens in order to pay the required fees. -To provide an incentive for the inclusion of transactions by proposers, Namada transfers 50% of the base fee to the next few block proposers, proportional to block fullness. For example, if the block is 100% full, the proposer will receive full fees, whereas if the block is only 25% full, they will only receive 25% of the fees. These fees are kept in a temporary account, with at most a tenth used to pay out the current proposer. - -The other 50% of the base fee is immediately burned, reducing the total supply of NAM by the amount burned. - -Base fees are changed to reflect changes in demand, with a smoothing rate to reduce the frequency at which transaction authors need to calculate required fees. Namada requires a minimum of twenty (20) blocks between base fee changes and a delay of ten (10) blocks before a base fee change is applied. Each change of the base fee follows the function below: - -$$ -Tx_{fee}'=Tx_{fee}*(1+ch_{max}(F-0.5)) -$$ -where $Tx_{fee}$ is the previous transaction fee, $Tx_{fee}'$ is the new transcation fee, $ch_{max}$ is the max change the transaction fee can have, and $F$ is the block fullness. We decided that our target block fullness is 50 %. - -![](https://i.imgur.com/p3qeWw3.jpg) - -In Namada, the base fee is applied as a gas price, where the total fee of a particular transaction will be equal to the product of the base fee and consumed gas. \ No newline at end of file +The token whitelist consists of a list of $(T, GP_{min})$ pairs, where $T$ is a token identifier and $GP_{min}$ is the minimum price per unit gas which must be paid by a transaction paying fees using that asset. This whitelist can be updated with a standard governance proposal. All fees collected are paid directly to the block proposer (incentive-compatible, so that side payments are no more profitable). \ No newline at end of file diff --git a/documentation/specs/src/economics/inflation-system.md b/documentation/specs/src/economics/inflation-system.md index ccc27e9b33..7570693b73 100644 --- a/documentation/specs/src/economics/inflation-system.md +++ b/documentation/specs/src/economics/inflation-system.md @@ -1,126 +1,80 @@ -# Inflation system +## Inflation system -## Token flow +The Namada protocol controls the Namada token NAM (the native staking token), which is programmatically minted to pay for algorithmically measurable public goods - proof-of-stake security and shielded pool usage - and out-of-band public goods. Proof-of-stake rewards are paid into the reward distribution mechanism in order to distribute them to validators and delegators. Shielded pool rewards are paid into the shielded pool reward mechanism, where users who kept tokens in the shielded pool can claim them asynchronously. Public goods funding is paid to the public goods distribution mechanism, which further splits funding between proactive and retroactive funding and into separate categories. -The protocol controls Namada token NAM (the native staking token) sourced from two locations: +### Proof-of-stake rewards -- Fees paid for transactions per the description in [fee system](./fee-system.md), 50 % goes to block production and 50 % goes to treasury. -- Inflation (described below), as in tokens directly printed by the protocol (which we can do arbitrarily), where these tokens then flow to many different sinks: +The security of the proof-of-stake voting power allocation mechanism used by Namada is depenedent in part upon locking (bonding) tokens to validators, where these tokens can be slashed should the validators misbehave. Funds so locked are only able to be withdrawn after an unbonding period. In order to reward validators and delegators for locking their stake and participating in the consensus mechanism, Namada pays a variable amount of inflation to all delegators and validators. The amount of inflation paid is varied on a PD-controller in order to target a particular bonding ratio (fraction of the NAM token being locked in proof-of-stake). Namada targets a bonding ratio of 2/3, paying up to 10% inflation per annum to proof-of-stake rewards. See [reward distribution mechanism](./proof-of-stake/reward-distribution.md) for details. -1. Proof-of-stake rewards, which are paid into the reward distribution mechanism in order to distribute them to validators and delegators. -2. Shielded pool rewards, which are locked in a way such that they can be eventually paid to users who kept tokens in the shielded pool. -3. A governance pool - aka treasury. - - These tokens are slowly burned at a fixed fraction per epoch. -4. A set of configurable custom sinks, which can be addresses on Namada, addresses on Ethereum (over the Ethereum bridge), or addresses on other chains connected over IBC. - - These can be paid fixed amounts per epoch. - - Initial recipients will be configured at genesis, and recipients can be added, removed, or altered by Namada governance. +### Shielded pool rewards -## Token Inflation -In general, inflation refers to the process of a currency losing its purchasing power over time. While this is a classical economic phenomenon, the way cryptocurrencies are produced permits great control over money supply, and doing so cleverly can have positive effects such as increasing incentives. The Namada inflation model depends on several factors, such as ratio between locked and liquid tokens (described below), the multi-asset shielded pool, and funds for treasury. +Privacy provided by the MASP in practice depends on how many users use the shielded pool and what assets they use it with. To increase the likelihood of a sizeable privacy set, Namada pays a variable portion of inflation, up to 10% per annum, to shielded pool incentives, which are allocated on a per-asset basis by a PD-controller targeting specific amounts of each asset being locked in the shielded pool. See [shielded pool incentives](./shielded-pool-incentives.md) for details. -When validators are selected they need to be backed by funds. These funds are locked for the duration of an epoch and 21 days after the epoch has ended. Locked tokens help secure the system while liquidity supports its activity and liveness. We need to choose the ratio between locked and liquid tokens carefully. Liquid tokens make sure the price of the token is not increasing out of scarcity and users have access to tokens to pay transaction fees, while locked tokens are the guarantee that attacking the system is expensive for an adversary. +### Public goods funding -Here are some numbers from other projects +Namada provides 10% per annum inflation for other non-algorithmically-measurable public goods. See [public goods funding](./public-goods-funding.md) for details. -| Blockchain platform | Approximate locking % | -|--------------------------------------------------|------| -| Cosmos | 66.7 | -| Polkadot | 50 | -| Ethereum | 47 | -| Solana | 77 | +## Detailed inflation calculation model +Inflation is calculated and paid per-epoch as follows. -Our desired percentage for Namada is 33%-66%: Locked for validating and the rest %33-%66 is liquid. When the price of the token is low we can aim for a higher % of locked tokens and reduce this as the price and demand for liquid tokens increases. For example, we can set a range, in the beginning have 50 % and later aim for 1/3. I don't think we should go lower than that. The staking reward should be ideally set. +First, we start with the following fixed (governance-alterable) parameters: +- $Cap_{PoS}$ is the cap of proof-of-stake reward rate, in units of percent per annum +- $Cap_{SP-A}$ is the cap of shielded pool reward rate for each asset $A$, in units of percent per annum +- $R_{PGF}$ is the public goods funding reward rate, in units of percent per annum +- $R_{PoS-Target}$ is the target staking ratio (genesis default 2/3) +- $R_{SP-A-Target}$ is the target amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) +- $EpochsPerYear$ is the number of epochs per year (genesis default 365) +- ${KP}_{PoS}$ is the proportional gain of the proof-of-stake PD controller, as a fraction of the total input range +- ${KD}_{PoS}$ is the derivative gain of the proof-of-stake PD controller, as a fraction of the total input range +- ${KP}_{SP_A}$ is the proportional gain of the shielded pool reward controller for asset $A$, as a fraction of the total input range (separate value for each asset $A$) +- ${KD}_{SP_A}$ is the derivative gain of the shielded pool reward controller for asset $A$, as a fraction of the total input range (separate value for each asset $A$) - +- $S_{NAM}$ is the current supply of NAM +- $L_{NAM}$ is the current amount of NAM locked in proof-of-stake +- $I_{PoS}$ is the current proof-of-stake reward rate, in units of tokens per epoch +- $E_{PoS-last}$ is the error in proof-of-stake lock ratio (stored from the past epoch) +- $L_{SP_A}$ is the current amount of asset $A$ locked in the shielded pool (separate value for each asset $A$) +- $I_{SP_A}$ is the current shielded pool reward rate for asset $A$, in units of tokens per epoch +- $E_{SP_A-last}$ is the error in shielded pool lock amount for asset $A$ (stored from the past epoch) (separate value for each asset $A$) -The privacy that MASP is providing depends on the asset in the shielded pool. A transaction can only be private if it can hide among other transactions, hence more funds and activity in the shielded pool increase privacy for transactions. +Public goods funding inflation can be calculated and paid immediately: -The Treasury is a pool of native tokens that can be appropriated for funding public-good products for Namada. The decision on spending these funds will be assigned to governance. +- $I_{PGF} := R_{PGF} * S_{NAM} / EpochsPerYear$ -### Related work -Ethereum 2.0, Solana, and Near protocols inflation rate are independent of how much tokens are staked. Near protocol and Ethereum 2.0 have fixed inflation rates, while Solana start with a high inflation rate that decreases over time, as less transaction fees are burned. +These tokens are distributed to the public goods funding validity predicate. -In Polkadot and Cosmos the total inflation rate that is paid as rewards to validators depends on the staking ratio. This is to incentivize validators and delegators to invest in the staking pool. We will follow the same idea and have inflation vary depending on our target staking ratio. Here is how we achieve that. +To run the PD-controllers for proof-of-stake and shielded pool rewards, we first calculate some intermediate values: -For funds going to treasury Near protocol where 5 % goes to treasury and Polkadot sends the difference between inflation for PoS and the total constant inflation to treasury. +- Calculate the staking ratio $R_{PoS}$ as $L_{NAM} / S_{NAM}$ +- Calculate the per-epoch cap on proof-of-stake and shielded pool reward rates + - $Cap_{PoS-Epoch} := S_{NAM} * Cap_{PoS} / EpochsPerYear$ + - $Cap_{SP_A-Epoch} := S_{NAM} * Cap_{SP_A} / EpochsPerYear$ (separate value for each $A$) +- Calculate PD-controller constants + - ${KP}_{PoS} := {KP}_{PoS} * Cap_{PoS-Epoch}$ + - ${KD}_{PoS} := {KD}_{PoS} * Cap_{PoS-Epoch}$ + - ${KP}_{SP_A} := {KP}_{SP_A} * Cap_{SP_A-Epoch}$ + - ${KD}_{SP_A} := {KD}_{SP_A} * Cap_{SP_A-Epoch}$ -### Model +Then, for proof-of-stake first, run the PD-controller: -Let us assume $T$ is the total token supply and $I$ is the total inflation of Namada. +- Calculate the error $E_{PoS} := R_{PoS-Target} - R_{PoS}$ +- Calculate the error derivative $E'_{PoS} := E_{PoS} - E_{PoS-last}$ +- Calculate the control value $C_{PoS} := (KP_{PoS} * E_{PoS}) - (KD_{PoS} * E'_{PoS})$ +- Calculate the new $I_{PoS} := max(0, min(I_{PoS} + C_{PoS}, Cap_{PoS}))$ -$$I=\frac{T_\textrm{end of year}-T_\textrm{beginning of year}}{T_\textrm{beginning of year}}$$ +These tokens are distributed to the proof-of-stake reward distribution validity predicate. -The total inflation consists of several components as follows. +Similarly, for each asset $A$ for which shielded pool rewards are being paid: -$$I=I_{PoS}+I_L+I_T-D_T$$ - -where $I_T$ is our inflation that goes to treasury, $I_{PoS}$ is inflation that is paid as PoS rewards, and $I_L$ is the inflation for locking that is paid to accounts in shielded pool. We can extend the $I_L$ be extended to be for many other types of $I_L1,...,I_Ln$. For simplicity we only assume to have one $I_L$. $D_T$ is the constant deflation of the treasury. This is applied to incentivize governance voters to spend treasury funds. - -These components are each varying depending on independent factors as follows. The $I_{PoS}$ depends on the staking ratio $R(t)$. The locking inflation $I_L$ depends on the locking ratio $L(t)$. Ideally we want the total token supply to consist of tokens locked for staking and shielded pool and the rest are liquid tokens $Y$. - -$$T=T*R_{target}+T*L_{target}+Y$$ - -where $R_{target}$ is the target staking ratio and $L_{target}$ is the target locking of assets in the shielded pool. - -We assume further assume $I_{target}$ is our target total inflation that we want to achieve on the long term, where we split it up into $I_{PoS,target}$ and $I_{L,target}$ for staking and locking respectivly. - -We define $I_{PoS}$ as a PD controller as follows. - -$$A(t)=K_1(R(t)-R_{target})+K_2(\frac{dR}{dt})$$ - -If $I_{PoS}^{min}< I_{PoS}< I_{PoS}^{max}$ then $\frac{dI_{PoS}}{dt}=A(t)$. - -If $I_{PoS}{min}< I_{PoS}$ then $\frac{dI_{PoS}}{dt}=max(A(t),0$. - -If $I_{PoS}< I_{PoS}^{max}$ then $\frac{dI_{PoS}}{dt}=min(A(t),0)$. - -For $I_{PoS}^{min}=0.05$, $I_{PoS}^{max}=0.15$, $I_{PoS,target}=0.10$, and $R_{target}=0.50$ we set $K_1=-0.01$ and $K_2=-0.2$. Lets review what these parameters give us with examples as follows. - -**Example 1:** If $I= I_{PoS,target}=0.10$ and $R_{target}=0.50$, but then $R$ drops quickly to $0.25$, then the effect of the $K_2$ term will be to increase $I_{PoS}$ by $-0.2 \times -0.25=0.05$ and inflation will hit its maximum value of $0.15$. Changes in $R$ smaller than $0.25$ will not cause inflation to hit its maximum or minimum quickly. - -**Example 2:** If $I_{PoS}=0.05$, but $R$ holds steady at $0.40$, then $K_1$ term will cause $I$ to increase by $-0.01 \times -0.10=0.001$ per day/epoch. $I_{PoS}$ will take 100 days to reach its maximum. This is slow compared to the unbonding period, allowing delegators time to react. - - ---- - -We define $I_{L}$ as a PD controller follows. - -$$A(t)=K_1(L(t)-L_{target})+K_2(\frac{dL}{dt})$$ - -If $I_{L}^{min}< I_{L}< I_{L}^{max}$ then $\frac{dI_{L}}{dt}=A(t)$. - -If $I_{L}^{min}< I_{L}$ then $\frac{dI_{L}}{dt}=max(A(t),0$. - -If $I_{L}< I_{L}^{max}$ then $\frac{dI_{L}}{dt}=min(A(t),0)$. - -For $I_{L}^{min}=0.03$, $I_{L}^{max}=0.07$, $I_{L,target}=0.05$, and $L_{target}=0.30$ we set $K_1=-0.05$ and $K_2=-0.1$. Lets review what these parameters give us with examples as follows. - -**Example 1:** If $I= I_{L,target}=0.05$ and $L_{target}=0.30$, but then $L$ drops quickly to $0.15$, then the effect of the $K_2$ term will be to increase $I_L$ by $-0.1 \times -0.15=0.015$ and inflation will hit $0.065$ which is short of its maximum value of $0.07$. Changes in $L$ smaller than $0.15$ will not cause inflation to hit its maximum or minimum quickly. - -**Example 2:** If $I_{L}=0.03$, but $L$ holds steady at $0.20$, then $K_1$ term will cause $I_L$ to increase by $-0.05 \times -0.10=0.005$ per day/epoch. $I_{L}$ will take 8 days to reach its maximum. - -TODO: Why we chose those min and max values. -TODO: Dt and It based on Chris proposal - -The ratio between staking and locking in the shielded pool is a trade off between security, privacy, and liveness. A higher staking ratio means more security, a higher locking ratio means more privacy, and if both are too high there wont be enough liquidity for transactions. It would be easier to consider these separately, for example, setting the target staking ratio to 50 % and the target locking ratio to 30 %. - -The funds minted for the treasury is a constant %, for example 1 %. Same goes for $D_T$. - -We need to define $I_{PoS}^{max}$, $I_{L}^{max}$, and $I_{T}$ to bound total inflation. - -$$I_{PoS}^{max}+I_{L}^{max}+I_T=< I^{max}$$ - -The sum of $I_L$ and other $I_L1, ..., I_Ln$ will also be limited. If their sum would exceed the limit, then we need to scale them down to stay within the limit. - -These bounds on $I_{PoS}$ and $I_L$ give us a min and max bound on the total inflation, where the total inflation depends on $L_{target}$ and $R_{target}$ independently. +- Calculate the error $E_{SP_A} := L_{SP_A-Target} - L_{SP_A}$ +- Calculate the error derivative $E'_{SP_A} := E_{SP-A} - E_{SP_A-last}$ +- Calculate the control value $C_{SP_A} := (KP_{SP_A} * E_{SP_A}) - (KD_{SP_A} * E'{SP_A})$ +- Calculate the new $I_{SP_A} := max(0, min(I_{SP_A} + C_{SP_A}, Cap_{SP_A-Epoch}))$ +These tokens are distributed to the shielded pool reward distribution validity predicate. +Finally, we store the current inflation and error values for the next controller round. \ No newline at end of file diff --git a/documentation/specs/src/economics/proof-of-stake.md b/documentation/specs/src/economics/proof-of-stake.md index 7e9d01eba7..7160872da5 100644 --- a/documentation/specs/src/economics/proof-of-stake.md +++ b/documentation/specs/src/economics/proof-of-stake.md @@ -6,15 +6,27 @@ This section is split into three subcomponents: the [bonding mechanism](./proof- ## Introduction -Blockchain system rely on economic security to prevent abuse and for actors to behave according to protocol. The aim is that economic incentive promote correct and long-term operation of the system and economic punishments would discourage diverting from correct protocol execution either by mistake or with the intent to carrying out attacks. Many PoS blockcains rely on the 1/3 Byzantine rule, where they make the assumption the adversary cannot control more 2/3 of the total stake or 2/3 of the actors. +Blockchain systems rely on economic security (directly or indirectly) to +prevent +abuse and +for actors +to behave according to protocol. The aim is that economic incentives promote +correct long-term operation of the system and economic punishments +discourage diverging from correct protocol execution either by mistake or +with the intent of carrying out attacks. Many PoS blockcains rely on the 1/3 Byzantine rule, where they make the assumption the adversary cannot control more 2/3 of the total stake or 2/3 of the actors. ## Goals of Rewards and Slashing: Liveness and Security -* **Security: Delegation and Slashing**: we want to make sure validators backed by enough funds to make misbehaviour very expensive. Security is achieved by punishing (slashing) if they do. *Slashing* locked funds (stake) intends to disintensivize diverting from correct execution of protocol, which is this case is voting to finalize valid blocks. +* **Security: Delegation and Slashing**: we want to make sure validators are + backed by enough funds to make misbehaviour very expensive. Security is + achieved by punishing (slashing) if they do. *Slashing* locked funds (stake) + intends to disincentivize diverging from correct execution of protocol, + which in this case is voting to finalize valid blocks. * **Liveness: Paying Rewards**. For continued operation of Namada we want to incentivize participating in consensus and delegation, which helps security. ### Security -In blockchain system 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. +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 diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index 5d7a25ef2e..ab0449e026 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -6,7 +6,8 @@ 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: +Epoched data is data associated with a specific epoch that is 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. diff --git a/documentation/specs/src/economics/public-goods-funding.md b/documentation/specs/src/economics/public-goods-funding.md index 72c415ad2b..96e82dfc93 100644 --- a/documentation/specs/src/economics/public-goods-funding.md +++ b/documentation/specs/src/economics/public-goods-funding.md @@ -1,6 +1,6 @@ ### Motivation -**Public goods** are non-excludable non-rivalrous items which provide benefits of some sort to their users. Examples include languages, open-source software, research, designs, Earth's atmosphere, and art (conceptually - a physical painting is excludable and rivalrous, but the painting as-such is not). Namada's software stack, supporting research, and ecosystem tooling are all public goods, as are the information ecosystem and education which provide for the technology to be used safety, the hardware designs and software stacks (e.g. instruction set, OS, programming language) on which it runs, and the atmosphere and biodiverse environment which renders its operation possible. Without these things, Namada could not exist, and without their continued sustenance it will not continue to. Public goods, by their nature as non-excludable and non-rivalrous, are mis-modeled by economic systems (such as payment-for-goods) built upon the assumption of scarcity, and are usually either under-funded (relative to their public benefit) or funded in ways which require artificial scarcity and thus a public loss. For this reason, it is in the interest of Namada to help out, where possible, in funding the public goods upon which its existence depends in ways which do not require the introduction of artificial scarcity, balancing the costs of available resources and operational complexity. This is a proposal for a mechanism to be built into the Namada protocol, community governance precedent, and surrounding social structures in order to fund public goods while balancing these constraints. +**Public goods** are non-excludable non-rivalrous items which provide benefits of some sort to their users. Examples include languages, open-source software, research, designs, Earth's atmosphere, and art (conceptually - a physical painting is excludable and rivalrous, but the painting as-such is not). Namada's software stack, supporting research, and ecosystem tooling are all public goods, as are the information ecosystem and education which provide for the technology to be used safety, the hardware designs and software stacks (e.g. instruction set, OS, programming language) on which it runs, and the atmosphere and biodiverse environment which renders its operation possible. Without these things, Namada could not exist, and without their continued sustenance it will not continue to. Public goods, by their nature as non-excludable and non-rivalrous, are mis-modeled by economic systems (such as payment-for-goods) built upon the assumption of scarcity, and are usually either under-funded (relative to their public benefit) or funded in ways which require artificial scarcity and thus a public loss. For this reason, it is in the interest of Namada to help out, where possible, in funding the public goods upon which its existence depends in ways which do not require the introduction of artificial scarcity, balancing the costs of available resources and operational complexity. ### Design precedent @@ -19,13 +19,13 @@ This proposal requires the following protocol components: - Stake-weighted approval voting: as public goods councils are exclusive, we can use a stake-weighted form of approval voting. Governance voters include all public goods council candidates of which they approve, and the council candidate with the most stake approving it wins. This doesn't have game-theoretic properties as nice as ranked-choice voting (especially when votes are public, as they are at the moment), but it is _much_ simpler ([background](https://en.wikipedia.org/wiki/Condorcet_method)), and in practice I do not think there will be too many public goods council candidates. - Interface support: the interface should support limited liquid democracy for delegate selection and approval voting for public goods council candidates. The interface or explorer should display past retroactive PGF winners and past/current continuous funding recipients. Proposal submission for continuous and retroactive funding will happen separately, in whatever manner the public goods council deems fit. ---- +### Funding categories -Please note that the following is _social consensus_, precedent which can be set at genesis and ratified by governance but does not require any protocol changes. +> Note that the following is _social consensus_, precedent which can be set at genesis and ratified by governance but does not require any protocol changes. _Categories of public-goods funding_ -I propose that the Namada public-goods funding council group public goods into four categories, with earmarked pools of funding: +Namada groups public goods into four categories, with earmarked pools of funding: - Technical research _Technical research_ covers funding for technical research topics related to Namada and Anoma, such as cryptography, distributed systems, programming language theory, and human-computer interface design, both inside and outside the academy. Possible funding forms could include PhD sponsorships, independent researcher grants, institutional funding, funding for experimental resources (e.g. compute resources for benchmarking), funding for prizes (e.g. theoretical cryptography optimisations), and similar. @@ -36,8 +36,10 @@ I propose that the Namada public-goods funding council group public goods into f - External public goods _External public goods_ covers funding for public goods explicitly external to the Namada and Anoma ecosystem, including carbon sequestration, independent journalism, direct cash transfers, legal advocacy, etc. Possible funding forms could include direct purchase of tokenised assets such as carbon credits, direct cash transfers (e.g. GiveDirectly), institutional funding (e.g. Wikileaks), and similar. -_Amounts_ +### Funding amounts -I propose 10% total per annum inflation, 5% to continuous funding and 5% to retroactive funding (this is chosen in-protocol, so this suggestion is merely a genesis default and can be altered by governance). +In Namada, 10% inflation per annum of the NAM token is directed to this public goods mechanism, 5% to continuous funding and 5% to retroactive funding. This is a genesis default and can be altered by governance. -I proposal a social consensus of an equal split between categories, meaning 1.25% per annum inflation for each category (e.g. 1.25% for technical research continuous funding, 1.25% for technical research retroactive PGF). \ No newline at end of file +Namada encourages the public goods council to adopt a default social consensus of an equal split between categories, meaning 1.25% per annum inflation for each category (e.g. 1.25% for technical research continuous funding, 1.25% for technical research retroactive PGF). If no qualified recipients are available, funds may be redirected or burnt. + +Namada also pays the public goods council members themselves (in total) a default of 0.1% inflation per annum. \ No newline at end of file diff --git a/documentation/specs/src/economics/shielded-pool-incentives.md b/documentation/specs/src/economics/shielded-pool-incentives.md index 1dce6cc5be..10892501b4 100644 --- a/documentation/specs/src/economics/shielded-pool-incentives.md +++ b/documentation/specs/src/economics/shielded-pool-incentives.md @@ -1,6 +1,9 @@ -# Shielded pool incentives +## Shielded pool incentives -Private transactions made by individual users using the MASP increase the privacy set for other users, so even if the individual doesn't care whether a particular transaction is private, others benefit from their choice to do the transaction in private instead of in public. In the absence of a subsidy (the computation required for private state transitions is likely more expensive) orother incentives, users may not elect to make their transactions private when they do not need to because the benefits do not directly accrue to them. This provides grounds for a protocol subsidy of shielded transactions (relative to the computatation required), so that users who do not have a strong preference on whether or not to make their transaction private will be "nudged" by the fee difference to do so. +### Rationale + +Private transactions made by individual users using the MASP increase the +privacy set for other users, so even if the individual doesn't care whether a particular transaction is private, others benefit from their choice to do the transaction in private instead of in public. In the absence of a subsidy (the computation required for private state transitions is likely more expensive) or other incentives, users may not elect to make their transactions private when they do not need to because the benefits do not directly accrue to them. This provides grounds for a protocol subsidy of shielded transactions (relative to the computatation required), so that users who do not have a strong preference on whether or not to make their transaction private will be "nudged" by the fee difference to do so. Separately, and additionally, a privacy set which is very small in absolute terms does not provide much privacy, and transactions increasing the privacy set provide more additional privacy if the privacy set is small. Compare, for example, the doubled privacy set from 10 to 20 transactions to the minor increase from 1010 to 1020 transactions. This provides grounds for some sort of incentive mechanism for _making_ shielded transactions which pays in inverse proportion to the size of the current privacy set (so shielded transactions when the privacy set is small receive increased incentives in accordance with their increased contributions to privacy). @@ -10,7 +13,12 @@ Incentive mechanisms are also dangerous, as they give users reason to craft part - Incentives for contributing to the privacy set should not incentivise transactions which do not meaningfully contribute to the privacy set or merely repeat a previous action (shielded and unshielding the same assets, repeatedly transferring the same assets, etc.) - Incentives for contributing to the privacy set, since the MASP supports many assets, will need to be adjusted over time according to actual conditions of use. -(to be written up: formal definition of "privacy set" used for the incentives here) +### Design + +Namada enacts a shielded pool incentive which pays users a variable rate for keeping assets in the shielded pool. Assets do not need to be locked in any way. Users may claim rewards while remaining in the shielded pool using the convert circuit, and unshield the rewards (should they wish to) at some later point in time. The protocol uses a PD-controller to target particular minimum amounts of particular assets being shielded. Rewards accumulate automatically over time, so claiming rewards more frequently does not result in additional funds. + +### Implementation + +When users deposit assets into the shielded pool, the current epoch is appended to the asset type. Users can use these "epoched assets" as normal within the shielded pool. When epochs advance, users can use the [convert circuit](../masp/convert-circuit.md) to convert assets tagged with the old epoch to assets tagged with the new epoch, receiving shielded rewards in NAM proportional to the amount of the asset they had shielded, which automatically compound while the assets are shielded and the epochs progressing. When unshielding from the shielded pool, assets must be first converted to the current epoch (claiming any rewards), after which they can be converted back to the normal (un-epoched) unshielded asset denomination. - -The total incetives that are paid out, $I_L$, is minted each epoch based on the current parameters and are calculated according to the [inflation model](./inflation-system.md). This total is then distributed evenly among recipients. +Namada allocates up to 10% per annum inflation of NAM to pay for shielded pool rewards. This inflation is kept in a temporary shielded rewards pool, which is then allocated according to a set of PD (proportional-derivative) controllers for assets and target shielded amounts configured by Namada governance. Each epoch, subject to available rewards, each controller calculates the reward rate for its asset in this epoch, which is then used to compute entries into the conversion table. Entries from epochs before the previous one are recalculated based on cumulative rewards. Users may then asynchronously claim their rewards by using the convert circuit at some future point in time. diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index ffab14558b..cfc5ce6841 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -78,13 +78,13 @@ the state with the new state rather than applying state diffs. The storage keys involved are: ``` # all values are Borsh-serialized -/eth_msgs/$msg_hash/body : EthereumEvent -/eth_msgs/$msg_hash/seen_by : Vec
-/eth_msgs/$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) -/eth_msgs/$msg_hash/seen: bool +/eth_msgs/\$msg_hash/body : EthereumEvent +/eth_msgs/\$msg_hash/seen_by : Vec
+/eth_msgs/\$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) +/eth_msgs/\$msg_hash/seen: bool ``` -`$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant +`\$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant `EthereumEvent`. Changes to this `/eth_msgs` storage subspace are only ever made by internal @@ -101,10 +101,10 @@ removed by the ledger code for the specific permitted transactions that are allowed to update `/eth_msgs`. ### Including events into storage -For every Namada block proposal, each validator must include a vote extension -containing the events of the Ethereum blocks they have seen via their full node -such that: -1. The storage value `/eth_msgs/$msg_hash/seen_by` does not include their + +For every Namada block proposal, the vote extension of a validator should include +the events of the Ethereum blocks they have seen via their full node such that: +1. The storage value `/eth_msgs/\$msg_hash/seen_by` does not include their address. 2. It's correctly formatted. 3. It's reached the required number of confirmations on the Ethereum chain @@ -175,7 +175,7 @@ of events from validators by the block proposer. In `FinalizeBlock`, we derive a second transaction (the "state update" transaction) from the vote extensions transaction that: - calculates the required changes to `/eth_msgs` storage and applies it -- acts on any `/eth_msgs/$msg_hash` where `seen` is going from `false` to `true` +- acts on any `/eth_msgs/\$msg_hash` where `seen` is going from `false` to `true` (e.g. appropriately minting wrapped Ethereum assets) This state update transaction will not be recorded on chain but will be @@ -185,7 +185,7 @@ their own local blockchain state, whenever they receive a block with a vote extensions transaction. This transaction cannot require a protocol signature as even non-validator full nodes of Namada will be expected to do this. -The value of `/eth_msgs/$msg_hash/seen` will also indicate if the event +The value of `/eth_msgs/\$msg_hash/seen` will also indicate if the event has been acted on on the Namada side. The appropriate transfers of tokens to the given user will be included on chain free of charge and requires no additional actions from the end user. diff --git a/documentation/specs/src/interoperability/ibc.md b/documentation/specs/src/interoperability/ibc.md index 8b0446575e..198266dd93 100644 --- a/documentation/specs/src/interoperability/ibc.md +++ b/documentation/specs/src/interoperability/ibc.md @@ -18,9 +18,17 @@ In Anoma, the sending tokens is triggered by a transaction having [MsgTransfer]( Anoma chain receives the tokens by a transaction having [MsgRecvPacket](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/core/ics04_channel/msgs/recv_packet.rs#L19-L23) which has the packet including `FungibleTokenPacketData`. -The sending and receiving tokens in a transaction are validated by not only IBC validity predicate but also [IBC token validity predicate](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.IbcToken.html#impl-NativeVp). IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. the unexpected amount is minted, the validity predicate makes the transaction fail. - -A transaction escrowing/unescrowing a token changes the escrow account's balance of the token. The key is `{token_addr}/balance/{escrow_addr}`. A transaction burning a token changes the burn account's balance of the token. The key is `{token_addr}/balance/BURN_ADDR`. A transaction minting a token changes the mint account's balance of the token. The key is `{token_addr}/balance/MINT_ADDR`. `{escrow_addr}`, `{BURN_ADDR}`, and `{MINT_ADDR}` are addresses of [`InternalAddress`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html). When these address are included of the change keys after transaction execution, IBC token validity predicate is executed. +The sending and receiving tokens in a transaction are validated by not only +IBC validity predicate but also [IBC token validity predicate](https://docs. +anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.IbcToken. +html#impl-NativeVp). IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. an unexpected amount is minted, the validity predicate makes the transaction fail. + +A transaction escrowing/unescrowing a token changes the escrow account's +balance of the token. The key is `{token_addr}/balance/{escrow_addr}`. A +transaction burning a token changes the burn account's balance of the token. +The key is `{token_addr}/balance/BURN_ADDR`. A transaction minting a token +changes the mint account's balance of the token. The key is `{token_addr} +/balance/MINT_ADDR`. `{escrow_addr}`, `{BURN_ADDR}`, and `{MINT_ADDR}` are addresses of [`InternalAddress`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html). When these addresses are included in the changed keys after transaction execution, IBC token validity predicate is executed. ## IBC message diff --git a/documentation/specs/src/introduction.md b/documentation/specs/src/introduction.md new file mode 100644 index 0000000000..6f4971e049 --- /dev/null +++ b/documentation/specs/src/introduction.md @@ -0,0 +1,56 @@ +## Namada + +Welcome to the Namada specifications! + +Namada 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 features +full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stake +system with automatic reward compounding and cubic slashing, a stake-weighted governance +signalling mechanism, and a proactive/retroactive public goods funding system. +Users of shielded transfers are rewarded for their contributions +to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet +is provided in order to facilitate safe and private user interaction with the protocol. + +### How does Namada relate to Anoma? + +Namada is the first fractal instance launched as part of the Anoma ecosystem. + +The Anoma protocol is designed to facilitate the operation of networked fractal instances, +which intercommunicate but can utilise varied state machines and security models. Different +fractal instances may specialise in different tasks and serve different communities. The Namada +instance will be the first such fractal instance, and it will be focused exclusively on the use-case of private asset transfers. + +### Raison d'être + +Safe and user-friendly multi-asset privacy doesn't yet exist in the blockchain ecosystem. +Up until now users have had the choice of either a sovereign chain that reissues assets (e.g. Zcash) +or a privacy preserving solution built on an existing smart contract chain (e.g. Tornado Cash on +Ethereum). Both have large trade-offs: in the former case, users don't have +assets that they actually want to transact with, and in the latter case, the restrictions +of existing platforms mean that users leak a ton of metadata +and the protocols are expensive and clunky to use. + +Namada can support any fungible or non-fungible asset on an IBC-compatible blockchain +and fungible or non-fungible assets (such as ERC20 tokens) sent over a custom Ethereum bridge that +reduces transfer costs and streamlines UX as much as possible. Once assets are on Namada, +shielded transfers are cheap and all assets contribute to the same anonymity set. + +Namada is also a helpful stepping stone to finalise, test, +and launch a protocol version that is simpler than the full +Anoma protocol but still encapsulates a unified and useful +set of features. There are reasons to expect that it may +make sense for a fractal instance focused exclusively on +shielded transfers to exist in the long-term, as it can +provide throughput and user-friendliness guarantees which +are more difficult to provide with a more general platform. +Namada is designed to be such an instance. + +### Layout of this specification + +The Namada specification documents are organised into four sub-sections: + +- [Base ledger](./base-ledger.md) +- [Multi-asset shielded pool](./masp.md) +- [Interoperability](./interoperability.md) +- [Economics](./economics.md) diff --git a/documentation/specs/src/masp/burn-and-mint.md b/documentation/specs/src/masp/burn-and-mint.md index 67699657f7..f66b8e28eb 100644 --- a/documentation/specs/src/masp/burn-and-mint.md +++ b/documentation/specs/src/masp/burn-and-mint.md @@ -24,7 +24,7 @@ Each allowed conversion is committed to a Jubjub point using a binding Bowe-Hopw In order for an unbalanced transaction containing burns and mints to get a net value balance of zero, one or more value commitments burning and minting assets must be added to the value balance. Similar to how Spend and Output circuits check the validity of their respective value commitments, the Convert circuit checks the validity and integrity of: -1. There exists an allowed conversion commitment in the Merkle tree, and +1. There exists an allowed conversion commitment in the Merkle tree, and 1. The imbalance in the value commitment is a multiple of an allowed conversion's asset generator In particular, the Convert circuit takes public input: diff --git a/documentation/specs/src/masp/convert-circuit.md b/documentation/specs/src/masp/convert-circuit.md index ffb94e41b1..ee0469be33 100644 --- a/documentation/specs/src/masp/convert-circuit.md +++ b/documentation/specs/src/masp/convert-circuit.md @@ -1,9 +1,10 @@ # Convert Circuit ## Convert Circuit Description -The high-level description of `Convert` can be found [Brun and mint](./burn-and-mint.html). +The high-level description of `Convert` can be found [Burn and mint](./burn-and-mint.html). -The `Convert` provides a mechanism that burning and minting of assets can be enabled by adding `Convert Value Vommitments` in transaction and ensuring the homomorphic sum of `Spend`, `Output` and `Convert` value commitments to be zero. +The `Convert` provides a mechanism that burning and minting of assets can be +enabled by adding `Convert Value Commitments` in transaction and ensuring the homomorphic sum of `Spend`, `Output` and `Convert` value commitments to be zero. The Convert value commitment is constructed from `AllowedConversion` which was published earlier in `AllowedConversion Tree`. The `AllowedConversion` defines the allowed conversion assets. The `AllowedConversion Tree` is a merkle hash tree stored in the ledger. @@ -12,7 +13,8 @@ An `AllowedConversion` is a compound asset type in essence, which contains disti `AllowedConversion` is an array of tuple $\{(t_1, v_1^{ratio}),(t_2, v_2^{ratio})...(t_n, v_n^{ratio})\}$ * $t$: $\mathbb{B}^{\mathcal{l}_t}$ is a bytestring representing the asset identifier of the note. -* $v^{ratio}$: $v^{ratio}$ is a signed 64-bit integer in the range $\{−2^{63} .. 2^{63} − 1\}$. +* $v^{ratio}$: $v^{ratio}$ is a signed 64-bit integer in the range $\{−2^{63} + ,\dots, 2^{63} − 1\}$. Calculate: @@ -28,7 +30,8 @@ An `AllowedConversion` can be used by proving the existence in `AllowedConversio ## Convert Value Commitment `Convert Value Commitment` is a tuple $(vb^{allowedconversion}, v^{convert}, rcv^{convert})$ -* $v^{convert}$: $v^{convert}$ is an unsigned integer representing the value of conversion in range $\{0 .. 2^{64} − 1\}$. +* $v^{convert}$: $v^{convert}$ is an unsigned integer representing the value + of conversion in range $[2^{64} − 1]$. Choose independent uniformly random commitment trapdoors: * $rcv^{convert}$ $\leftarrow \mathsf{ValueCommit}\mathsf{.GenTrapdoor}()$ @@ -76,7 +79,9 @@ Return $(cv^{convert}, rt^{convert},\pi_{convert})$ Notes: * Public and auxiliary inputs MUST be constrained to have the types specified. In particular, see the original Sapling specification, for required validity checks on compressed representations of Jubjub curve points. The ValueCommit.Output type also represents points, i.e. $\mathbb{J}$. -* In the Merkle path validity check, each layer does not check that its input bit sequence is a canonical encoding(in {${0 .. r_{\mathbb{S}} − 1}$}) of the integer from the previous layer. +* In the Merkle path validity check, each layer does not check that its + input bit sequence is a canonical encoding(in $[r_{\mathbb{S}} − 1]$) of + the integer from the previous layer. ## Incentive Description @@ -90,18 +95,29 @@ In general, there are three items in `Incentive AllowedConversion Struct`(but no Note that the absolute value of input and output must be consistent in incentive system. The quantity of input is negative and the quantity of output is positive. -To guarantee the input and output to be open as the same asset type in future unshielding transactions, the input and output assets have the same prefix description(e.g. BTC_1, BTC_2...BTC_n). And to prevent repeated shielding and unshielding and encourage long-term contribution to privacy pool, the postfix `timestamp` is used to distinct the input and output assets. The `timestamp` is depended on the update period and can be defined flexibly(e.g. date, epoch num). When new `timestamp` occurs, the `AllowedConversion` will be updated to support all the "history asset" conversion to the latest one. +To guarantee the input and output to be open as the same asset type in +future unshielding transactions, the input and output assets have the same +prefix description(e.g. BTC_1, BTC_2...BTC_n). To prevent repeated +shielding and unshielding and encourage long-term contribution to privacy +pool, the postfix `timestamp` is used to distinguish the input and output +assets. The `timestamp` depends on the update period and can be defined +flexibly (e.g. date, epoch num). When a new `timestamp` occurs, the +`AllowedConversion` will be updated to support all the "history asset" conversion to the latest one. -### Incentive AllowedConversion Operatioin -`Incentive AllowedConversion` is governed by incentive system, who will be in charge of issuing new incentive plan, updating(modifying) to the latest `timestamp` and destroying the disabled. +### Incentive AllowedConversion Operation +`Incentive AllowedConversion` is governed by the incentive system, which +will be in charge of issuing new incentive plan, updating(modifying) to the latest +`timestamp`, and removing disabled conversion permissions. * Issue * Issue a new incentive plan for new asset. * Issue for the last latest `AllowedConversion` when new `timestamp` occurs. * Update - * For every new `timestamp` occurs, updating the existing `AllowedConversion`. Keep the input still, update the output to the latest asset and modify the reward quantity according to the ratio. + * For every new `timestamp` that occurs, update the existing + `AllowedConversion`. Keep the input but update the output to the latest + asset and modify the reward quantity according to the ratio. * Destroy - * Detele the `AllowedConversion` from the tree. + * Delete the `AllowedConversion` from the tree. * Query Service * A service for querying the latest `AllowedConversion`, return (anchor, path, AllowedConversion). diff --git a/documentation/specs/src/masp/ledger-integration.md b/documentation/specs/src/masp/ledger-integration.md index ebd0294af1..0eed1d7d80 100644 --- a/documentation/specs/src/masp/ledger-integration.md +++ b/documentation/specs/src/masp/ledger-integration.md @@ -122,110 +122,6 @@ of the pool. The client needs the capability to create notes, transactions, and proofs of transactions, but it has the advantage of simply being able to link against the MASP crates, unlike the VP. -### 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 -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: -``` -anomaw --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: -``` -anomaw masp gen-addr --alias my-pa1 --key my-sk -anomaw 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: -``` -anomaw masp add --alias my-sk --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv -anomaw masp add --alias my-vk --value xfvktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7erg38awgq60r259csg3lxeeyy5355f5nj3ywpeqgd2guqd73uxz46645d0ayt9em88wflka0vsrq29u47x55psw93ly80lvftzdr5ccrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqt7n63v -anomaw 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 -the specified amount from the source address, and a corresponding -increase in the balance of the MASP validity predicate's address. The -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: -``` -anomac 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 -specified amount from the MASP validity predicate's address and a -corresponding increase in the transparent target address. The gas fee -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: -``` -anomac 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 -address. The gas fee is charged to the signer's address. Once the -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: -``` -anomac 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 -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: -``` -anomac balance -anomac balance --owner my-key -anomac balance --owner my-key --token BTC -anomac 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: -``` -anomaw masp list-keys -anomaw masp list-keys --unsafe-show-secret -anomaw masp list-keys --unsafe-show-secret --decrypt -anomaw 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: -``` -anomaw masp find --alias my-alias -anomaw masp find --alias my-alias --unsafe-show-secret -``` - ## Protocol ### Note Format @@ -338,6 +234,8 @@ pub struct Transfer { pub token: Address, /// The amount of tokens pub amount: Amount, + /// The unused storage location at which to place TxId + pub key: Option, /// Shielded transaction part pub shielded: Option, } @@ -350,6 +248,8 @@ Below, the conditions necessary for a valid shielded or unshielded transfer are * the `Transfer` must satisfy the usual conditions for Anoma ledger transfers (i.e. sufficient funds, ...) as enforced by token and account validity predicates * the `Transaction` must satisfy the conditions specified in the [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) * the `Transaction` and `Transfer` together must additionaly satisfy the below boundary conditions intended to ensure consistency between the MASP validity predicate ledger and Anoma ledger +* A key equal to `None` indicates an unpinned shielded transaction; one that can only be found by scanning and trial-decrypting the entire shielded pool +* Otherwise the key must have the form `Some(x)` where `x` is a `String` such that there exists no prior accepted transaction with the same key ### Boundary Conditions Below, the conditions necessary to maintain consistency between the MASP validity predicate ledger and Anoma ledger are outlined: @@ -359,7 +259,9 @@ Below, the conditions necessary to maintain consistency between the MASP validit * its public key must be the hash of the target address bytes - this prevents replay attacks altering transfer destinations * the hash is specifically a RIPEMD-160 of a SHA-256 of the input bytes * its value must equal that of the containing transfer - this prevents replay attacks altering transfer amounts - * its asset type must be derived from the token address raw bytes - this prevents replay attacks altering transfer asset types + * its asset type must be derived from the token address raw bytes and the current epoch once Borsh serialized from the type `(Address, Epoch)`: + * the dependency on the address prevents replay attacks altering transfer asset types + * the current epoch requirement prevents attackers from claiming extra rewards by forging the time when they began to receive rewards * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` * If the source address is the MASP validity predicate, then: * no transparent inputs are permitted in the shielded transaction @@ -369,7 +271,9 @@ Below, the conditions necessary to maintain consistency between the MASP validit * If the source address is not the MASP validity predicate, then: * there must be exactly one transparent input in the shielded transaction and: * its value must equal that of amount in the containing transfer - this prevents stealing/losing funds from/to the pool - * its asset type must be derived from the token address raw bytes - this prevents stealing/losing funds from/to the pool + * its asset type must be derived from the token address raw bytes and the current epoch once Borsh serialized from the type `(Address, Epoch)`: + * the address dependency prevents stealing/losing funds from/to the pool + * the current epoch requirement ensures that withdrawers receive their full reward when leaving the shielded pool * the derivation must be done as specified in `0.3 Derivation of Asset Generator from Asset Identifer` ## Remarks @@ -378,9 +282,12 @@ Below are miscellaneous remarks on the capabilities and limitations of the curre * As a consequence, an amount exceeding the gas fees must be available in a transparent account in order to execute an unshielding transaction - this prevents denial of service attacks * Using the MASP sentinel transaction key for transaction signing indicates that gas be drawn from the transaction's transparent value pool * In this case, the gas will be taken from the MASP transparent address if the shielded transaction is proven to be valid +* With knowledge of its key, a pinned shielded transaction can be directly downloaded or proven non-existent without scanning the entire blockchain + * It is recommended that pinned transaction's key be derived from the hash of its payment address, something that both transaction parties would share + * This key must not be reused, this is in order to avoid revealing that multiple transactions are going to the same entity ## Multi-Asset Shielded Pool Specification Differences from Zcash Protocol Specification -The [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) referenced above is in turn an extension to the [Zcash Protocol Specification](https://zips.z.cash/protocol/protocol.pdf). Below, the changes from the Zcash Protocol Specification assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: +The [Multi-Asset Shielded Pool Specication](https://media.githubusercontent.com/media/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) referenced above is in turn an extension to the [Zcash Protocol Specification](https://zips.z.cash/protocol/protocol.pdf). Below, the changes from the Zcash Protocol Specification assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: * [3.2 Notes](https://zips.z.cash/protocol/protocol.pdf#notes) * Sapling note tuple must include asset type * Note commitment must be parameterized by asset type @@ -398,6 +305,7 @@ The [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/an * `NoteCommit` and hence `cm` must be parameterized by asset type * `ValueCommit` and hence `cv` must be parameterized by asset type * [4.13 Balance and Binding Signature (Sapling)](https://zips.z.cash/protocol/protocol.pdf#saplingbalance) + * The Sapling balance value is now defined as the net value of Spend and [Convert](#convert-descriptions) transfers minus Output transfers. * The Sapling balance value is no longer a scalar but a vector of pairs comprising values and asset types * Addition, subtraction, and equality checks of Sapling balance values is now done component-wise * A Sapling balance value is defined to be non-negative iff each of its components is non-negative @@ -427,9 +335,52 @@ The [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/an * a length `nValueBalanceSapling` sequence of 40 byte values where: * the first 32 bytes encode the asset type * the last 8 bytes are an `int64` encoding asset value + * In between `vSpendsSapling` and `nOutputsSapling` are two additional rows: + * First row: + * Bytes: Varies + * Name: nConvertsMASP + * Data Type: compactSize + * Description: The number of Convert descriptions in vConvertsMASP + * Second row: + * Bytes: 64*nConvertsMASP + * Name: vConvertsMASP + * Data Type: ConvertDescription[nConvertsMASP] + * Description: A sequence of Convert descriptions, encoded as described in the following section. * [7.4 Output Description Encoding and Consensus](https://zips.z.cash/protocol/protocol.pdf#outputencodingandconsensus) * The `encCiphertext` field must be 612 bytes in order to make 32 bytes room to encode the asset type +### Additional Sections +In addition to the above components of shielded transactions inherited from Zcash, we have the following: +#### Convert Descriptions +Each transaction includes a sequence of zero or more Convert descriptions. + +Let `ValueCommit.Output` be as defined in [4.1.8](https://zips.z.cash/protocol/protocol.pdf#abstractcommit) Commitment. +Let `B[Sapling Merkle]` be as defined in [5.3](https://zips.z.cash/protocol/protocol.pdf#constants) Constants. +Let `ZKSpend` be as defined in [4.1.13](https://zips.z.cash/protocol/protocol.pdf#abstractzk) Zero-Knowledge Proving System. + +A convert description comprises `(cv, rt, pi)` where +* `cv: ValueCommit.Output` is value commitment to the value of the conversion note +* `rt: B[Sapling Merkle]` is an anchor for the current conversion tree or an archived conversion tree +* `pi: ZKConvert.Proof` is a zk-SNARK proof with primary input `(rt, cv)` for the Convert statement defined at [Burn and Mint conversion transactions in MASP](./burn-and-mint.md). +#### Convert Description Encoding +Let `pi_{ZKConvert}` be the zk-SNARK proof of the corresponding Convert statement. `pi_{ZKConvert}` is encoded in the `zkproof` field of the Convert description. + +An abstract Convert description, as described above, is encoded in a transaction as an instance of a `ConvertDescription` type: +* First Entry + * Bytes: 32 + * Name: `cv` + * Data Type: `byte[32]` + * Description: A value commitment to the value of the conversion note, `LEBS2OSP_256(repr_J(cv))`. +* Second Entry + * Bytes: 32 + * Name: `anchor` + * Data Type: `byte[32]` + * Description: A root of the current conversion tree or an archived conversion tree, `LEBS2OSP_256(rt^Sapling)`. +* Third Entry + * Bytes: 192 + * Name: `zkproof` + * Data Type: `byte[192]` + * Description: An encoding of the zk-SNARK proof `pi_{ZKConvert}` (see [5.4.10.2](https://zips.z.cash/protocol/protocol.pdf#groth) `Groth16`). ## Required Changes to ZIP 32: Shielded Hierarchical Deterministic Wallets Below, the changes from [ZIP 32: Shielded Hierarchical Deterministic Wallets](https://zips.z.cash/zip-0032) assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: * [Specification: Key Encodings](https://zips.z.cash/zip-0032#specification-key-encodings) @@ -438,3 +389,42 @@ Below, the changes from [ZIP 32: Shielded Hierarchical Deterministic Wallets](ht * For extended spending keys on the Testnet, the Human-Readable Part is "xsktest" * [Sapling extended full viewing keys](https://zips.z.cash/zip-0032#sapling-extended-full-viewing-keys) * For extended full viewing keys on the Testnet, the Human-Readable Part is "xfvktest" + +# Storage Interface Specification +Anoma nodes provide interfaces that allow Anoma clients to query for specific pinned transactions, transactions accepted into the shielded pool, and allowed conversions between various asset types. Below we describe the ABCI paths and the encodings of the responses to each type of query. + +## Shielded Transfer Query +In order to determine shielded balances belonging to particular keys or spend one's balance, it is necessary to download the transactions that transferred the assets to you. To this end, the nth transaction in the shielded pool can be obtained by getting the value at the storage path `/tx-`. Note that indexing is 0-based. This will return a quadruple of the type below: + +``` +( + /// the epoch of the transaction's block + Epoch, + /// the height of the transaction's block + BlockHeight, + /// the index of the transaction within the block + TxIndex, + /// the actual bytes of the transfer + Transfer +) +``` +`Transfer` is defined as above and `(Epoch, BlockHeight, TxIndex) = (u64, u64, u32)`. +## Transaction Count Query +When scanning the shielded pool, it is sometimes useful know when to stop scanning. This can be done by querying the storage path `head-tx`, which will return a `u64` indicating the total number of transactions in the shielded pool. +## Pinned Transfer Query +A transaction pinned to the key `x` in the shielded pool can be obtained indirectly by getting the value at the storage path `/pin-`. This will return the index of the desired transaction within the shielded pool encoded as a `u64`. At this point, the above shielded transaction query can then be used to obtain the actual transaction bytes. +## Conversion Query +In order for MASP clients to convert older asset types to their latest variants, they need to query nodes for currently valid conversions. This can be done by querying the ABCI path `conv/` where `asset-type` is a hexadecimal encoding of the asset identifier as defined in [Multi-Asset Shielded Pool Specication](https://media.githubusercontent.com/media/anoma/masp/main/docs/multi-asset-shielded-pool.pdf). This will return a quadruple of the type below: +``` +( + /// the token address of this asset type + Address, + /// the epoch of this asset type + Epoch, + /// the amount to be treated as equivalent to zero + Amount, + /// the Merkle path to this conversion + MerklePath +) +``` +If no conversions are available the amount will be exactly zero, otherwise the amount must contain negative units of the queried asset type. diff --git a/documentation/specs/src/masp/trusted-setup-assets/namada-ts-arch.png b/documentation/specs/src/masp/trusted-setup-assets/namada-ts-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..7d81106692bbc4857b52ffc69edda08806e58e55 GIT binary patch literal 434042 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

J%4+h)R_fF1~fbR;zi5#n84>bTOnE?*ua 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

_h{JA?e}>9`Tg7(Z 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

&WQF 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 literal 0 HcmV?d00001 diff --git a/documentation/specs/src/masp/trusted-setup-assets/namda-ts-swimlane.png b/documentation/specs/src/masp/trusted-setup-assets/namda-ts-swimlane.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f3da8780f048e7a9a0a687cd91dda3f623b781 GIT binary patch literal 84319 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 literal 0 HcmV?d00001 diff --git a/documentation/specs/src/masp/trusted-setup.md b/documentation/specs/src/masp/trusted-setup.md index 8b1b33a2a7..acc14f723d 100644 --- a/documentation/specs/src/masp/trusted-setup.md +++ b/documentation/specs/src/masp/trusted-setup.md @@ -3,16 +3,28 @@ This spec assumes that you have some previous knowledge about Trusted Setup Cere The Namada Trusted Setup (TS) consists of running the phase 2 of the MPC which is a circuit-specific step to construct the multi-asset shielded pool circuit. Our phase 2 takes as input the Powers of Tau (phase 1) ran by Zcash that can be found [here](https://download.z.cash/downloads/powersoftau/). -## Overview of the contribution flow -NOTE: add CLI flag `--offline` for the contributors that run on an offline machine. The flag will skip all the steps where there is communication with the coordinator and go straight to the generation of parameters in step 14. + +## Contribution flow + +### Overview + +1. Contributor compiles or downloads the CLI binary and runs it. +2. CLI generates a 24-words `BIP39` mnemonic. +3. CLI can choose to participate in the incentivized program or not. +4. CLI joins the queue and waits for its turn. +5. CLI downloads the challenge from the nearest AWS S3 bucket. +6. Contributor can choose to contribute on the same machine or another. +7. Contributor can choose to give its own seed of randomness or not. +8. CLI contributes. +9. CLI uploads the response to the challenge and notifies the coordinator with its personal info. + +### Detailed Flow +**NOTE:** add CLI flag `--offline` for the contributors that run on an offline machine. The flag will skip all the steps where there is communication with the coordinator and go straight to the generation of parameters in step 14. 1. Contributor downloads the Namada CLI source from GitHub, compiles it, runs it. 2. CLI asks the Contributor a couple of questions: a) Do you want to participate in the incentivized trusted setup? - Yes. Asks for personal information: full name and email. - No. Contribution will be identified as Anonymous. - b) Do you want to take part in the contest? - - Yes. This assumes that the Contributor will generate the parameters with another method than the default implementation and that in any case the contribution file will be uploaded through the CLI. - - No. Pursue with the normal flow. 3. CLI generates a `ed25519` key pair that will serve to communicate and sign requests with the HTTP REST API endpoints and receive any potential rewards. The private key is generated through [BIP39](https://docs.rs/tiny-bip39/latest/bip39/#) where we use it as a seed for the `ed25519` key pair and a 24 word seed-phrase is presented to the user to back-up. 5. CLI sends request to the HTTP REST API endpoint `contributor/join_queue`. Contributor is added to the queue of the ceremony. @@ -41,7 +53,7 @@ NOTE: CLI will display a countdown of 10 min with an extension capability of 5 m ## Subcomponents -![](https://hackmd.io/_uploads/BJ-OIAhD5.png) +![](./trusted-setup-assets/namada-ts-arch.png) Our implementation of the TS consists of the following subcomponents: 1. A fork of the [Aleo Trusted Setup](https://github.com/AleoHQ/aleo-setup/) where we re-used the Coordinator Library (CL) contained in the `phase1-coordinator` folder. @@ -64,17 +76,24 @@ To be able to re-use the CL without heavy refactoring, we decided to keep most o ## 2. HTTP REST API ### Description -The HTTP REST API is a rocket web server that interfaces with the CL. All requests need to be signed to be accepted by the endpoints. It's the core of the ceremony where the Coordinator is started together with utility functions like `verify_contributions`  and `update_coordinator`. +The [HTTP REST API](https://github.com/anoma/namada-trusted-setup/blob/main/phase1-coordinator/src/rest.rs) is a rocket web server that interfaces with the CL. All requests need to be signed to be accepted by the endpoints. It's the core of the ceremony where the Coordinator is started together with utility functions like `verify_contributions`  and `update_coordinator`. ### Endpoints -- `contributor/join_queue` -- `contributor/lock_chunk` -- `download/chunk` -- `contributor/challenge` -- `upload/chunk` -- `contributor/contribute_chunk` -- `contributor/heartbeat` -- `contributor/get_tasks_left` +- `/contributor/join_queue` Add the incoming contributor to the queue of contributors. +- `/contributor/lock_chunk` Lock a Chunk in the ceremony. This should be the first function called when attempting to contribute to a chunk. Once the chunk is locked, it is ready to be downloaded. +- `/contributor/challenge` Get the challenge key on Amazon S3 from the Coordinator. +- `/upload/chunk` Request the urls where to upload a Chunk contribution and the ContributionFileSignature. +- `/contributor/contribute_chunk` Notify the Coordinator of a finished and uploaded Contribution. This will unlock the given Chunk. +- `/contributor/heartbeat` Let the Coordinator know that the participant is still alive and participating (or waiting to participate) in the ceremony. +- `/update` Update the Coordinator state. This endpoint is accessible only by the coordinator itself. +- `/stop` Stop the Coordinator and shuts the server down. This endpoint is accessible only by the coordinator itself. +- `/verify` Verify all the pending contributions. This endpoint is accessible only by the coordinator itself. +- `/contributor/queue_status` Get the queue status of the contributor. +- `/contributor/contribution_info` Write ContributionInfo to disk. +- `/contribution_info` Retrieve the contributions' info. This endpoint is accessible by anyone and does not require a signed request. +- `/healthcheck` Retrieve healthcheck info. This endpoint is accessible by anyone and does not require a signed request. + +![](./trusted-setup-assets/namda-ts-swimlane.png) ### Saved files - `contributors/namada_contributor_info_round_{i}.json` contributor info received from the participant. Same file as described below. @@ -126,8 +145,6 @@ The CLI communicates with the HTTP REST API accordingly to the [overview of the "public_key":"very random public key", // User participates in incentivized program or not "is_incentivized":true, - // User expresses his intent to participate or not in the contest for creative contributions - "is_contest_participant":true, // User can choose to contribute on another machine "is_another_machine":true, // User can choose the default method to generate randomness or his own. From 89b36cfb194cccd993bf025bd7904f01a75df21c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 15:19:34 +0100 Subject: [PATCH 0350/1995] Add Ethereum events vote extensions mod to apps --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 5 +++++ .../lib/node/ledger/shell/vote_extensions/ethereum_events.rs | 1 + 2 files changed, 6 insertions(+) create mode 100644 apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 8cd86c1b78..165c1f3f17 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,3 +1,8 @@ +//! Extend Tendermint votes with Ethereum bridge logic. + +#[cfg(not(feature = "ABCI"))] +pub mod ethereum_events; + #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs new file mode 100644 index 0000000000..f750c6f6e9 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -0,0 +1 @@ +//! Extend Tendermint votes with Ethereum events seen by a quorum of validators. From 806e46ccbaa4a48a2e49248b52d06f75ec94daeb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 15:48:41 +0100 Subject: [PATCH 0351/1995] Deserialize VoteExtension instances from a local commit info --- .../lib/node/ledger/shell/prepare_proposal.rs | 6 +++-- .../lib/node/ledger/shell/vote_extensions.rs | 25 ++++++++----------- shared/src/types/vote_extensions.rs | 6 +++++ .../vote_extensions/validator_set_update.rs | 4 ++- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 217e19fac9..4ff0d334d6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -192,7 +192,8 @@ mod prepare_block { ); let mut voting_power = FractionalVotingPower::default(); - let deserialized = deserialize_vote_extensions(vote_extensions); + let deserialized = deserialize_vote_extensions(vote_extensions) + .map(|vext| vext.ethereum_events); for (validator_voting_power, vote_extension) in self.filter_invalid_vote_extensions(deserialized) @@ -352,7 +353,8 @@ mod prepare_block { let votes = deserialize_vote_extensions(vec![vote_extension_serialize( vext, - )]); + )]) + .map(|vext| vext.ethereum_events); let filtered_votes: Vec<_> = shell.filter_invalid_vote_extensions(votes).collect(); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 165c1f3f17..00aa52f16a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -8,7 +8,7 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; - use namada::types::vote_extensions::ethereum_events; + use namada::types::vote_extensions::{ethereum_events, VoteExtension}; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::queries::QueriesExt; @@ -259,21 +259,16 @@ mod extend_votes { // set update vote extensions pub fn deserialize_vote_extensions( vote_extensions: Vec, - ) -> impl Iterator> + 'static { + ) -> impl Iterator + 'static { vote_extensions.into_iter().filter_map(|vote| { - Signed::::try_from_slice( - &vote.vote_extension[..], - ) - .map_err(|err| { - tracing::error!( - ?err, - // TODO: change this error message, probably, such that - // it mentions Ethereum events rather than vote - // extensions - "Failed to deserialize signed vote extension", - ); - }) - .ok() + VoteExtension::try_from_slice(&vote.vote_extension[..]) + .map_err(|err| { + tracing::error!( + ?err, + "Failed to deserialize signed vote extension", + ); + }) + .ok() }) } diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index e47d74ecb1..1e0def9001 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -3,10 +3,13 @@ pub mod ethereum_events; pub mod validator_set_update; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + use crate::proto::Signed; /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct VoteExtension { /// Vote extension data related with Ethereum events. pub ethereum_events: Signed, @@ -21,6 +24,9 @@ pub struct VoteExtension { /// [`crate::types::transaction::protocol::ProtocolTxType`] transactions: /// - A `ProtocolTxType::EthereumEvents` tx, and /// - A `ProtocolTxType::ValidatorSetUpdate` tx +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct VoteExtensionDigest { /// The digest of Ethereum events vote extension signatures. pub ethereum_events: ethereum_events::VextDigest, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 2e26d6369b..f5197b32a9 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -22,7 +22,9 @@ const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct VextDigest { /// A mapping from a validator address to a [`Signature`]. pub signatures: HashMap, From aa74d4d10311fe2c9eb191049b09d796784f413f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 8 Aug 2022 17:10:57 +0100 Subject: [PATCH 0352/1995] WIP: Move relevant code to vote_extensions/ethereum_events.rs --- .../lib/node/ledger/shell/vote_extensions.rs | 418 +---------------- .../shell/vote_extensions/ethereum_events.rs | 419 ++++++++++++++++++ 2 files changed, 421 insertions(+), 416 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 00aa52f16a..df08b6a434 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -108,155 +108,11 @@ mod extend_votes { } } } - - /// Validates an Ethereum events vote extension issued at the provided - /// block height - /// - /// Checks that at epoch of the provided height: - /// * The Tendermint address corresponds to an active validator - /// * The validator correctly signed the extension - /// * The validator signed over the correct height inside of the - /// extension - /// * There are no duplicate Ethereum events in this vote extension, - /// and the events are sorted in ascending order - #[inline] - pub fn validate_eth_events_vext( - &self, - ext: Signed, - last_height: BlockHeight, - ) -> bool { - self.validate_eth_events_vext_and_get_it_back(ext, last_height) - .is_ok() - } - - /// This method behaves exactly like [`Self::validate_eth_events_vext`], - /// with the added bonus of returning the vote extension back, if it - /// is valid. - pub fn validate_eth_events_vext_and_get_it_back( - &self, - ext: Signed, - last_height: BlockHeight, - ) -> std::result::Result< - (VotingPower, Signed), - VoteExtensionError, - > { - if ext.data.block_height != last_height { - let ext_height = ext.data.block_height; - tracing::error!( - "Vote extension issued for a block height {ext_height} \ - different from the expected height {last_height}" - ); - return Err(VoteExtensionError::UnexpectedBlockHeight); - } - if last_height.0 == 0 { - tracing::error!("Dropping vote extension issued at genesis"); - return Err(VoteExtensionError::IssuedAtGenesis); - } - // verify if we have any duplicate Ethereum events, - // and if these are sorted in ascending order - let have_dupes_or_non_sorted = { - !ext.data - .ethereum_events - // TODO: move to `array_windows` when it reaches Rust stable - .windows(2) - .all(|evs| evs[0] < evs[1]) - }; - let validator = &ext.data.validator_addr; - if have_dupes_or_non_sorted { - tracing::error!( - %validator, - "Found duplicate or non-sorted Ethereum events in a vote extension from validator" - ); - return Err(VoteExtensionError::HaveDupesOrNonSorted); - } - // get the public key associated with this validator - let epoch = self.storage.block.pred_epochs.get_epoch(last_height); - let (voting_power, pk) = self - .storage - .get_validator_from_address(validator, epoch) - .map_err(|err| { - tracing::error!( - ?err, - %validator, - "Could not get public key from Storage for validator" - ); - VoteExtensionError::PubKeyNotInStorage - })?; - // verify the signature of the vote extension - ext.verify(&pk) - .map_err(|err| { - tracing::error!( - ?err, - %validator, - "Failed to verify the signature of a vote extension issued by validator" - ); - VoteExtensionError::VerifySigFailed - }) - .map(|_| (voting_power, ext)) - } - - /// Checks the channel from the Ethereum oracle monitoring - /// the fullnode and retrieves all seen Ethereum events. - pub fn new_ethereum_events(&mut self) -> Vec { - match &mut self.mode { - ShellMode::Validator { - ref mut ethereum_recv, - .. - } => { - ethereum_recv.fill_queue(); - ethereum_recv.get_events() - } - _ => vec![], - } - } - - /// Takes an iterator over vote extension instances, - /// and returns another iterator. The latter yields - /// valid vote extensions, or the reason why these - /// are invalid, in the form of a [`VoteExtensionError`]. - // TODO: the `vote_extensions` iterator should be over `VoteExtension` - // instances, I guess? to be determined in the next PR - #[inline] - pub fn validate_vote_extension_list( - &self, - vote_extensions: impl IntoIterator> - + 'static, - ) -> impl Iterator< - Item = std::result::Result< - (VotingPower, Signed), - VoteExtensionError, - >, - > + '_ { - vote_extensions.into_iter().map(|vote_extension| { - self.validate_eth_events_vext_and_get_it_back( - vote_extension, - self.storage.last_height, - ) - }) - } - - /// Takes a list of signed vote extensions, - /// and filters out invalid instances. - // TODO: the `vote_extensions` iterator should be over `VoteExtension` - // instances, I guess? to be determined in the next PR - #[inline] - pub fn filter_invalid_vote_extensions( - &self, - vote_extensions: impl IntoIterator> - + 'static, - ) -> impl Iterator)> + '_ - { - self.validate_vote_extension_list(vote_extensions) - .filter_map(|ext| ext.ok()) - } } /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// ones we could deserialize to [`Signed`] + /// ones we could deserialize to [`VoteExtension`] /// instances. - // TODO: we need to return an iterator over instances of `VoteExtension`, - // which contain both the ethereum events vote extensions and validator - // set update vote extensions pub fn deserialize_vote_extensions( vote_extensions: Vec, ) -> impl Iterator + 'static { @@ -265,282 +121,12 @@ mod extend_votes { .map_err(|err| { tracing::error!( ?err, - "Failed to deserialize signed vote extension", + "Failed to deserialize data as a VoteExtension", ); }) .ok() }) } - - #[cfg(test)] - mod test_vote_extensions { - use std::convert::TryInto; - - use borsh::{BorshDeserialize, BorshSerialize}; - use namada::ledger::pos; - use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::proto::Signed; - use namada::types::ethereum_events::{ - EthAddress, EthereumEvent, TransferToEthereum, - }; - use namada::types::key::*; - use namada::types::storage::{BlockHeight, Epoch}; - use namada::types::vote_extensions::ethereum_events; - use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; - use tower_abci::request; - - use crate::node::ledger::shell::queries::QueriesExt; - use crate::node::ledger::shell::test_utils::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; - - /// Test that we successfully receive ethereum events - /// from the channel to fullnode process - /// - /// We further check that ledger side buffering is done if multiple - /// events are in the channel and that queueing and de-duplicating is - /// done - #[test] - fn test_get_eth_events() { - let (mut shell, _, oracle) = setup(); - let event_1 = EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }; - let event_2 = EthereumEvent::TransfersToEthereum { - nonce: 2.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }; - let event_3 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - }; - - oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); - let [event_first, event_second]: [EthereumEvent; 2] = - shell.new_ethereum_events().try_into().expect("Test failed"); - - assert_eq!(event_first, event_1); - assert_eq!(event_second, event_3); - // check that we queue and de-duplicate events - oracle.send(event_2.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); - let [event_first, event_second, event_third]: [EthereumEvent; 3] = - shell.new_ethereum_events().try_into().expect("Test failed"); - - assert_eq!(event_first, event_1); - assert_eq!(event_second, event_2); - assert_eq!(event_third, event_3); - } - - /// Test that ethereum events are added to vote extensions. - /// Check that vote extensions pass verification. - #[test] - fn test_eth_events_vote_extension() { - let (mut shell, _, oracle) = setup(); - let address = shell - .mode - .get_validator_address() - .expect("Test failed") - .clone(); - let event_1 = EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }; - let event_2 = EthereumEvent::NewContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - }; - oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_2.clone()).expect("Test failed"); - let vote_extension = - as BorshDeserialize>::try_from_slice( - &shell.extend_vote(Default::default()).vote_extension[..], - ) - .expect("Test failed"); - - let [event_first, event_second]: [EthereumEvent; 2] = - vote_extension - .data - .ethereum_events - .clone() - .try_into() - .expect("Test failed"); - - assert_eq!(event_first, event_1); - assert_eq!(event_second, event_2); - let req = request::VerifyVoteExtension { - hash: vec![], - validator_address: address - .raw_hash() - .expect("Test failed") - .as_bytes() - .to_vec(), - height: 0, - vote_extension: vote_extension - .try_to_vec() - .expect("Test failed"), - }; - let res = shell.verify_vote_extension(req); - assert_eq!(res.status, i32::from(VerifyStatus::Accept)); - } - - /// Test that Ethereum events signed by a non-validator are rejected - #[test] - fn test_eth_events_must_be_signed_by_validator() { - let (shell, _, _) = setup(); - let signing_key = gen_keypair(); - let address = shell - .mode - .get_validator_address() - .expect("Test failed") - .clone(); - let vote_ext = ethereum_events::Vext { - ethereum_events: vec![EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }], - block_height: shell.storage.last_height + 1, - validator_addr: address.clone(), - } - .sign(&signing_key) - .try_to_vec() - .expect("Test failed"); - let req = request::VerifyVoteExtension { - hash: vec![], - validator_address: address - .raw_hash() - .expect("Test failed") - .as_bytes() - .to_vec(), - height: 0, - vote_extension: vote_ext, - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); - } - - /// Test that validation of Ethereum events cast during the - /// previous block are accepted for the current block. This - /// should pass even if the epoch changed resulting in a - /// change to the validator set. - #[test] - fn test_validate_vote_extensions() { - let (mut shell, _, _) = setup(); - let signing_key = - shell.mode.get_protocol_key().expect("Test failed").clone(); - let address = shell - .mode - .get_validator_address() - .expect("Test failed") - .clone(); - let signed_height = shell.storage.last_height + 1; - let vote_ext = ethereum_events::Vext { - ethereum_events: vec![EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }], - block_height: signed_height, - validator_addr: address, - } - .sign(shell.mode.get_protocol_key().expect("Test failed")); - - assert_eq!(shell.storage.get_current_epoch().0.0, 0); - // We make a change so that there are no - // validators in the next epoch - let mut current_validators = shell.storage.read_validator_set(); - current_validators.data.insert( - 1, - Some(pos::types::ValidatorSet { - active: Default::default(), - inactive: Default::default(), - }), - ); - shell.storage.write_validator_set(¤t_validators); - // we advance forward to the next epoch - let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); - shell.storage.last_height = BlockHeight(11); - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - assert_eq!(shell.storage.get_current_epoch().0.0, 1); - assert!( - shell - .storage - .get_validator_from_protocol_pk(&signing_key.ref_to(), None) - .is_err() - ); - let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); - assert!( - shell - .shell - .storage - .get_validator_from_protocol_pk( - &signing_key.ref_to(), - Some(prev_epoch) - ) - .is_ok() - ); - - assert!(shell.validate_eth_events_vext(vote_ext, signed_height)); - } - - /// Test that an [`ethereum_events::Vext`] that incorrectly labels what - /// block it was included on in a vote extension is rejected - #[test] - fn reject_incorrect_block_number() { - let (shell, _, _) = setup(); - let address = shell.mode.get_validator_address().unwrap().clone(); - let vote_ext = ethereum_events::Vext { - ethereum_events: vec![EthereumEvent::TransfersToEthereum { - nonce: 1.into(), - transfers: vec![TransferToEthereum { - amount: 100.into(), - asset: EthAddress([1; 20]), - receiver: EthAddress([2; 20]), - }], - }], - block_height: shell.storage.last_height, - validator_addr: address.clone(), - } - .sign(shell.mode.get_protocol_key().expect("Test failed")) - .try_to_vec() - .expect("Test failed"); - - let req = request::VerifyVoteExtension { - hash: vec![], - validator_address: address.try_to_vec().expect("Test failed"), - height: 0, - vote_extension: vote_ext, - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); - } - } } #[cfg(not(feature = "ABCI"))] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index f750c6f6e9..8b548741ad 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -1 +1,420 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. + +// TODO: import deps here + +impl Shell +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + /// Validates an Ethereum events vote extension issued at the provided + /// block height + /// + /// Checks that at epoch of the provided height: + /// * The Tendermint address corresponds to an active validator + /// * The validator correctly signed the extension + /// * The validator signed over the correct height inside of the + /// extension + /// * There are no duplicate Ethereum events in this vote extension, + /// and the events are sorted in ascending order + #[inline] + pub fn validate_eth_events_vext( + &self, + ext: Signed, + last_height: BlockHeight, + ) -> bool { + self.validate_eth_events_vext_and_get_it_back(ext, last_height) + .is_ok() + } + + /// This method behaves exactly like [`Self::validate_eth_events_vext`], + /// with the added bonus of returning the vote extension back, if it + /// is valid. + pub fn validate_eth_events_vext_and_get_it_back( + &self, + ext: Signed, + last_height: BlockHeight, + ) -> std::result::Result< + (VotingPower, Signed), + VoteExtensionError, + > { + if ext.data.block_height != last_height { + let ext_height = ext.data.block_height; + tracing::error!( + "Vote extension issued for a block height {ext_height} \ + different from the expected height {last_height}" + ); + return Err(VoteExtensionError::UnexpectedBlockHeight); + } + if last_height.0 == 0 { + tracing::error!("Dropping vote extension issued at genesis"); + return Err(VoteExtensionError::IssuedAtGenesis); + } + // verify if we have any duplicate Ethereum events, + // and if these are sorted in ascending order + let have_dupes_or_non_sorted = { + !ext.data + .ethereum_events + // TODO: move to `array_windows` when it reaches Rust stable + .windows(2) + .all(|evs| evs[0] < evs[1]) + }; + let validator = &ext.data.validator_addr; + if have_dupes_or_non_sorted { + tracing::error!( + %validator, + "Found duplicate or non-sorted Ethereum events in a vote extension from validator" + ); + return Err(VoteExtensionError::HaveDupesOrNonSorted); + } + // get the public key associated with this validator + let epoch = self.storage.block.pred_epochs.get_epoch(last_height); + let (voting_power, pk) = self + .storage + .get_validator_from_address(validator, epoch) + .map_err(|err| { + tracing::error!( + ?err, + %validator, + "Could not get public key from Storage for validator" + ); + VoteExtensionError::PubKeyNotInStorage + })?; + // verify the signature of the vote extension + ext.verify(&pk) + .map_err(|err| { + tracing::error!( + ?err, + %validator, + "Failed to verify the signature of a vote extension issued by validator" + ); + VoteExtensionError::VerifySigFailed + }) + .map(|_| (voting_power, ext)) + } + + /// Checks the channel from the Ethereum oracle monitoring + /// the fullnode and retrieves all seen Ethereum events. + pub fn new_ethereum_events(&mut self) -> Vec { + match &mut self.mode { + ShellMode::Validator { + ref mut ethereum_recv, + .. + } => { + ethereum_recv.fill_queue(); + ethereum_recv.get_events() + } + _ => vec![], + } + } + + /// Takes an iterator over vote extension instances, + /// and returns another iterator. The latter yields + /// valid vote extensions, or the reason why these + /// are invalid, in the form of a [`VoteExtensionError`]. + // TODO: the `vote_extensions` iterator should be over `VoteExtension` + // instances, I guess? to be determined in the next PR + #[inline] + pub fn validate_vote_extension_list( + &self, + vote_extensions: impl IntoIterator> + + 'static, + ) -> impl Iterator< + Item = std::result::Result< + (VotingPower, Signed), + VoteExtensionError, + >, + > + '_ { + vote_extensions.into_iter().map(|vote_extension| { + self.validate_eth_events_vext_and_get_it_back( + vote_extension, + self.storage.last_height, + ) + }) + } + + /// Takes a list of signed vote extensions, + /// and filters out invalid instances. + // TODO: the `vote_extensions` iterator should be over `VoteExtension` + // instances, I guess? to be determined in the next PR + #[inline] + pub fn filter_invalid_vote_extensions( + &self, + vote_extensions: impl IntoIterator> + + 'static, + ) -> impl Iterator)> + '_ + { + self.validate_vote_extension_list(vote_extensions) + .filter_map(|ext| ext.ok()) + } +} + +#[cfg(test)] +mod test_vote_extensions { + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::ledger::pos; + use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::proto::Signed; + use namada::types::ethereum_events::{ + EthAddress, EthereumEvent, TransferToEthereum, + }; + use namada::types::key::*; + use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::vote_extensions::ethereum_events; + use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + use tower_abci::request; + + use crate::node::ledger::shell::queries::QueriesExt; + use crate::node::ledger::shell::test_utils::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + + /// Test that we successfully receive ethereum events + /// from the channel to fullnode process + /// + /// We further check that ledger side buffering is done if multiple + /// events are in the channel and that queueing and de-duplicating is + /// done + #[test] + fn test_get_eth_events() { + let (mut shell, _, oracle) = setup(); + let event_1 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + let event_2 = EthereumEvent::TransfersToEthereum { + nonce: 2.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + let event_3 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = + shell.new_ethereum_events().try_into().expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_3); + // check that we queue and de-duplicate events + oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_3.clone()).expect("Test failed"); + let [event_first, event_second, event_third]: [EthereumEvent; 3] = + shell.new_ethereum_events().try_into().expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + assert_eq!(event_third, event_3); + } + + /// Test that ethereum events are added to vote extensions. + /// Check that vote extensions pass verification. + #[test] + fn test_eth_events_vote_extension() { + let (mut shell, _, oracle) = setup(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let event_1 = EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }; + let event_2 = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + oracle.send(event_1.clone()).expect("Test failed"); + oracle.send(event_2.clone()).expect("Test failed"); + let vote_extension = + as BorshDeserialize>::try_from_slice( + &shell.extend_vote(Default::default()).vote_extension[..], + ) + .expect("Test failed"); + + let [event_first, event_second]: [EthereumEvent; 2] = + vote_extension + .data + .ethereum_events + .clone() + .try_into() + .expect("Test failed"); + + assert_eq!(event_first, event_1); + assert_eq!(event_second, event_2); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), + height: 0, + vote_extension: vote_extension + .try_to_vec() + .expect("Test failed"), + }; + let res = shell.verify_vote_extension(req); + assert_eq!(res.status, i32::from(VerifyStatus::Accept)); + } + + /// Test that Ethereum events signed by a non-validator are rejected + #[test] + fn test_eth_events_must_be_signed_by_validator() { + let (shell, _, _) = setup(); + let signing_key = gen_keypair(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let vote_ext = ethereum_events::Vext { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }], + block_height: shell.storage.last_height + 1, + validator_addr: address.clone(), + } + .sign(&signing_key) + .try_to_vec() + .expect("Test failed"); + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address + .raw_hash() + .expect("Test failed") + .as_bytes() + .to_vec(), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } + + /// Test that validation of Ethereum events cast during the + /// previous block are accepted for the current block. This + /// should pass even if the epoch changed resulting in a + /// change to the validator set. + #[test] + fn test_validate_vote_extensions() { + let (mut shell, _, _) = setup(); + let signing_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let signed_height = shell.storage.last_height + 1; + let vote_ext = ethereum_events::Vext { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }], + block_height: signed_height, + validator_addr: address, + } + .sign(shell.mode.get_protocol_key().expect("Test failed")); + + assert_eq!(shell.storage.get_current_epoch().0.0, 0); + // We make a change so that there are no + // validators in the next epoch + let mut current_validators = shell.storage.read_validator_set(); + current_validators.data.insert( + 1, + Some(pos::types::ValidatorSet { + active: Default::default(), + inactive: Default::default(), + }), + ); + shell.storage.write_validator_set(¤t_validators); + // we advance forward to the next epoch + let mut req = FinalizeBlock::default(); + req.header.time = namada::types::time::DateTimeUtc::now(); + shell.storage.last_height = BlockHeight(11); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + assert_eq!(shell.storage.get_current_epoch().0.0, 1); + assert!( + shell + .storage + .get_validator_from_protocol_pk(&signing_key.ref_to(), None) + .is_err() + ); + let prev_epoch = Epoch(shell.storage.get_current_epoch().0.0 - 1); + assert!( + shell + .shell + .storage + .get_validator_from_protocol_pk( + &signing_key.ref_to(), + Some(prev_epoch) + ) + .is_ok() + ); + + assert!(shell.validate_eth_events_vext(vote_ext, signed_height)); + } + + /// Test that an [`ethereum_events::Vext`] that incorrectly labels what + /// block it was included on in a vote extension is rejected + #[test] + fn reject_incorrect_block_number() { + let (shell, _, _) = setup(); + let address = shell.mode.get_validator_address().unwrap().clone(); + let vote_ext = ethereum_events::Vext { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }], + block_height: shell.storage.last_height, + validator_addr: address.clone(), + } + .sign(shell.mode.get_protocol_key().expect("Test failed")) + .try_to_vec() + .expect("Test failed"); + + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address.try_to_vec().expect("Test failed"), + height: 0, + vote_extension: vote_ext, + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } +} From 13b87d751bd1a5b6c5a151204478966a87ec65d6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 08:52:46 +0100 Subject: [PATCH 0353/1995] Fix up deps --- .../lib/node/ledger/shell/vote_extensions.rs | 2 -- .../shell/vote_extensions/ethereum_events.rs | 35 +++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index df08b6a434..df887d6f02 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -6,12 +6,10 @@ pub mod ethereum_events; #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; - use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::proto::Signed; use namada::types::vote_extensions::{ethereum_events, VoteExtension}; use tendermint_proto::abci::ExtendedVoteInfo; - use super::super::queries::QueriesExt; use super::super::*; /// The error yielded from validating faulty vote extensions in the shell diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 8b548741ad..9015fd790b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -1,6 +1,15 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. -// TODO: import deps here +use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::proto::Signed; +use namada::types::ethereum_events::EthereumEvent; +use namada::types::storage::BlockHeight; +use namada::types::vote_extensions::ethereum_events; + +use super::*; +use crate::node::ledger::shell::queries::QueriesExt; +use crate::node::ledger::shell::{Shell, ShellMode}; impl Shell where @@ -13,10 +22,9 @@ where /// Checks that at epoch of the provided height: /// * The Tendermint address corresponds to an active validator /// * The validator correctly signed the extension - /// * The validator signed over the correct height inside of the - /// extension - /// * There are no duplicate Ethereum events in this vote extension, - /// and the events are sorted in ascending order + /// * The validator signed over the correct height inside of the extension + /// * There are no duplicate Ethereum events in this vote extension, and + /// the events are sorted in ascending order #[inline] pub fn validate_eth_events_vext( &self, @@ -248,13 +256,12 @@ mod test_vote_extensions { ) .expect("Test failed"); - let [event_first, event_second]: [EthereumEvent; 2] = - vote_extension - .data - .ethereum_events - .clone() - .try_into() - .expect("Test failed"); + let [event_first, event_second]: [EthereumEvent; 2] = vote_extension + .data + .ethereum_events + .clone() + .try_into() + .expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_2); @@ -266,9 +273,7 @@ mod test_vote_extensions { .as_bytes() .to_vec(), height: 0, - vote_extension: vote_extension - .try_to_vec() - .expect("Test failed"), + vote_extension: vote_extension.try_to_vec().expect("Test failed"), }; let res = shell.verify_vote_extension(req); assert_eq!(res.status, i32::from(VerifyStatus::Accept)); From 510436d7e24f941043885a242b376711f584c9b4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 10:27:07 +0100 Subject: [PATCH 0354/1995] Check if we are at the last block before a new epoch --- apps/src/lib/node/ledger/shell/queries.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 4f6a885677..8d24dbbbf8 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -321,6 +321,10 @@ pub(crate) trait QueriesExt { tm_address: &[u8], epoch: Option, ) -> std::result::Result; + + /// Verifies if we are at the last block before the + /// start of a new epoch. + fn is_last_block_before_new_epoch(&self) -> bool; } impl QueriesExt for Storage @@ -491,4 +495,11 @@ where ) }) } + + fn is_last_block_before_new_epoch(&self) -> bool { + let current_height = self.last_height.0 + 1; + let new_epoch_height = self.next_epoch_min_start_height.0; + + (new_epoch_height - current_height) == 1 + } } From 6a6c9a9415baaaa25820f7ab44e8381a8b567f6f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 10:32:20 +0100 Subject: [PATCH 0355/1995] A bit of cleanup --- apps/src/lib/node/ledger/shell/queries.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 8d24dbbbf8..9afd3c0832 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -408,7 +408,6 @@ where } } - #[allow(dead_code)] fn get_validator_from_protocol_pk( &self, pk: &key::common::PublicKey, @@ -478,7 +477,6 @@ where .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) } - #[allow(dead_code)] fn get_validator_from_tm_address( &self, tm_address: &[u8], @@ -499,7 +497,6 @@ where fn is_last_block_before_new_epoch(&self) -> bool { let current_height = self.last_height.0 + 1; let new_epoch_height = self.next_epoch_min_start_height.0; - - (new_epoch_height - current_height) == 1 + new_epoch_height.wrapping_sub(current_height) == 1 } } From 8958bbeeab549549179a0fccaafbe97b334ab806 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 11:02:49 +0100 Subject: [PATCH 0356/1995] Extend vote with validator set update --- .../lib/node/ledger/shell/vote_extensions.rs | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index df887d6f02..3b9dc89b80 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -7,10 +7,13 @@ pub mod ethereum_events; mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; - use namada::types::vote_extensions::{ethereum_events, VoteExtension}; + use namada::types::vote_extensions::{ + ethereum_events, validator_set_update, VoteExtension, + }; use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; + use crate::node::ledger::shell::queries::QueriesExt; /// The error yielded from validating faulty vote extensions in the shell #[derive(Error, Debug)] @@ -48,17 +51,40 @@ mod extend_votes { .get_validator_address() .expect("only validators should receive this method call") .to_owned(); - let ext = ethereum_events::Vext { + let eth_evs = ethereum_events::Vext { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), validator_addr, }; - self.mode - .get_protocol_key() - .map(|signing_key| response::ExtendVote { - vote_extension: ext.sign(signing_key).try_to_vec().unwrap(), - }) - .unwrap_or_default() + let vset_upd = if self.storage.is_last_block_before_new_epoch() { + // TODO: get new validator set + todo!() + } else { + None + }; + + if let ShellMode::Validator { data, .. } = &self.mode { + let protocol_key = &data.keys.protocol_keypair; + + let vset_upd = + vset_upd.map(|ext: validator_set_update::Vext| { + // TODO: sign validator set update with secp key instead + ext.sign(protocol_key) + }); + + let eth_evs = eth_evs.sign(protocol_key); + + let vote_extension = VoteExtension { + ethereum_events: eth_evs, + validator_set_update: vset_upd, + } + .try_to_vec() + .unwrap(); + + response::ExtendVote { vote_extension } + } else { + Default::default() + } } /// This checks that the vote extension: From 51e1869f68bb0f98b82a93cb6d40b296c2571547 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 16:45:34 +0100 Subject: [PATCH 0357/1995] Create validator set update vote extension --- apps/src/lib/node/ledger/shell/queries.rs | 22 +++++++++++----- .../lib/node/ledger/shell/vote_extensions.rs | 26 ++++++++++++++++--- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9afd3c0832..4809711260 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -322,9 +322,16 @@ pub(crate) trait QueriesExt { epoch: Option, ) -> std::result::Result; - /// Verifies if we are at the last block before the - /// start of a new epoch. - fn is_last_block_before_new_epoch(&self) -> bool; + /// Determines if we are able to send a validator set update vote extension. + /// This is done by checking if we are at the first block of a new epoch, + /// or if we are at block height 1 of the first epoch. + /// + /// The genesis block will not have vote extensions, + /// therefore it is a special case, which we account for + /// by checking if the block height is 1. Otherwise, + /// validator set update vote extensions will always + /// be included at the first block of an epoch. + fn can_send_validator_set_update(&self) -> bool; } impl QueriesExt for Storage @@ -494,9 +501,10 @@ where }) } - fn is_last_block_before_new_epoch(&self) -> bool { - let current_height = self.last_height.0 + 1; - let new_epoch_height = self.next_epoch_min_start_height.0; - new_epoch_height.wrapping_sub(current_height) == 1 + fn can_send_validator_set_update(&self) -> bool { + // let current_height = self.last_height.0 + 1; + // let new_epoch_height = self.next_epoch_min_start_height.0; + // new_epoch_height.wrapping_sub(current_height) == 1 + todo!() } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 3b9dc89b80..f4077c712d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -7,6 +7,7 @@ pub mod ethereum_events; mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; + use namada::types::storage::Epoch; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, }; @@ -46,19 +47,36 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - let validator_addr = self + let addr = self .mode .get_validator_address() .expect("only validators should receive this method call") .to_owned(); + + let validator_addr = addr.clone(); let eth_evs = ethereum_events::Vext { block_height: self.storage.last_height + 1, ethereum_events: self.new_ethereum_events(), validator_addr, }; - let vset_upd = if self.storage.is_last_block_before_new_epoch() { - // TODO: get new validator set - todo!() + + let validator_addr = addr; + let vset_upd = if self.storage.can_send_validator_set_update() { + let (Epoch(current_epoch), _) = + self.storage.get_current_epoch(); + let next_epoch = Epoch(current_epoch + 1); + let _validator_set = + self.storage.get_active_validators(Some(next_epoch)); + + Some(validator_set_update::Vext { + validator_addr, + // TODO: we need a way to map ethereum addresses to + // namada validator addresses + voting_powers: todo!(), + // TODO: switch to storage epoch? or keep the pos epoch + // type? + epoch: next_epoch.into(), + }) } else { None }; From eb1f7d71034cd31d09f916115404171578d0977c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 9 Aug 2022 19:01:11 +0100 Subject: [PATCH 0358/1995] WIP: Implement can_send_validator_set_update --- apps/src/lib/node/ledger/shell/queries.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 4809711260..1f2f59cdc1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -501,10 +501,15 @@ where }) } + // TODO: we might need to add some param here to perform + // the same check when we receive a validator set update + // vote extension fn can_send_validator_set_update(&self) -> bool { - // let current_height = self.last_height.0 + 1; - // let new_epoch_height = self.next_epoch_min_start_height.0; - // new_epoch_height.wrapping_sub(current_height) == 1 - todo!() + self.last_height.0 == 0 || { + // let current_height = self.last_height.0 + 1; + // let new_epoch_height = self.next_epoch_min_start_height.0; + // new_epoch_height.wrapping_sub(current_height) == 1 + todo!() + } } } From e2c0154eba31d2974692245bbb63587046b92696 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 10:35:13 +0100 Subject: [PATCH 0359/1995] Fix up can_send_validator_set_update() We will be signing validator set update vote extensions at the last block before the end of an epoch. New validators will be responsible for deciding a protocol tx with the validator set update. --- apps/src/lib/node/ledger/shell/queries.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1f2f59cdc1..0757f86815 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -323,14 +323,8 @@ pub(crate) trait QueriesExt { ) -> std::result::Result; /// Determines if we are able to send a validator set update vote extension. - /// This is done by checking if we are at the first block of a new epoch, - /// or if we are at block height 1 of the first epoch. - /// - /// The genesis block will not have vote extensions, - /// therefore it is a special case, which we account for - /// by checking if the block height is 1. Otherwise, - /// validator set update vote extensions will always - /// be included at the first block of an epoch. + /// This is done by checking if we are at the last block of the current + /// epoch. fn can_send_validator_set_update(&self) -> bool; } @@ -501,15 +495,9 @@ where }) } - // TODO: we might need to add some param here to perform - // the same check when we receive a validator set update - // vote extension fn can_send_validator_set_update(&self) -> bool { - self.last_height.0 == 0 || { - // let current_height = self.last_height.0 + 1; - // let new_epoch_height = self.next_epoch_min_start_height.0; - // new_epoch_height.wrapping_sub(current_height) == 1 - todo!() - } + let current_height = self.last_height.0 + 1; + let new_epoch_height = self.next_epoch_min_start_height.0; + new_epoch_height.wrapping_sub(current_height) == 1 } } From 55f2490a70dd20c816d09b42b60814c3785aeb23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 10:53:44 +0100 Subject: [PATCH 0360/1995] Start work on validating valset update vote extensions --- .../lib/node/ledger/shell/vote_extensions.rs | 3 ++ .../vote_extensions/validator_set_update.rs | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index f4077c712d..6b34902047 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -3,6 +3,9 @@ #[cfg(not(feature = "ABCI"))] pub mod ethereum_events; +#[cfg(not(feature = "ABCI"))] +pub mod validator_set_update; + #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs new file mode 100644 index 0000000000..165ecff4f5 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -0,0 +1,50 @@ +//! Extend Tendermint votes with validator set updates, to be relayed to +//! Namada's Ethereum bridge smart contracts. + +use namada::ledger::pos::types::{Epoch, VotingPower}; +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::types::vote_extensions::validator_set_update; + +use super::*; +use crate::node::ledger::shell::Shell; + +impl Shell +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + /// Validates a validator set update vote extension issued for the new + /// epoch provided as an argument + /// + /// Checks that: + /// * The signing validator was active at the preceding epoch + /// * The validator correctly signed the extension + /// * The validator signed over the new epoch inside of the extension + /// * The voting powers are normalized to 2^32, and sorted in descending + /// order + #[inline] + #[allow(dead_code)] + pub fn validate_valset_upd_vext( + &self, + ext: validator_set_update::SignedVext, + new_epoch: Epoch, + ) -> bool { + self.validate_valset_upd_vext_and_get_it_back(ext, new_epoch) + .is_ok() + } + + /// This method behaves exactly like [`Self::validate_valset_upd_vext`], + /// with the added bonus of returning the vote extension back, if it + /// is valid. + #[allow(dead_code)] + pub fn validate_valset_upd_vext_and_get_it_back( + &self, + _ext: validator_set_update::SignedVext, + _new_epoch: Epoch, + ) -> std::result::Result< + (VotingPower, validator_set_update::SignedVext), + VoteExtensionError, + > { + todo!() + } +} From 765c69298d5391958efbac80fd0a27ddd46d2cf4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 11:01:22 +0100 Subject: [PATCH 0361/1995] Check valset update vext epoch --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 7 +++++-- .../ledger/shell/vote_extensions/ethereum_events.rs | 8 ++++---- .../shell/vote_extensions/validator_set_update.rs | 12 ++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 6b34902047..1d60cd9d74 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -24,8 +24,11 @@ mod extend_votes { pub enum VoteExtensionError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, - #[error("The vote extension has an unexpected block height.")] - UnexpectedBlockHeight, + #[error( + "The vote extension has an unexpected sequence number (e.g. block \ + height)." + )] + UnexpectedSequenceNumber, #[error( "The vote extension contains duplicate or non-sorted Ethereum \ events." diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 9015fd790b..6b4054ca25 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -49,10 +49,10 @@ where if ext.data.block_height != last_height { let ext_height = ext.data.block_height; tracing::error!( - "Vote extension issued for a block height {ext_height} \ - different from the expected height {last_height}" + "Ethereum events vote extension issued for a block height \ + {ext_height} different from the expected height {last_height}" ); - return Err(VoteExtensionError::UnexpectedBlockHeight); + return Err(VoteExtensionError::UnexpectedSequenceNumber); } if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); @@ -71,7 +71,7 @@ where if have_dupes_or_non_sorted { tracing::error!( %validator, - "Found duplicate or non-sorted Ethereum events in a vote extension from validator" + "Found duplicate or non-sorted Ethereum events in a vote extension from some validator" ); return Err(VoteExtensionError::HaveDupesOrNonSorted); } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 165ecff4f5..ef7ac17156 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -39,12 +39,20 @@ where #[allow(dead_code)] pub fn validate_valset_upd_vext_and_get_it_back( &self, - _ext: validator_set_update::SignedVext, - _new_epoch: Epoch, + ext: validator_set_update::SignedVext, + new_epoch: Epoch, ) -> std::result::Result< (VotingPower, validator_set_update::SignedVext), VoteExtensionError, > { + if ext.data.epoch != new_epoch { + let ext_epoch = ext.data.epoch; + tracing::error!( + "Validator set update vote extension issued for an epoch \ + {ext_epoch} different from the expected epoch {new_epoch}" + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } todo!() } } From 9264cfb9cbf6f5c5d79cca57e1e94b46a94aeff8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 11:13:15 +0100 Subject: [PATCH 0362/1995] Replace pos Epoch with storage Epoch type --- .../lib/node/ledger/shell/vote_extensions.rs | 4 +--- .../vote_extensions/validator_set_update.rs | 18 +++++++++++++++++- .../vote_extensions/validator_set_update.rs | 3 ++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 1d60cd9d74..240955eaf6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -79,9 +79,7 @@ mod extend_votes { // TODO: we need a way to map ethereum addresses to // namada validator addresses voting_powers: todo!(), - // TODO: switch to storage epoch? or keep the pos epoch - // type? - epoch: next_epoch.into(), + epoch: next_epoch, }) } else { None diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index ef7ac17156..daf7c32def 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -1,11 +1,13 @@ //! Extend Tendermint votes with validator set updates, to be relayed to //! Namada's Ethereum bridge smart contracts. -use namada::ledger::pos::types::{Epoch, VotingPower}; +use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::types::storage::Epoch; use namada::types::vote_extensions::validator_set_update; use super::*; +use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::Shell; impl Shell @@ -53,6 +55,20 @@ where ); return Err(VoteExtensionError::UnexpectedSequenceNumber); } + // get the public key associated with this validator + let validator = &ext.data.validator_addr; + let prev_epoch = todo!(); + let (voting_power, pk) = self + .storage + .get_validator_from_address(validator, prev_epoch) + .map_err(|err| { + tracing::error!( + ?err, + %validator, + "Could not get public key from Storage for validator" + ); + VoteExtensionError::PubKeyNotInStorage + })?; todo!() } } diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index f5197b32a9..f994b3bc44 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -10,11 +10,12 @@ use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; -use crate::ledger::pos::types::{Epoch, VotingPower}; +use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, KeccakHash}; use crate::types::key::common::{self, Signature}; +use crate::types::storage::Epoch; // the namespace strings plugged into validator set hashes const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; From 58b345e15bc09bac9f5faefdd06a22878b3e0340 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 11:19:24 +0100 Subject: [PATCH 0363/1995] Misc changes --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 9 ++++----- .../ledger/shell/vote_extensions/validator_set_update.rs | 4 +++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 240955eaf6..37173147c3 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -88,11 +88,10 @@ mod extend_votes { if let ShellMode::Validator { data, .. } = &self.mode { let protocol_key = &data.keys.protocol_keypair; - let vset_upd = - vset_upd.map(|ext: validator_set_update::Vext| { - // TODO: sign validator set update with secp key instead - ext.sign(protocol_key) - }); + let vset_upd = vset_upd.map(|ext| { + // TODO: sign validator set update with secp key instead + ext.sign(protocol_key) + }); let eth_evs = eth_evs.sign(protocol_key); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index daf7c32def..72a157e440 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -22,6 +22,8 @@ where /// * The signing validator was active at the preceding epoch /// * The validator correctly signed the extension /// * The validator signed over the new epoch inside of the extension + /// * The voting powers in the vote extension correspond to the voting + /// powers of the validators of the new epoch /// * The voting powers are normalized to 2^32, and sorted in descending /// order #[inline] @@ -57,7 +59,7 @@ where } // get the public key associated with this validator let validator = &ext.data.validator_addr; - let prev_epoch = todo!(); + let prev_epoch = Some(Epoch(new_epoch.0 - 1)); let (voting_power, pk) = self .storage .get_validator_from_address(validator, prev_epoch) From a69f602106d3084b1ba08186225480325eb6fb41 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 11:23:33 +0100 Subject: [PATCH 0364/1995] Improve log messages of vote extension validation --- .../lib/node/ledger/shell/vote_extensions/ethereum_events.rs | 4 ++-- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 6b4054ca25..ee762a197b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -84,7 +84,7 @@ where tracing::error!( ?err, %validator, - "Could not get public key from Storage for validator" + "Could not get public key from Storage for some validator, while validating Ethereum events vote extension" ); VoteExtensionError::PubKeyNotInStorage })?; @@ -94,7 +94,7 @@ where tracing::error!( ?err, %validator, - "Failed to verify the signature of a vote extension issued by validator" + "Failed to verify the signature of an Ethereum events vote extension issued by some validator" ); VoteExtensionError::VerifySigFailed }) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 72a157e440..b406356131 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -67,7 +67,7 @@ where tracing::error!( ?err, %validator, - "Could not get public key from Storage for validator" + "Could not get public key from Storage for some validator, while validating validator set update vote extension" ); VoteExtensionError::PubKeyNotInStorage })?; From f3f845bcf0a0ea71ffcce8389d83be05d65be1b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 11:34:03 +0100 Subject: [PATCH 0365/1995] Verify signature of validator set update vote extension --- .../vote_extensions/validator_set_update.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index b406356131..1adee3470a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -41,6 +41,12 @@ where /// with the added bonus of returning the vote extension back, if it /// is valid. #[allow(dead_code)] + // TODO: + // - verify if the voting powers in the vote extension are the same + // as the ones in storage. we can't do this yet, because we need to map + // ethereum addresses to namada validator addresses + // + // - verify signatures with a secp key, instead of an ed25519 key pub fn validate_valset_upd_vext_and_get_it_back( &self, ext: validator_set_update::SignedVext, @@ -71,6 +77,16 @@ where ); VoteExtensionError::PubKeyNotInStorage })?; - todo!() + // verify the signature of the vote extension + ext.verify(&pk) + .map_err(|err| { + tracing::error!( + ?err, + %validator, + "Failed to verify the signature of a validator set update vote extension issued by some validator" + ); + VoteExtensionError::VerifySigFailed + }) + .map(|_| (voting_power, ext)) } } From 8a52ab4e14678dd0fd166b08b81f9a716213ec14 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 12:38:53 +0100 Subject: [PATCH 0366/1995] Clean up inclusion of validator set update in vote extension --- .../lib/node/ledger/shell/vote_extensions.rs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 37173147c3..79b3b862c7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -67,23 +67,22 @@ mod extend_votes { }; let validator_addr = addr; - let vset_upd = if self.storage.can_send_validator_set_update() { - let (Epoch(current_epoch), _) = - self.storage.get_current_epoch(); - let next_epoch = Epoch(current_epoch + 1); - let _validator_set = - self.storage.get_active_validators(Some(next_epoch)); - - Some(validator_set_update::Vext { - validator_addr, - // TODO: we need a way to map ethereum addresses to - // namada validator addresses - voting_powers: todo!(), - epoch: next_epoch, - }) - } else { - None - }; + let vset_upd = + self.storage.can_send_validator_set_update().then(|| { + let (Epoch(current_epoch), _) = + self.storage.get_current_epoch(); + let next_epoch = Epoch(current_epoch + 1); + let _validator_set = + self.storage.get_active_validators(Some(next_epoch)); + + validator_set_update::Vext { + validator_addr, + // TODO: we need a way to map ethereum addresses to + // namada validator addresses + voting_powers: todo!(), + epoch: next_epoch, + } + }); if let ShellMode::Validator { data, .. } = &self.mode { let protocol_key = &data.keys.protocol_keypair; From b8573d8ae95e19eedb500aed8b44d5fa42e452be Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 12:40:36 +0100 Subject: [PATCH 0367/1995] Remove todo!() to get rid of compiler warnings --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 79b3b862c7..b850edc1e5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -79,7 +79,7 @@ mod extend_votes { validator_addr, // TODO: we need a way to map ethereum addresses to // namada validator addresses - voting_powers: todo!(), + voting_powers: std::collections::HashMap::new(), epoch: next_epoch, } }); From 6a8e5e4c8e48cf468d8045543286571587461a82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 13:14:24 +0100 Subject: [PATCH 0368/1995] Implement verify_vote_extension() --- .../lib/node/ledger/shell/vote_extensions.rs | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b850edc1e5..e3fe8ef82f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,7 +9,6 @@ pub mod validator_set_update; #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; - use namada::proto::Signed; use namada::types::storage::Epoch; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, @@ -117,39 +116,64 @@ mod extend_votes { &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { - // TODO: this should deserialize to - // `namada::types::vote_extensions::VoteExtension`, - // which contains an optional validator set update and - // a set of ethereum events seen at the previous block height - if let Ok(signed) = Signed::::try_from_slice( - &req.vote_extension[..], - ) { - response::VerifyVoteExtension { - status: if self.validate_eth_events_vext( - signed, - self.storage.last_height + 1, - ) { - VerifyStatus::Accept.into() - } else { + let ext = + match VoteExtension::try_from_slice(&req.vote_extension[..]) { + Ok(ext) => ext, + Err(err) => { tracing::warn!( + ?err, ?req.validator_address, ?req.hash, req.height, - "received vote extension that didn't validate" + "Received undeserializable vote extension" ); - VerifyStatus::Reject.into() - }, - } - } else { - tracing::warn!( - ?req.validator_address, - ?req.hash, - req.height, - "received undeserializable vote extension" - ); - response::VerifyVoteExtension { - status: VerifyStatus::Reject.into(), - } + return response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + }; + } + }; + let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, self.storage.last_height + 1) + .then(|| true) + .unwrap_or_else(|| { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "Received Ethereum events vote extension that didn't validate" + ); + false + }); + let validated_valset_upd = self.storage.can_send_validator_set_update().then(|| { + ext.validator_set_update + .map(|ext| { + let next_epoch = { + let (Epoch(current_epoch), _) = + self.storage.get_current_epoch(); + Epoch(current_epoch + 1) + }; + self.validate_valset_upd_vext(ext, next_epoch) + }) + .unwrap_or_else(|| { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "Received validator set update vote extension that didn't validate" + ); + false + }) + }).unwrap_or({ + // NOTE: if we're not supposed to send a validator set update + // vote extension at a particular block height, we will + // just return true as the validation result + true + }); + response::VerifyVoteExtension { + status: if validated_eth_events && validated_valset_upd { + VerifyStatus::Accept.into() + } else { + VerifyStatus::Reject.into() + }, } } } From 751d1134f30063158a81164f826f645ccaa1d1bf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 13:17:39 +0100 Subject: [PATCH 0369/1995] Small fix --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index e3fe8ef82f..4e199805f1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -145,13 +145,14 @@ mod extend_votes { }); let validated_valset_upd = self.storage.can_send_validator_set_update().then(|| { ext.validator_set_update - .map(|ext| { + .and_then(|ext| { let next_epoch = { let (Epoch(current_epoch), _) = self.storage.get_current_epoch(); Epoch(current_epoch + 1) }; self.validate_valset_upd_vext(ext, next_epoch) + .then(|| true) }) .unwrap_or_else(|| { tracing::warn!( From 4eb3732f58c4bdb51768b64cef03c23ffc17ba71 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 10 Aug 2022 16:58:37 +0200 Subject: [PATCH 0370/1995] [feat]: Events voted on by validators are not voted on again --- .../lib/node/ledger/shell/finalize_block.rs | 78 ++++++++++++++++++- apps/src/lib/node/ledger/shell/mod.rs | 12 +++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 05491c4fab..269c88b064 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -326,7 +326,12 @@ where continue; } TxType::Protocol(protocol_tx) => match protocol_tx.tx { - ProtocolTxType::EthereumEvents(_) => { + ProtocolTxType::EthereumEvents(ref digest) => { + for event in + digest.events.iter().map(|signed| &signed.event) + { + self.mode.deque_eth_event(event); + } Event::new_tx_event(&tx_type, height.0) } _ => { @@ -512,8 +517,12 @@ where #[cfg(test)] mod test_finalize_block { use namada::types::address::xan; + use namada::types::ethereum_events::EthAddress; use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee}; + use namada::types::vote_extensions::ethereum_events::{ + MultiSignedEthEvent, Vext, VextDigest, + }; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -985,4 +994,71 @@ mod test_finalize_block { assert_eq!(counter, 2); } } + + /// Test that once a validator's vote for an Ethereum event lands + /// on-chain, it dequeues from the list of events to vote on. + #[test] + fn test_eth_events_dequeued() { + let (mut shell, _, oracle) = setup(); + let protocol_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + + // ---- the ledger receives a new Ethereum event + let event = EthereumEvent::NewContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + }; + oracle.send(event.clone()).expect("Test failed"); + let [queued_event]: [EthereumEvent; 1] = + shell.new_ethereum_events().try_into().expect("Test failed"); + assert_eq!(queued_event, event); + + // ---- The protocol tx that includes this event on-chain + let signature = Vext { + block_height: shell.storage.last_height, + ethereum_events: vec![event.clone()], + validator_addr: address.clone(), + } + .sign(&protocol_key) + .sig; + let signed = MultiSignedEthEvent { + event, + signers: HashSet::from([address.clone()]), + }; + + let digest = VextDigest { + signatures: vec![(address, signature)].into_iter().collect(), + events: vec![signed], + }; + let processed_tx = ProcessedTx { + tx: ProtocolTxType::EthereumEvents(digest) + .sign(&protocol_key) + .to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + }; + + // ---- This protocol tx is accepted + let [result]: [Event; 1] = shell + .finalize_block(FinalizeBlock { + txs: vec![processed_tx], + ..Default::default() + }) + .expect("Test failed") + .try_into() + .expect("Test failed"); + assert_eq!(result.event_type.to_string(), String::from("applied")); + let code = result.attributes.get("code").expect("Test failed").as_str(); + assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); + + // --- The event is removed from the queue + assert!(shell.new_ethereum_events().is_empty()); + } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2e9bfaa677..8f9d3e7b56 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -243,6 +243,18 @@ impl ShellMode { } } + /// Remove an Ethereum event from the internal queue + pub fn deque_eth_event(&mut self, event: &EthereumEvent) { + if let ShellMode::Validator { + ethereum_recv: EthereumReceiver { ref mut queue, .. }, + .. + } = self + { + queue.remove(event); + } + } + + /// Get the protocol keypair for this validator pub fn get_protocol_key(&self) -> Option<&common::SecretKey> { match &self { ShellMode::Validator { From 951bacbe1667b60c903ce33db57c30a3cc7cbf9f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 10 Aug 2022 21:27:58 +0100 Subject: [PATCH 0371/1995] Add a TODO --- apps/src/lib/node/ledger/shell/queries.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0757f86815..79646d7ccd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -495,6 +495,12 @@ where }) } + // TODO: + // - accept last_height param + // - get epoch duration from storage + // - use modulo arithmetic (???? maybe) to calc offset of block within the + // epoch + // - we must be at the last block of the epoch fn can_send_validator_set_update(&self) -> bool { let current_height = self.last_height.0 + 1; let new_epoch_height = self.next_epoch_min_start_height.0; From 36d728c7d2f2808fbf2546c83d3f005b7369c7b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 09:15:22 +0100 Subject: [PATCH 0372/1995] Add get_epoch_from_height() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++------ apps/src/lib/node/ledger/shell/process_proposal.rs | 7 +++---- apps/src/lib/node/ledger/shell/queries.rs | 7 +++++++ .../node/ledger/shell/vote_extensions/ethereum_events.rs | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4ff0d334d6..19fb6f7ce4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -177,9 +177,7 @@ mod prepare_block { ) -> Option { let events_epoch = self .storage - .block - .pred_epochs - .get_epoch(self.storage.last_height) + .get_epoch_from_height(self.storage.last_height) .expect( "The epoch of the last block height should always be known", ); @@ -628,9 +626,7 @@ mod prepare_block { // to move to a new epoch let events_epoch = shell .storage - .block - .pred_epochs - .get_epoch(FIRST_HEIGHT) + .get_epoch_from_height(FIRST_HEIGHT) .expect("Test failed"); let validator_set = { let params = shell.storage.read_pos_params(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9f6af6fd88..25dcb129df 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -150,10 +150,9 @@ where let mut voting_power = FractionalVotingPower::default(); let total_power = { - let epoch = - self.storage.block.pred_epochs.get_epoch( - BlockHeight(self.storage.last_height.0), - ); + let epoch = self.storage.get_epoch_from_height( + BlockHeight(self.storage.last_height.0), + ); u64::from(self.storage.get_total_voting_power(epoch)) }; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 79646d7ccd..65d711d5fe 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -326,6 +326,9 @@ pub(crate) trait QueriesExt { /// This is done by checking if we are at the last block of the current /// epoch. fn can_send_validator_set_update(&self) -> bool; + + /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. + fn get_epoch_from_height(&self, height: BlockHeight) -> Option; } impl QueriesExt for Storage @@ -506,4 +509,8 @@ where let new_epoch_height = self.next_epoch_min_start_height.0; new_epoch_height.wrapping_sub(current_height) == 1 } + + fn get_epoch_from_height(&self, height: BlockHeight) -> Option { + self.block.pred_epochs.get_epoch(height) + } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index ee762a197b..4c56968eb5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -76,7 +76,7 @@ where return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator - let epoch = self.storage.block.pred_epochs.get_epoch(last_height); + let epoch = self.storage.get_epoch_from_height(last_height); let (voting_power, pk) = self .storage .get_validator_from_address(validator, epoch) From 68db920ad650ca0f5a14c9439ab39bc65344ba0d Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 11 Aug 2022 10:40:16 +0200 Subject: [PATCH 0373/1995] [fix]: Used more fully qualified syntax for vext types in unit tests --- apps/src/lib/node/ledger/shell/finalize_block.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 269c88b064..c0055d7ab7 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -521,7 +521,7 @@ mod test_finalize_block { use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee}; use namada::types::vote_extensions::ethereum_events::{ - MultiSignedEthEvent, Vext, VextDigest, + self, MultiSignedEthEvent, }; use super::*; @@ -1019,7 +1019,7 @@ mod test_finalize_block { assert_eq!(queued_event, event); // ---- The protocol tx that includes this event on-chain - let signature = Vext { + let signature = ethereum_events::Vext { block_height: shell.storage.last_height, ethereum_events: vec![event.clone()], validator_addr: address.clone(), @@ -1031,7 +1031,7 @@ mod test_finalize_block { signers: HashSet::from([address.clone()]), }; - let digest = VextDigest { + let digest = ethereum_events::VextDigest { signatures: vec![(address, signature)].into_iter().collect(), events: vec![signed], }; From c3586dd9109a4ebf45cf0a7e1624e283bea37610 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 09:43:24 +0100 Subject: [PATCH 0374/1995] Refactor get next epoch --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 4e199805f1..228d34dd86 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,7 +9,6 @@ pub mod validator_set_update; #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; - use namada::types::storage::Epoch; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, }; @@ -68,9 +67,7 @@ mod extend_votes { let validator_addr = addr; let vset_upd = self.storage.can_send_validator_set_update().then(|| { - let (Epoch(current_epoch), _) = - self.storage.get_current_epoch(); - let next_epoch = Epoch(current_epoch + 1); + let next_epoch = self.storage.get_current_epoch().0.next(); let _validator_set = self.storage.get_active_validators(Some(next_epoch)); @@ -146,12 +143,7 @@ mod extend_votes { let validated_valset_upd = self.storage.can_send_validator_set_update().then(|| { ext.validator_set_update .and_then(|ext| { - let next_epoch = { - let (Epoch(current_epoch), _) = - self.storage.get_current_epoch(); - Epoch(current_epoch + 1) - }; - self.validate_valset_upd_vext(ext, next_epoch) + self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) .then(|| true) }) .unwrap_or_else(|| { From a33ea81bb2655489b0b2501921c8ccef4bc6948b Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 11 Aug 2022 10:52:49 +0200 Subject: [PATCH 0375/1995] [feat]: updated specs to not include the ethereum sentinel --- .../specs/src/interoperability/ethereum-bridge.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index cfc5ce6841..4b7827f4c5 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -87,18 +87,13 @@ keys involved are: `\$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant `EthereumEvent`. -Changes to this `/eth_msgs` storage subspace are only ever made by internal -transactions crafted and applied by all nodes based on the aggregate of vote +Changes to this `/eth_msgs` storage subspace are only ever made by +validators as part of the ledger code based on the aggregate of vote extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen in block `n+1` in a deterministic manner based on the vote extensions of the -Tendermint round for block `n`. - -The `/eth_msgs` storage subspace does not belong to any account and cannot be -modified by transactions submitted from outside of the ledger via Tendermint. -The storage will be guarded by a special validity predicate - `EthSentinel` - -that is part of the verifier set by default for every transaction, but will be -removed by the ledger code for the specific permitted transactions that are -allowed to update `/eth_msgs`. +Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong +the the `EthBridge` validity predicate. It should disallow any changes to +this storage from transactions. ### Including events into storage From 1ec6620d161a967b2f87cc95c32c17ff19ec953c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 09:55:11 +0100 Subject: [PATCH 0376/1995] Sign protocol txs in PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 19fb6f7ce4..c04b363ce6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -8,6 +8,7 @@ mod prepare_block { use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; + use namada::types::vote_extensions::VoteExtensionDigest; use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -79,13 +80,13 @@ mod prepare_block { .get_protocol_key() .expect("Validators should always have a protocol key"); - let vote_extension_digest = + let ethereum_events = local_last_commit.and_then(|local_last_commit| { let votes = local_last_commit.votes; self.compress_ethereum_events(votes) }); - let vote_extension_digest = - match (vote_extension_digest, self.storage.last_height) { + let ethereum_events = + match (ethereum_events, self.storage.last_height) { // handle genesis block (None, BlockHeight(0)) => return vec![], (Some(_), BlockHeight(0)) => { @@ -110,14 +111,12 @@ mod prepare_block { ), }; - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(protocol_key) - .to_bytes(); - let tx_record = record::add(tx); - - // TODO: include here a validator set update tx, - // if we are at the end of an epoch - vec![tx_record] + iter_protocol_txs(VoteExtensionDigest { + ethereum_events, + validator_set_update: None, + }) + .map(|tx| record::add(tx.sign(protocol_key).to_bytes())) + .collect() } /// Builds a batch of mempool transactions @@ -248,6 +247,21 @@ mod prepare_block { } } + /// Yields an iterator over the [`ProtocolTxType`] transactions + /// in a [`VoteExtensionDigest`]. + fn iter_protocol_txs( + digest: VoteExtensionDigest, + ) -> impl Iterator { + [ + Some(ProtocolTxType::EthereumEvents(digest.ethereum_events)), + digest + .validator_set_update + .map(ProtocolTxType::ValidatorSetUpdate), + ] + .into_iter() + .flat_map(|tx| tx) + } + /// Functions for creating the appropriate TxRecord given the /// numeric code pub(super) mod record { From 1948a03525d920df810a9dade0fcc8978dbe2557 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 10:46:32 +0100 Subject: [PATCH 0377/1995] Split vote extensions + fix unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 108 +++++++++++------- .../lib/node/ledger/shell/process_proposal.rs | 2 +- .../shell/vote_extensions/ethereum_events.rs | 48 ++++---- 3 files changed, 91 insertions(+), 67 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c04b363ce6..488ef17493 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,11 +4,14 @@ mod prepare_block { use std::collections::{BTreeMap, HashMap, HashSet}; + use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; - use namada::types::vote_extensions::VoteExtensionDigest; + use namada::types::vote_extensions::{ + validator_set_update, VoteExtensionDigest, + }; use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -75,16 +78,21 @@ mod prepare_block { &mut self, local_last_commit: Option, ) -> Vec { - let protocol_key = self - .mode - .get_protocol_key() - .expect("Validators should always have a protocol key"); + // genesis block should not contain vote extensions + if self.storage.last_height == BlockHeight(0) { + return vec![]; + } - let ethereum_events = - local_last_commit.and_then(|local_last_commit| { - let votes = local_last_commit.votes; - self.compress_ethereum_events(votes) - }); + let (eth_events, _valset_upds) = split_vote_extensions( + local_last_commit + .expect( + "Block heights >0 should always contain vote \ + extensions", + ) + .votes, + ); + + let ethereum_events = self.compress_ethereum_events(eth_events); let ethereum_events = match (ethereum_events, self.storage.last_height) { // handle genesis block @@ -111,6 +119,11 @@ mod prepare_block { ), }; + let protocol_key = self + .mode + .get_protocol_key() + .expect("Validators should always have a protocol key"); + iter_protocol_txs(VoteExtensionDigest { ethereum_events, validator_set_update: None, @@ -172,7 +185,7 @@ mod prepare_block { // ethereum events and validator set update vote extensions fn compress_ethereum_events( &self, - vote_extensions: Vec, + vote_extensions: Vec>, ) -> Option { let events_epoch = self .storage @@ -189,11 +202,8 @@ mod prepare_block { ); let mut voting_power = FractionalVotingPower::default(); - let deserialized = deserialize_vote_extensions(vote_extensions) - .map(|vext| vext.ethereum_events); - for (validator_voting_power, vote_extension) in - self.filter_invalid_vote_extensions(deserialized) + self.filter_invalid_eth_events_vexts(vote_extensions) { let validator_addr = vote_extension.data.validator_addr; @@ -262,6 +272,28 @@ mod prepare_block { .flat_map(|tx| tx) } + /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering + /// out invalid data, and splits these into [`ethereum_events::Vext`] + /// and [`validator_set_update::Vext`] instances. + fn split_vote_extensions( + vote_extensions: Vec, + ) -> ( + Vec>, + Vec, + ) { + let mut eth_evs = vec![]; + let mut valset_upds = vec![]; + + for ext in deserialize_vote_extensions(vote_extensions) { + if let Some(validator_set_update) = ext.validator_set_update { + valset_upds.push(validator_set_update); + } + eth_evs.push(ext.ethereum_events); + } + + (eth_evs, valset_upds) + } + /// Functions for creating the appropriate TxRecord given the /// numeric code pub(super) mod record { @@ -312,7 +344,7 @@ mod prepare_block { use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; - use namada::types::vote_extensions::ethereum_events; + use namada::types::vote_extensions::{ethereum_events, VoteExtension}; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, @@ -346,29 +378,13 @@ mod prepare_block { ); } - /// Serialize a [`Signed`] to an - /// [`ExtendedVoteInfo`] - fn vote_extension_serialize( - vext: Signed, - ) -> ExtendedVoteInfo { - ExtendedVoteInfo { - vote_extension: vext.try_to_vec().unwrap(), - ..Default::default() - } - } - /// Check if we are filtering out an invalid vote extension `vext` fn check_eth_events_filtering( shell: &mut TestShell, vext: Signed, ) { - let votes = - deserialize_vote_extensions(vec![vote_extension_serialize( - vext, - )]) - .map(|vext| vext.ethereum_events); let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); + shell.filter_invalid_eth_events_vexts(vec![vext]).collect(); assert_eq!(filtered_votes, vec![]); } @@ -496,11 +512,8 @@ mod prepare_block { ext }; - let maybe_digest = { - let votes = - vec![vote_extension_serialize(signed_vote_extension)]; - shell.compress_ethereum_events(votes) - }; + let maybe_digest = + shell.compress_ethereum_events(vec![signed_vote_extension]); // we should be filtering out the vote extension with // duped ethereum events; therefore, no valid vote @@ -564,7 +577,7 @@ mod prepare_block { nonce: 1u64.into(), transfers: vec![], }; - let signed_vote_extension = { + let ethereum_events = { let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, @@ -574,8 +587,12 @@ mod prepare_block { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; + let vote_extension = VoteExtension { + ethereum_events, + validator_set_update: None, + }; let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension.try_to_vec().unwrap(), + vote_extension: vote_extension.try_to_vec().unwrap(), ..Default::default() }; @@ -613,7 +630,7 @@ mod prepare_block { let digest = manually_assemble_digest( &protocol_key, - signed_vote_extension, + vote_extension.ethereum_events, LAST_HEIGHT, ); @@ -677,7 +694,7 @@ mod prepare_block { nonce: 1u64.into(), transfers: vec![], }; - let signed_vote_extension = { + let ethereum_events = { let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, @@ -688,7 +705,12 @@ mod prepare_block { ext }; let vote = ExtendedVoteInfo { - vote_extension: signed_vote_extension.try_to_vec().unwrap(), + vote_extension: VoteExtension { + ethereum_events, + validator_set_update: None, + } + .try_to_vec() + .unwrap(), ..Default::default() }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 25dcb129df..cb68dd200d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -146,7 +146,7 @@ where let extensions = digest.decompress(self.storage.last_height); let valid_extensions = - self.validate_vote_extension_list(extensions); + self.validate_eth_events_vext_list(extensions); let mut voting_power = FractionalVotingPower::default(); let total_power = { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 4c56968eb5..182e1c6449 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -116,14 +116,12 @@ where } } - /// Takes an iterator over vote extension instances, + /// Takes an iterator over Ethereum events vote extension instances, /// and returns another iterator. The latter yields - /// valid vote extensions, or the reason why these + /// valid Ethereum events vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. - // TODO: the `vote_extensions` iterator should be over `VoteExtension` - // instances, I guess? to be determined in the next PR #[inline] - pub fn validate_vote_extension_list( + pub fn validate_eth_events_vext_list( &self, vote_extensions: impl IntoIterator> + 'static, @@ -141,18 +139,16 @@ where }) } - /// Takes a list of signed vote extensions, + /// Takes a list of signed Ethereum events vote extensions, /// and filters out invalid instances. - // TODO: the `vote_extensions` iterator should be over `VoteExtension` - // instances, I guess? to be determined in the next PR #[inline] - pub fn filter_invalid_vote_extensions( + pub fn filter_invalid_eth_events_vexts( &self, vote_extensions: impl IntoIterator> + 'static, ) -> impl Iterator)> + '_ { - self.validate_vote_extension_list(vote_extensions) + self.validate_eth_events_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) } } @@ -164,13 +160,12 @@ mod test_vote_extensions { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::proto::Signed; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; use namada::types::key::*; use namada::types::storage::{BlockHeight, Epoch}; - use namada::types::vote_extensions::ethereum_events; + use namada::types::vote_extensions::{ethereum_events, VoteExtension}; use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; @@ -251,12 +246,13 @@ mod test_vote_extensions { oracle.send(event_1.clone()).expect("Test failed"); oracle.send(event_2.clone()).expect("Test failed"); let vote_extension = - as BorshDeserialize>::try_from_slice( + ::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], ) .expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = vote_extension + .ethereum_events .data .ethereum_events .clone() @@ -289,7 +285,7 @@ mod test_vote_extensions { .get_validator_address() .expect("Test failed") .clone(); - let vote_ext = ethereum_events::Vext { + let ethereum_events = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -301,9 +297,7 @@ mod test_vote_extensions { block_height: shell.storage.last_height + 1, validator_addr: address.clone(), } - .sign(&signing_key) - .try_to_vec() - .expect("Test failed"); + .sign(&signing_key); let req = request::VerifyVoteExtension { hash: vec![], validator_address: address @@ -312,7 +306,12 @@ mod test_vote_extensions { .as_bytes() .to_vec(), height: 0, - vote_extension: vote_ext, + vote_extension: VoteExtension { + ethereum_events, + validator_set_update: None, + } + .try_to_vec() + .expect("Test failed"), }; assert_eq!( shell.verify_vote_extension(req).status, @@ -395,7 +394,7 @@ mod test_vote_extensions { fn reject_incorrect_block_number() { let (shell, _, _) = setup(); let address = shell.mode.get_validator_address().unwrap().clone(); - let vote_ext = ethereum_events::Vext { + let ethereum_events = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), transfers: vec![TransferToEthereum { @@ -407,15 +406,18 @@ mod test_vote_extensions { block_height: shell.storage.last_height, validator_addr: address.clone(), } - .sign(shell.mode.get_protocol_key().expect("Test failed")) - .try_to_vec() - .expect("Test failed"); + .sign(shell.mode.get_protocol_key().expect("Test failed")); let req = request::VerifyVoteExtension { hash: vec![], validator_address: address.try_to_vec().expect("Test failed"), height: 0, - vote_extension: vote_ext, + vote_extension: VoteExtension { + ethereum_events, + validator_set_update: None, + } + .try_to_vec() + .expect("Test failed"), }; assert_eq!( shell.verify_vote_extension(req).status, From 95f07d60cc89f0db32a1f46b5918e5a7d844cc9d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 11:00:28 +0100 Subject: [PATCH 0378/1995] Split panic messages into two --- .../lib/node/ledger/shell/prepare_proposal.rs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 488ef17493..a8af042bab 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -86,8 +86,14 @@ mod prepare_block { let (eth_events, _valset_upds) = split_vote_extensions( local_last_commit .expect( - "Block heights >0 should always contain vote \ - extensions", + "Honest Namada validators will always sign \ + ethereum_events::Vext instances, even if no Ethereum \ + events were observed at a given block height. In \ + fact, a quorum of signed empty ethereum_events::Vext \ + instances commits the fact no events were observed \ + by a majority of validators. Therefore, for block \ + heights greater than zero, we should always have \ + vote extensions.", ) .votes, ); @@ -106,16 +112,9 @@ mod prepare_block { // handle block heights > 0 (Some(digest), _) => digest, _ => unreachable!( - "Honest Namada validators will always sign \ - ethereum_events::Vext instances, even if no Ethereum \ - events were observed at a given block height. In \ - fact, a quorum of signed empty ethereum_events::Vext \ - instances commits the fact no events were observed \ - by a majority of validators. Likewise, a Tendermint \ - quorum should never decide on a block including vote \ - extensions reflecting less than or equal to 2/3 of \ - the total stake. These scenarios are virtually \ - impossible, so we will panic here." + "A Tendermint quorum should never decide on a block \ + including vote extensions reflecting less than or \ + equal to 2/3 of the total stake." ), }; @@ -643,7 +642,7 @@ mod prepare_block { /// Test if Ethereum events validation and inclusion in a block /// behaves as expected, considering <= 2/3 voting power. #[test] - #[should_panic(expected = "Honest Namada validators")] + #[should_panic(expected = "A Tendermint quorum should never")] fn test_prepare_proposal_vext_insufficient_voting_power() { const FIRST_HEIGHT: BlockHeight = BlockHeight(0); const LAST_HEIGHT: BlockHeight = BlockHeight(FIRST_HEIGHT.0 + 11); From 9a1282ba45b2918575cca185c99fba62012db2d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 11:19:06 +0100 Subject: [PATCH 0379/1995] Remove redundant logic --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a8af042bab..a6613ede5c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -102,13 +102,9 @@ mod prepare_block { let ethereum_events = match (ethereum_events, self.storage.last_height) { // handle genesis block - (None, BlockHeight(0)) => return vec![], - (Some(_), BlockHeight(0)) => { - unreachable!( - "We already handle this scenario in \ - validate_eth_events_vext." - ) - } + (_, BlockHeight(0)) => unreachable!( + "We guard the genesis block at the top of this method." + ), // handle block heights > 0 (Some(digest), _) => digest, _ => unreachable!( From 0b88755fd5910decd0c3e89101df49f2197f6ad5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 11:25:03 +0100 Subject: [PATCH 0380/1995] Remove match block altogether --- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a6613ede5c..d9a2875353 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -98,21 +98,14 @@ mod prepare_block { .votes, ); - let ethereum_events = self.compress_ethereum_events(eth_events); - let ethereum_events = - match (ethereum_events, self.storage.last_height) { - // handle genesis block - (_, BlockHeight(0)) => unreachable!( - "We guard the genesis block at the top of this method." - ), - // handle block heights > 0 - (Some(digest), _) => digest, - _ => unreachable!( - "A Tendermint quorum should never decide on a block \ - including vote extensions reflecting less than or \ - equal to 2/3 of the total stake." - ), - }; + const NOT_ENOUGH_VOTING_POWER_MSG: &str = + "A Tendermint quorum should never decide on a block including \ + vote extensions reflecting less than or equal to 2/3 of the \ + total stake."; + + let ethereum_events = self + .compress_ethereum_events(eth_events) + .expect(NOT_ENOUGH_VOTING_POWER_MSG); let protocol_key = self .mode From 8f753f269df15c2954d64f82ec0954d6ff2ece5b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 11:34:41 +0100 Subject: [PATCH 0381/1995] Move compress_ethereum_events --- .../lib/node/ledger/shell/prepare_proposal.rs | 96 ++----------------- .../shell/vote_extensions/ethereum_events.rs | 82 +++++++++++++++- 2 files changed, 88 insertions(+), 90 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d9a2875353..0afec9aa8b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,22 +2,15 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { - use std::collections::{BTreeMap, HashMap, HashSet}; - use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::vote_extensions::ethereum_events::{ - self, MultiSignedEthEvent, - }; use namada::types::vote_extensions::{ - validator_set_update, VoteExtensionDigest, + ethereum_events, validator_set_update, VoteExtensionDigest, }; - use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use super::super::queries::QueriesExt; use super::super::vote_extensions::deserialize_vote_extensions; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -164,85 +157,6 @@ mod prepare_block { .map(record::add) .collect() } - - /// Compresses a set of signed Ethereum events into a single - /// [`ethereum_events::VextDigest`], whilst filtering invalid - /// [`Signed`] instances in the process - // TODO: rename this as `compress_vote_extensions`, and return - // a `VoteExtensionDigest`, which will contain both digests of - // ethereum events and validator set update vote extensions - fn compress_ethereum_events( - &self, - vote_extensions: Vec>, - ) -> Option { - let events_epoch = self - .storage - .get_epoch_from_height(self.storage.last_height) - .expect( - "The epoch of the last block height should always be known", - ); - - let mut event_observers = BTreeMap::new(); - let mut signatures = HashMap::new(); - - let total_voting_power = u64::from( - self.storage.get_total_voting_power(Some(events_epoch)), - ); - let mut voting_power = FractionalVotingPower::default(); - - for (validator_voting_power, vote_extension) in - self.filter_invalid_eth_events_vexts(vote_extensions) - { - let validator_addr = vote_extension.data.validator_addr; - - // update voting power - let validator_voting_power = u64::from(validator_voting_power); - voting_power += FractionalVotingPower::new( - validator_voting_power, - total_voting_power, - ) - .expect( - "The voting power we obtain from storage should always be \ - valid", - ); - - // register all ethereum events seen by `validator_addr` - for ev in vote_extension.data.ethereum_events { - let signers = - event_observers.entry(ev).or_insert_with(HashSet::new); - - signers.insert(validator_addr.clone()); - } - - // register the signature of `validator_addr` - let addr = validator_addr.clone(); - let sig = vote_extension.sig; - - if let Some(sig) = signatures.insert(addr, sig) { - tracing::warn!( - ?sig, - ?validator_addr, - "Overwrote old signature from validator while \ - constructing ethereum_events::VextDigest" - ); - } - } - - if voting_power <= FractionalVotingPower::TWO_THIRDS { - tracing::error!( - "Tendermint has decided on a block including Ethereum \ - events reflecting <= 2/3 of the total stake" - ); - return None; - } - - let events = event_observers - .into_iter() - .map(|(event, signers)| MultiSignedEthEvent { event, signers }) - .collect(); - - Some(ethereum_events::VextDigest { events, signatures }) - } } /// Yields an iterator over the [`ProtocolTxType`] transactions @@ -332,13 +246,17 @@ mod prepare_block { use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType}; - use namada::types::vote_extensions::{ethereum_events, VoteExtension}; + use namada::types::vote_extensions::ethereum_events::{ + self, MultiSignedEthEvent, + }; + use namada::types::vote_extensions::VoteExtension; use tendermint_proto::abci::tx_record::TxAction; use tendermint_proto::abci::{ ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; use super::*; + use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, }; @@ -526,7 +444,7 @@ mod prepare_block { }, }]; let signatures = { - let mut s = HashMap::new(); + let mut s = std::collections::HashMap::new(); s.insert(ext.data.validator_addr.clone(), ext.sig.clone()); s }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 182e1c6449..8c83e83e89 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -1,11 +1,16 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. +use std::collections::{BTreeMap, HashMap, HashSet}; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; -use namada::types::vote_extensions::ethereum_events; +use namada::types::vote_extensions::ethereum_events::{ + self, MultiSignedEthEvent, +}; +use namada::types::voting_power::FractionalVotingPower; use super::*; use crate::node::ledger::shell::queries::QueriesExt; @@ -151,6 +156,81 @@ where self.validate_eth_events_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) } + + /// Compresses a set of signed Ethereum events into a single + /// [`ethereum_events::VextDigest`], whilst filtering invalid + /// [`Signed`] instances in the process + pub fn compress_ethereum_events( + &self, + vote_extensions: Vec>, + ) -> Option { + let events_epoch = self + .storage + .get_epoch_from_height(self.storage.last_height) + .expect( + "The epoch of the last block height should always be known", + ); + + let mut event_observers = BTreeMap::new(); + let mut signatures = HashMap::new(); + + let total_voting_power = + u64::from(self.storage.get_total_voting_power(Some(events_epoch))); + let mut voting_power = FractionalVotingPower::default(); + + for (validator_voting_power, vote_extension) in + self.filter_invalid_eth_events_vexts(vote_extensions) + { + let validator_addr = vote_extension.data.validator_addr; + + // update voting power + let validator_voting_power = u64::from(validator_voting_power); + voting_power += FractionalVotingPower::new( + validator_voting_power, + total_voting_power, + ) + .expect( + "The voting power we obtain from storage should always be \ + valid", + ); + + // register all ethereum events seen by `validator_addr` + for ev in vote_extension.data.ethereum_events { + let signers = + event_observers.entry(ev).or_insert_with(HashSet::new); + + signers.insert(validator_addr.clone()); + } + + // register the signature of `validator_addr` + let addr = validator_addr.clone(); + let sig = vote_extension.sig; + + if let Some(sig) = signatures.insert(addr, sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing ethereum_events::VextDigest" + ); + } + } + + if voting_power <= FractionalVotingPower::TWO_THIRDS { + tracing::error!( + "Tendermint has decided on a block including Ethereum events \ + reflecting <= 2/3 of the total stake" + ); + return None; + } + + let events = event_observers + .into_iter() + .map(|(event, signers)| MultiSignedEthEvent { event, signers }) + .collect(); + + Some(ethereum_events::VextDigest { events, signatures }) + } } #[cfg(test)] From c69c3494659231b3667929bbbf904e1d1ba91bef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 11:39:22 +0100 Subject: [PATCH 0382/1995] Move more code --- .../lib/node/ledger/shell/prepare_proposal.rs | 51 ++----------------- .../lib/node/ledger/shell/vote_extensions.rs | 40 +++++++++++++++ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0afec9aa8b..0decf300e9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,16 +2,12 @@ #[cfg(not(feature = "ABCI"))] mod prepare_block { - use namada::proto::Signed; - use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::vote_extensions::{ - ethereum_events, validator_set_update, VoteExtensionDigest, - }; - use tendermint_proto::abci::{ - ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, - }; + use namada::types::vote_extensions::VoteExtensionDigest; + use tendermint_proto::abci::{ExtendedCommitInfo, TxRecord}; - use super::super::vote_extensions::deserialize_vote_extensions; + use super::super::vote_extensions::{ + iter_protocol_txs, split_vote_extensions, + }; use super::super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -159,43 +155,6 @@ mod prepare_block { } } - /// Yields an iterator over the [`ProtocolTxType`] transactions - /// in a [`VoteExtensionDigest`]. - fn iter_protocol_txs( - digest: VoteExtensionDigest, - ) -> impl Iterator { - [ - Some(ProtocolTxType::EthereumEvents(digest.ethereum_events)), - digest - .validator_set_update - .map(ProtocolTxType::ValidatorSetUpdate), - ] - .into_iter() - .flat_map(|tx| tx) - } - - /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering - /// out invalid data, and splits these into [`ethereum_events::Vext`] - /// and [`validator_set_update::Vext`] instances. - fn split_vote_extensions( - vote_extensions: Vec, - ) -> ( - Vec>, - Vec, - ) { - let mut eth_evs = vec![]; - let mut valset_upds = vec![]; - - for ext in deserialize_vote_extensions(vote_extensions) { - if let Some(validator_set_update) = ext.validator_set_update { - valset_upds.push(validator_set_update); - } - eth_evs.push(ext.ethereum_events); - } - - (eth_evs, valset_upds) - } - /// Functions for creating the appropriate TxRecord given the /// numeric code pub(super) mod record { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 228d34dd86..a374f77d05 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,8 +9,11 @@ pub mod validator_set_update; #[cfg(not(feature = "ABCI"))] mod extend_votes { use borsh::BorshDeserialize; + use namada::proto::Signed; + use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, + VoteExtensionDigest, }; use tendermint_proto::abci::ExtendedVoteInfo; @@ -188,6 +191,43 @@ mod extend_votes { .ok() }) } + + /// Yields an iterator over the [`ProtocolTxType`] transactions + /// in a [`VoteExtensionDigest`]. + pub fn iter_protocol_txs( + digest: VoteExtensionDigest, + ) -> impl Iterator { + [ + Some(ProtocolTxType::EthereumEvents(digest.ethereum_events)), + digest + .validator_set_update + .map(ProtocolTxType::ValidatorSetUpdate), + ] + .into_iter() + .flat_map(|tx| tx) + } + + /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering + /// out invalid data, and splits these into [`ethereum_events::Vext`] + /// and [`validator_set_update::Vext`] instances. + pub fn split_vote_extensions( + vote_extensions: Vec, + ) -> ( + Vec>, + Vec, + ) { + let mut eth_evs = vec![]; + let mut valset_upds = vec![]; + + for ext in deserialize_vote_extensions(vote_extensions) { + if let Some(validator_set_update) = ext.validator_set_update { + valset_upds.push(validator_set_update); + } + eth_evs.push(ext.ethereum_events); + } + + (eth_evs, valset_upds) + } } #[cfg(not(feature = "ABCI"))] From a8cc2257653b7b72a67f1a09ef16a97cda87f912 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 11 Aug 2022 13:10:24 +0200 Subject: [PATCH 0383/1995] Update documentation/specs/src/interoperability/ethereum-bridge.md Co-authored-by: James --- documentation/specs/src/interoperability/ethereum-bridge.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index 4b7827f4c5..8004feb949 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -88,7 +88,7 @@ keys involved are: `EthereumEvent`. Changes to this `/eth_msgs` storage subspace are only ever made by -validators as part of the ledger code based on the aggregate of vote +nodes as part of the ledger code based on the aggregate of vote extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen in block `n+1` in a deterministic manner based on the vote extensions of the Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong From b5e1c44c68c17208511ca75fe4eeb4e43b8ab6e3 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 11 Aug 2022 13:10:54 +0200 Subject: [PATCH 0384/1995] Update documentation/specs/src/interoperability/ethereum-bridge.md Co-authored-by: James --- documentation/specs/src/interoperability/ethereum-bridge.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index 8004feb949..5877998b7a 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -92,8 +92,8 @@ nodes as part of the ledger code based on the aggregate of vote extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen in block `n+1` in a deterministic manner based on the vote extensions of the Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong -the the `EthBridge` validity predicate. It should disallow any changes to -this storage from transactions. +to the `EthBridge` validity predicate. It should disallow any changes to +this storage from wasm transactions. ### Including events into storage From 09d8d771cbed865a8295574941265514a83a3685 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 9 Aug 2022 14:07:29 +0100 Subject: [PATCH 0385/1995] Split out apply_wasm_tx and apply_protocol_tx --- apps/src/lib/node/ledger/protocol/mod.rs | 202 +++++++++--------- .../lib/node/ledger/shell/finalize_block.rs | 25 ++- apps/src/lib/node/ledger/shell/mod.rs | 17 +- 3 files changed, 125 insertions(+), 119 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 5d3cddeee7..a40480aebb 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -23,8 +23,6 @@ use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; -use crate::node::ledger::shell::Shell; - #[derive(Error, Debug)] pub enum Error { #[error("Storage error: {0}")] @@ -61,56 +59,60 @@ pub enum Error { AccessForbidden(InternalAddress), } -pub(crate) struct ShellParams<'a, D, H, CA> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - CA: 'static + WasmCacheAccess + Sync, -{ - pub block_gas_meter: &'a mut BlockGasMeter, - pub write_log: &'a mut WriteLog, - pub storage: &'a Storage, - pub wasm_dir: &'a std::path::Path, - pub vp_wasm_cache: &'a mut VpCache, - pub tx_wasm_cache: &'a mut TxCache, -} +pub type Result = std::result::Result; -impl<'a, D, H> From<&'a mut Shell> - for ShellParams<'a, D, H, namada::vm::WasmCacheRwAccess> +/// Dispatch a given transaction to be applied based on its type. Some storage +/// updates may be derived and applied natively rather than via the wasm +/// environment, in which case validity predicates will be bypassed. +/// +/// If the given tx is a successfully decrypted payload apply the necessary +/// vps. Otherwise, we include the tx on chain with the gas charge added +/// but no further validations. +pub(crate) fn dispatch_tx<'a, D, H, CA>( + tx_type: TxType, + tx_length: usize, + block_gas_meter: &'a mut BlockGasMeter, + write_log: &'a mut WriteLog, + storage: &'a mut Storage, + vp_wasm_cache: &'a mut VpCache, + tx_wasm_cache: &'a mut TxCache, +) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, + CA: 'static + WasmCacheAccess + Sync, { - fn from(shell: &'a mut Shell) -> Self { - Self { - block_gas_meter: &mut shell.gas_meter, - write_log: &mut shell.write_log, - storage: &mut shell.storage, - wasm_dir: shell.wasm_dir.as_path(), - vp_wasm_cache: &mut shell.vp_wasm_cache, - tx_wasm_cache: &mut shell.tx_wasm_cache, + match tx_type { + TxType::Raw(_) => Err(Error::TxTypeError), + TxType::Decrypted(DecryptedTx::Decrypted(tx)) => apply_wasm_tx( + tx, + tx_length, + block_gas_meter, + write_log, + storage, + vp_wasm_cache, + tx_wasm_cache, + ), + TxType::Protocol(ProtocolTx { tx, .. }) => { + apply_protocol_tx(tx, storage) + } + _ => { + // other transaction types we treat as a noop + Ok(TxResult::default()) } } } -pub type Result = std::result::Result; - -/// Apply a given transaction -/// -/// If the given tx is a successfully decrypted payload apply the necessary -/// vps. Otherwise, we include the tx on chain with the gas charge added -/// but no further validations. -pub(crate) fn apply_tx<'a, D, H, CA>( - tx: TxType, +/// Apply a transaction going via the wasm environment. Gas will be metered and +/// validity predicates will be triggered in the normal way. +pub(crate) fn apply_wasm_tx<'a, D, H, CA>( + tx: Tx, tx_length: usize, - ShellParams { - block_gas_meter, - write_log, - storage, - wasm_dir: _wasm_dir, - vp_wasm_cache, - tx_wasm_cache, - }: ShellParams<'a, D, H, CA>, + block_gas_meter: &'a mut BlockGasMeter, + write_log: &'a mut WriteLog, + storage: &'a Storage, + vp_wasm_cache: &'a mut VpCache, + tx_wasm_cache: &'a mut TxCache, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -121,74 +123,74 @@ where block_gas_meter .add_base_transaction_fee(tx_length) .map_err(Error::GasError)?; - match tx { - TxType::Raw(_) => Err(Error::TxTypeError), - TxType::Decrypted(DecryptedTx::Decrypted(tx)) => { - let verifiers = execute_tx( - &tx, - storage, - block_gas_meter, - write_log, - vp_wasm_cache, - tx_wasm_cache, - )?; + let verifiers = execute_tx( + &tx, + storage, + block_gas_meter, + write_log, + vp_wasm_cache, + tx_wasm_cache, + )?; - let vps_result = check_vps( - &tx, - storage, - block_gas_meter, - write_log, - &verifiers, - vp_wasm_cache, - )?; + let vps_result = check_vps( + &tx, + storage, + block_gas_meter, + write_log, + &verifiers, + vp_wasm_cache, + )?; - let gas_used = block_gas_meter - .finalize_transaction() - .map_err(Error::GasError)?; - let initialized_accounts = write_log.get_initialized_accounts(); - let changed_keys = write_log.get_keys(); - let ibc_event = write_log.take_ibc_event(); + let gas_used = block_gas_meter + .finalize_transaction() + .map_err(Error::GasError)?; + let initialized_accounts = write_log.get_initialized_accounts(); + let changed_keys = write_log.get_keys(); + let ibc_event = write_log.take_ibc_event(); - Ok(TxResult { - gas_used, - changed_keys, - vps_result, - initialized_accounts, - ibc_event, - }) - } - TxType::Protocol(ProtocolTx { - tx: - ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { - events, - .. - }), + Ok(TxResult { + gas_used, + changed_keys, + vps_result, + initialized_accounts, + ibc_event, + }) +} + +/// Apply a derived transaction to storage based on some protocol transaction. +/// The logic here must be completely deterministic and will be executed by all +/// full nodes every time a protocol transaction is included in a block. Storage +/// is updated natively rather than via the wasm environment, so gas does not +/// need to be metered and validity predicates are bypassed. A [`TxResult`] +/// containing changed keys and the like should be returned in the normal way. +pub(crate) fn apply_protocol_tx<'a, D, H>( + tx: ProtocolTxType, + // TODO: eventually this `storage` parameter could be tightened further to + // an impl trait of only the subset of [`Storage`] functionality that we + // need + _storage: &'a mut Storage, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + match tx { + ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { + events, .. }) => { if !events.is_empty() { - tracing::debug!("Ethereum events received"); + tracing::debug!(n = events.len(), "Ethereum events received"); } - let gas_used = block_gas_meter - .finalize_transaction() - .map_err(Error::GasError)?; - Ok(TxResult { - gas_used, - ..Default::default() - }) + Ok(TxResult::default()) } - tx_type @ _ => { + _ => { tracing::error!( - "Attempt made to apply an unsupported transaction! - {:#?}", - tx_type + "Attempt made to apply an unsupported protocol transaction! - {:#?}", + tx ); - let gas_used = block_gas_meter - .finalize_transaction() - .map_err(Error::GasError)?; - Ok(TxResult { - gas_used, - ..Default::default() - }) - } + Err(Error::TxTypeError) + }, } } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index c0055d7ab7..b6b5a0dd26 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -107,9 +107,6 @@ where 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 @@ -117,12 +114,16 @@ where .expect( "Should be able to write to storage.", ); - let tx_result = protocol::apply_tx( - tx_type, + let tx_result = protocol::apply_wasm_tx( + tx, 0, /* this is used to compute the fee * based on the code size. We dont * need it here. */ - self.into(), + &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) @@ -344,8 +345,16 @@ where }, }; - match protocol::apply_tx(tx_type, tx_length, self.into()) - .map_err(Error::TxApply) + match protocol::dispatch_tx( + tx_type, + tx_length, + &mut self.gas_meter, + &mut self.write_log, + &mut self.storage, + &mut self.vp_wasm_cache, + &mut self.tx_wasm_cache, + ) + .map_err(Error::TxApply) { Ok(result) => { if result.is_accepted() { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8f9d3e7b56..06917e4c08 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -74,7 +74,6 @@ use tower_abci_old::{request, response}; use super::rpc; use crate::config::{genesis, TendermintMode}; use crate::node::ledger::events::Event; -use crate::node::ledger::protocol::ShellParams; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{protocol, storage, tendermint_node}; @@ -671,18 +670,14 @@ where let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); match Tx::try_from(tx_bytes) { Ok(tx) => { - let tx = TxType::Decrypted(DecryptedTx::Decrypted(tx)); - match protocol::apply_tx( + match protocol::apply_wasm_tx( tx, tx_bytes.len(), - ShellParams { - block_gas_meter: &mut gas_meter, - write_log: &mut write_log, - storage: &self.storage, - wasm_dir: self.wasm_dir.as_path(), - vp_wasm_cache: &mut vp_wasm_cache, - tx_wasm_cache: &mut tx_wasm_cache, - }, + &mut gas_meter, + &mut write_log, + &self.storage, + &mut vp_wasm_cache, + &mut tx_wasm_cache, ) .map_err(Error::TxApply) { From c54d69e23035c110d39eff8927a5e94f770a6ca0 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 11:34:06 +0100 Subject: [PATCH 0386/1995] Switch back to using ShellParams Some things became mutable --- apps/src/lib/node/ledger/protocol/mod.rs | 73 ++++++++++++++----- .../lib/node/ledger/shell/finalize_block.rs | 12 +-- apps/src/lib/node/ledger/shell/mod.rs | 14 +--- apps/src/lib/node/ledger/shell/queries.rs | 2 +- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index a40480aebb..476229fbbe 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -23,6 +23,8 @@ use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; +use crate::node::ledger::shell::Shell; + #[derive(Error, Debug)] pub enum Error { #[error("Storage error: {0}")] @@ -59,6 +61,36 @@ pub enum Error { AccessForbidden(InternalAddress), } +pub(crate) struct ShellParams<'a, D, H, CA> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + CA: 'static + WasmCacheAccess + Sync, +{ + pub block_gas_meter: &'a mut BlockGasMeter, + pub write_log: &'a mut WriteLog, + pub storage: &'a mut Storage, + pub vp_wasm_cache: &'a mut VpCache, + pub tx_wasm_cache: &'a mut TxCache, +} + +impl<'a, D, H> From<&'a mut Shell> + for ShellParams<'a, D, H, namada::vm::WasmCacheRwAccess> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + fn from(shell: &'a mut Shell) -> Self { + Self { + block_gas_meter: &mut shell.gas_meter, + write_log: &mut shell.write_log, + storage: &mut shell.storage, + vp_wasm_cache: &mut shell.vp_wasm_cache, + tx_wasm_cache: &mut shell.tx_wasm_cache, + } + } +} + pub type Result = std::result::Result; /// Dispatch a given transaction to be applied based on its type. Some storage @@ -71,11 +103,13 @@ pub type Result = std::result::Result; pub(crate) fn dispatch_tx<'a, D, H, CA>( tx_type: TxType, tx_length: usize, - block_gas_meter: &'a mut BlockGasMeter, - write_log: &'a mut WriteLog, - storage: &'a mut Storage, - vp_wasm_cache: &'a mut VpCache, - tx_wasm_cache: &'a mut TxCache, + ShellParams { + block_gas_meter, + write_log, + storage, + vp_wasm_cache, + tx_wasm_cache, + }: ShellParams<'a, D, H, CA>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -87,11 +121,13 @@ where TxType::Decrypted(DecryptedTx::Decrypted(tx)) => apply_wasm_tx( tx, tx_length, - block_gas_meter, - write_log, - storage, - vp_wasm_cache, - tx_wasm_cache, + ShellParams { + block_gas_meter, + write_log, + storage, + vp_wasm_cache, + tx_wasm_cache, + }, ), TxType::Protocol(ProtocolTx { tx, .. }) => { apply_protocol_tx(tx, storage) @@ -108,11 +144,13 @@ where pub(crate) fn apply_wasm_tx<'a, D, H, CA>( tx: Tx, tx_length: usize, - block_gas_meter: &'a mut BlockGasMeter, - write_log: &'a mut WriteLog, - storage: &'a Storage, - vp_wasm_cache: &'a mut VpCache, - tx_wasm_cache: &'a mut TxCache, + ShellParams { + block_gas_meter, + write_log, + storage, + vp_wasm_cache, + tx_wasm_cache, + }: ShellParams<'a, D, H, CA>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -186,11 +224,12 @@ where } _ => { tracing::error!( - "Attempt made to apply an unsupported protocol transaction! - {:#?}", + "Attempt made to apply an unsupported protocol transaction! - \ + {:#?}", tx ); Err(Error::TxTypeError) - }, + } } } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index b6b5a0dd26..925816f3b9 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -119,11 +119,7 @@ where 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.into(), ); self.storage .delete(&pending_execution_key) @@ -348,11 +344,7 @@ where match protocol::dispatch_tx( tx_type, tx_length, - &mut self.gas_meter, - &mut self.write_log, - &mut self.storage, - &mut self.vp_wasm_cache, - &mut self.tx_wasm_cache, + self.into(), ) .map_err(Error::TxApply) { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 06917e4c08..9d6e47a70b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -662,7 +662,7 @@ where } /// Simulate validation and application of a transaction. - fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { + fn dry_run_tx(&mut self, tx_bytes: &[u8]) -> response::Query { let mut response = response::Query::default(); let mut gas_meter = BlockGasMeter::default(); let mut write_log = WriteLog::default(); @@ -670,16 +670,8 @@ where let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); match Tx::try_from(tx_bytes) { Ok(tx) => { - match protocol::apply_wasm_tx( - tx, - tx_bytes.len(), - &mut gas_meter, - &mut write_log, - &self.storage, - &mut vp_wasm_cache, - &mut tx_wasm_cache, - ) - .map_err(Error::TxApply) + match protocol::apply_wasm_tx(tx, tx_bytes.len(), self.into()) + .map_err(Error::TxApply) { Ok(result) => response.info = result.to_string(), Err(error) => { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 4f6a885677..9230dca9ac 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -60,7 +60,7 @@ where /// right query method and returns the result (which may be /// the default if `path` is not a supported string. /// INVARIANT: This method must be stateless. - pub fn query(&self, query: request::Query) -> response::Query { + pub fn query(&mut self, query: request::Query) -> response::Query { use rpc::Path; let height = match query.height { 0 => self.storage.get_block_height().0, From 93477079b2234d554d776a908f4f5ecf1bfcefd8 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 11:48:17 +0100 Subject: [PATCH 0387/1995] Adjust ShellParams --- apps/src/lib/node/ledger/protocol/mod.rs | 16 +++++++--------- .../src/lib/node/ledger/shell/finalize_block.rs | 6 +++++- apps/src/lib/node/ledger/shell/mod.rs | 17 ++++++++++++++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 476229fbbe..1bfe51ddc2 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -69,7 +69,7 @@ where { pub block_gas_meter: &'a mut BlockGasMeter, pub write_log: &'a mut WriteLog, - pub storage: &'a mut Storage, + pub storage: &'a Storage, pub vp_wasm_cache: &'a mut VpCache, pub tx_wasm_cache: &'a mut TxCache, } @@ -84,7 +84,7 @@ where Self { block_gas_meter: &mut shell.gas_meter, write_log: &mut shell.write_log, - storage: &mut shell.storage, + storage: &shell.storage, vp_wasm_cache: &mut shell.vp_wasm_cache, tx_wasm_cache: &mut shell.tx_wasm_cache, } @@ -103,13 +103,11 @@ pub type Result = std::result::Result; pub(crate) fn dispatch_tx<'a, D, H, CA>( tx_type: TxType, tx_length: usize, - ShellParams { - block_gas_meter, - write_log, - storage, - vp_wasm_cache, - tx_wasm_cache, - }: ShellParams<'a, D, H, CA>, + block_gas_meter: &'a mut BlockGasMeter, + write_log: &'a mut WriteLog, + storage: &'a mut Storage, + vp_wasm_cache: &'a mut VpCache, + tx_wasm_cache: &'a mut TxCache, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 925816f3b9..17b78a004a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -344,7 +344,11 @@ where match protocol::dispatch_tx( tx_type, tx_length, - self.into(), + &mut self.gas_meter, + &mut self.write_log, + &mut self.storage, + &mut self.vp_wasm_cache, + &mut self.tx_wasm_cache, ) .map_err(Error::TxApply) { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9d6e47a70b..b98c641aae 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -71,6 +71,7 @@ use tower_abci::{request, response}; #[cfg(feature = "ABCI")] use tower_abci_old::{request, response}; +use super::protocol::ShellParams; use super::rpc; use crate::config::{genesis, TendermintMode}; use crate::node::ledger::events::Event; @@ -662,7 +663,7 @@ where } /// Simulate validation and application of a transaction. - fn dry_run_tx(&mut self, tx_bytes: &[u8]) -> response::Query { + fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { let mut response = response::Query::default(); let mut gas_meter = BlockGasMeter::default(); let mut write_log = WriteLog::default(); @@ -670,8 +671,18 @@ where let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); match Tx::try_from(tx_bytes) { Ok(tx) => { - match protocol::apply_wasm_tx(tx, tx_bytes.len(), self.into()) - .map_err(Error::TxApply) + match protocol::apply_wasm_tx( + tx, + tx_bytes.len(), + ShellParams { + block_gas_meter: &mut gas_meter, + write_log: &mut write_log, + storage: &self.storage, + vp_wasm_cache: &mut vp_wasm_cache, + tx_wasm_cache: &mut tx_wasm_cache, + }, + ) + .map_err(Error::TxApply) { Ok(result) => response.info = result.to_string(), Err(error) => { From 67d9d43b66b0714132cab4e0d18d26b6d353b4f1 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 11:53:43 +0100 Subject: [PATCH 0388/1995] Make Shell:query immutable again --- apps/src/lib/node/ledger/shell/queries.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9230dca9ac..4f6a885677 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -60,7 +60,7 @@ where /// right query method and returns the result (which may be /// the default if `path` is not a supported string. /// INVARIANT: This method must be stateless. - pub fn query(&mut self, query: request::Query) -> response::Query { + pub fn query(&self, query: request::Query) -> response::Query { use rpc::Path; let height = match query.height { 0 => self.storage.get_block_height().0, From 61dd957dc76bad9854b45c01c6651ab840b5099a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:30:43 +0100 Subject: [PATCH 0389/1995] Filter out invalid validator set update vote extensions --- .../vote_extensions/validator_set_update.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 1adee3470a..7761d43e24 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -89,4 +89,42 @@ where }) .map(|_| (voting_power, ext)) } + + /// Takes an iterator over validator set update vote extension instances, + /// and returns another iterator. The latter yields + /// valid validator set update vote extensions, or the reason why these + /// are invalid, in the form of a [`VoteExtensionError`]. + #[inline] + #[allow(dead_code)] + pub fn validate_valset_upd_vext_list( + &self, + vote_extensions: impl IntoIterator + + 'static, + ) -> impl Iterator< + Item = std::result::Result< + (VotingPower, validator_set_update::SignedVext), + VoteExtensionError, + >, + > + '_ { + vote_extensions.into_iter().map(|vote_extension| { + self.validate_valset_upd_vext_and_get_it_back( + vote_extension, + self.storage.get_current_epoch().0, + ) + }) + } + + /// Takes a list of signed validator set update vote extensions, + /// and filters out invalid instances. + #[inline] + #[allow(dead_code)] + pub fn filter_invalid_valset_upd_vexts( + &self, + vote_extensions: impl IntoIterator + + 'static, + ) -> impl Iterator + '_ + { + self.validate_valset_upd_vext_list(vote_extensions) + .filter_map(|ext| ext.ok()) + } } From 6ac1bcc643af132c36f98c1f2ccc0e231bec2cfd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:32:03 +0100 Subject: [PATCH 0390/1995] Add a NOTE to the code --- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 7761d43e24..43aace63d6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -109,6 +109,9 @@ where vote_extensions.into_iter().map(|vote_extension| { self.validate_valset_upd_vext_and_get_it_back( vote_extension, + // NOTE: assumes we are in the new epoch, + // after the prev valset signed off the + // set of the new epoch self.storage.get_current_epoch().0, ) }) From f84f1ae788949c76f701f4632bd386af74a2c473 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:43:01 +0100 Subject: [PATCH 0391/1995] Change the API of can_send_validator_set_update() this pos is giving me trouble --- apps/src/lib/node/ledger/shell/queries.rs | 23 ++++++++++--------- .../lib/node/ledger/shell/vote_extensions.rs | 15 ++++++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 65d711d5fe..be7262c454 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -322,10 +322,11 @@ pub(crate) trait QueriesExt { epoch: Option, ) -> std::result::Result; - /// Determines if we are able to send a validator set update vote extension. - /// This is done by checking if we are at the last block of the current - /// epoch. - fn can_send_validator_set_update(&self) -> bool; + /// Determines if it is possible to send a validator set update vote + /// extension at the provided [`BlockHeight`]. + /// + /// This is done by checking if `height` is the last block of its epoch. + fn can_send_validator_set_update(&self, height: BlockHeight) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch_from_height(&self, height: BlockHeight) -> Option; @@ -499,15 +500,15 @@ where } // TODO: - // - accept last_height param + // - accept `height` param // - get epoch duration from storage - // - use modulo arithmetic (???? maybe) to calc offset of block within the - // epoch + // - calc offset of block height within epoch // - we must be at the last block of the epoch - fn can_send_validator_set_update(&self) -> bool { - let current_height = self.last_height.0 + 1; - let new_epoch_height = self.next_epoch_min_start_height.0; - new_epoch_height.wrapping_sub(current_height) == 1 + fn can_send_validator_set_update(&self, _height: BlockHeight) -> bool { + todo!() + // let current_height = self.last_height.0 + 1; + // let new_epoch_height = self.next_epoch_min_start_height.0; + // new_epoch_height.wrapping_sub(current_height) == 1 } fn get_epoch_from_height(&self, height: BlockHeight) -> Option { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index a374f77d05..6ea125a0a1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -60,16 +60,20 @@ mod extend_votes { .expect("only validators should receive this method call") .to_owned(); + let new_height = self.storage.last_height + 1; + let validator_addr = addr.clone(); let eth_evs = ethereum_events::Vext { - block_height: self.storage.last_height + 1, + block_height: new_height, ethereum_events: self.new_ethereum_events(), validator_addr, }; let validator_addr = addr; - let vset_upd = - self.storage.can_send_validator_set_update().then(|| { + let vset_upd = self + .storage + .can_send_validator_set_update(new_height) + .then(|| { let next_epoch = self.storage.get_current_epoch().0.next(); let _validator_set = self.storage.get_active_validators(Some(next_epoch)); @@ -132,7 +136,8 @@ mod extend_votes { }; } }; - let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, self.storage.last_height + 1) + let new_height = self.storage.last_height + 1; + let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, new_height) .then(|| true) .unwrap_or_else(|| { tracing::warn!( @@ -143,7 +148,7 @@ mod extend_votes { ); false }); - let validated_valset_upd = self.storage.can_send_validator_set_update().then(|| { + let validated_valset_upd = self.storage.can_send_validator_set_update(new_height).then(|| { ext.validator_set_update .and_then(|ext| { self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) From 6b355bc4777508a010ecfa7b2b6930bc52e6a1b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:50:32 +0100 Subject: [PATCH 0392/1995] Fix docstring --- .../lib/node/ledger/shell/vote_extensions/ethereum_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 8c83e83e89..0ac8e891ff 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -159,7 +159,7 @@ where /// Compresses a set of signed Ethereum events into a single /// [`ethereum_events::VextDigest`], whilst filtering invalid - /// [`Signed`] instances in the process + /// [`Signed`] instances in the process. pub fn compress_ethereum_events( &self, vote_extensions: Vec>, From 4120e9a1c940d820c13fa2f0d895d7ca6b4aee9f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:50:53 +0100 Subject: [PATCH 0393/1995] WIP: Compress validator set update vote extensions --- .../shell/vote_extensions/validator_set_update.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 43aace63d6..27fc66f7a1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -130,4 +130,16 @@ where self.validate_valset_upd_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) } + + /// Compresses a set of signed validator set update vote extensions into a + /// single [`validator_set_update::VextDigest`], whilst filtering + /// invalid [`validator_set_update::SignedVext`] instances in the + /// process. + #[allow(dead_code)] + pub fn compress_valset_updates( + &self, + _vote_extensions: Vec, + ) -> Option { + todo!() + } } From b5aea3b5a90595adbd30dfddb718106a9c83a681 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:55:33 +0100 Subject: [PATCH 0394/1995] Include validator set update vote extensions digest in PrepareProposal --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ef7f933a2e..87e2f47913 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,6 +9,7 @@ mod prepare_block { iter_protocol_txs, split_vote_extensions, }; use super::super::*; + use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -77,7 +78,7 @@ mod prepare_block { return vec![]; } - let (eth_events, _valset_upds) = split_vote_extensions( + let (eth_events, valset_upds) = split_vote_extensions( local_last_commit .expect( "Honest Namada validators will always sign \ @@ -101,6 +102,15 @@ mod prepare_block { .compress_ethereum_events(eth_events) .expect(NOT_ENOUGH_VOTING_POWER_MSG); + let validator_set_update = self + .storage + .can_send_validator_set_update(self.storage.last_height) + .then(|| ()) + .map(|()| { + self.compress_valset_updates(valset_upds) + .expect(NOT_ENOUGH_VOTING_POWER_MSG) + }); + let protocol_key = self .mode .get_protocol_key() @@ -108,7 +118,7 @@ mod prepare_block { iter_protocol_txs(VoteExtensionDigest { ethereum_events, - validator_set_update: None, + validator_set_update, }) .map(|tx| record::add(tx.sign(protocol_key).to_bytes())) .collect() From 835635dac900f75b8cad87cd55f56bb114fc70da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 15:59:27 +0100 Subject: [PATCH 0395/1995] Clean up the TODO comments in PrepareProposal --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 87e2f47913..7e16f3d8ed 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -35,7 +35,7 @@ mod prepare_block { // proposal is accepted self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { - // TODO: add some info logging + // TODO: add some info logging? // add ethereum events as protocol txs let mut txs = @@ -66,9 +66,7 @@ mod prepare_block { } /// Builds a batch of vote extension transactions, comprised of Ethereum - /// events - // TODO: add `and, optionally, a validator set update` to the docstring, - // after validator set updates are implemented + /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, local_last_commit: Option, @@ -205,8 +203,8 @@ mod prepare_block { } #[cfg(test)] - // TODO: write a test to check for unreachable code paths in - // prepare proposals, when processing ethereum events + // TODO: write tests for validator set update vote extensions in + // prepare proposals mod test_prepare_proposal { use std::collections::HashSet; From 6970a1b82c4cd13644928cc48356c39c9262eeb4 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 16:14:29 +0100 Subject: [PATCH 0396/1995] Fix bare hyperlinks in docstrings --- shared/src/types/vote_extensions/ethereum_events.rs | 2 +- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 524d03aa52..9086be0658 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -23,7 +23,7 @@ pub struct Vext { pub block_height: BlockHeight, /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator - /// address (see https://github.com/anoma/namada/issues/200) + /// address (see ) pub validator_addr: Address, /// The new ethereum events seen. These should be /// deterministically ordered. diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 2e26d6369b..4762f3bb33 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -79,7 +79,7 @@ pub struct Vext { pub voting_powers: VotingPowersMap, /// TODO: the validator's address is temporarily being included /// until we're able to map a Tendermint address to a validator - /// address (see https://github.com/anoma/namada/issues/200) + /// address (see ) pub validator_addr: Address, /// The new [`Epoch`]. /// From a6c8e1890e539e5aff452541686a436c12aac284 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 16:18:02 +0100 Subject: [PATCH 0397/1995] Link to ABCI++ method instead of ABCI --- apps/src/lib/node/ledger/shell/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index b98c641aae..2e208c1b8c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1,10 +1,9 @@ //! 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 -//! (unless we can simply overwrite them in the next block). -//! More info in . +//! reverted, so any changes applied in the methods [`Shell::prepare_proposal`] +//! must be also reverted (unless we can simply overwrite them in the next +//! block). More info in . mod finalize_block; mod init_chain; #[cfg(not(feature = "ABCI"))] From d4fbd2febd801c77e5005bda27b4813f01423e37 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 16:18:28 +0100 Subject: [PATCH 0398/1995] Remove link syntax --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1764b7f4b1..62893d9a80 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 `PrepareProposal` ABCI++ method for the Shell #[cfg(not(feature = "ABCI"))] mod prepare_block { From 19a930cbdd47329d2537380405f2ff1a163d3eac Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 16:23:21 +0100 Subject: [PATCH 0399/1995] Guard mock_web3_client by eth-fullnode feature --- apps/src/lib/node/ledger/ethereum_node/test_tools.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 3115e4cda6..6c8a46065c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -47,7 +47,7 @@ pub mod mock_oracle { } } -#[cfg(test)] +#[cfg(all(test, feature = "eth-fullnode"))] pub mod mock_web3_client { use std::cell::RefCell; use std::fmt::Debug; From e1750783615dbc757ac6a0963b790abf698c6362 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 16:31:22 +0100 Subject: [PATCH 0400/1995] Guard signatures by eth-fullnode feature --- apps/src/lib/node/ledger/ethereum_node/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 03e88665e9..8294272eb3 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,4 +1,4 @@ -#[cfg(any(test, feature = "eth-fullnode"))] +#[cfg(feature = "eth-fullnode")] pub mod signatures { pub const TRANSFER_TO_NAMADA_SIG: &str = "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; From 679cbf24ad5cc2af1fae10369cbcfbdc6c452a4e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 16:36:16 +0100 Subject: [PATCH 0401/1995] Implement compress_valset_updates() --- .../vote_extensions/validator_set_update.rs | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 27fc66f7a1..42f77c5b1e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -1,10 +1,13 @@ //! Extend Tendermint votes with validator set updates, to be relayed to //! Namada's Ethereum bridge smart contracts. +use std::collections::HashMap; + use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::types::storage::Epoch; use namada::types::vote_extensions::validator_set_update; +use namada::types::voting_power::FractionalVotingPower; use super::*; use crate::node::ledger::shell::queries::QueriesExt; @@ -138,8 +141,71 @@ where #[allow(dead_code)] pub fn compress_valset_updates( &self, - _vote_extensions: Vec, + vote_extensions: Vec, ) -> Option { - todo!() + let total_voting_power = { + let prev_valset_epoch = self.storage.get_current_epoch().0 - 1; + u64::from( + self.storage.get_total_voting_power(Some(prev_valset_epoch)), + ) + }; + let mut voting_power = FractionalVotingPower::default(); + + let mut voting_powers = None; + let mut signatures = HashMap::new(); + + for (validator_voting_power, mut vote_extension) in + self.filter_invalid_valset_upd_vexts(vote_extensions) + { + if voting_powers.is_none() { + voting_powers = Some(std::mem::take( + &mut vote_extension.data.voting_powers, + )); + } + + let validator_addr = vote_extension.data.validator_addr; + + // update voting power + let validator_voting_power = u64::from(validator_voting_power); + voting_power += FractionalVotingPower::new( + validator_voting_power, + total_voting_power, + ) + .expect( + "The voting power we obtain from storage should always be \ + valid", + ); + + // register the signature of `validator_addr` + let addr = validator_addr.clone(); + let sig = vote_extension.sig; + + if let Some(sig) = signatures.insert(addr, sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing validator_set_update::VextDigest" + ); + } + } + + if voting_power <= FractionalVotingPower::TWO_THIRDS { + tracing::error!( + "Tendermint has decided on a block including Ethereum events \ + reflecting <= 2/3 of the total stake" + ); + return None; + } + + let voting_powers = voting_powers.expect( + "We have enough voting power, so at least one validator set \ + update vote extension must have been validated.", + ); + + Some(validator_set_update::VextDigest { + signatures, + voting_powers, + }) } } From d0891d43406c6363d39b8954c3a526073447aae9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 16:38:09 +0100 Subject: [PATCH 0402/1995] Fix panic message --- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 42f77c5b1e..d502bf0f2e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -192,8 +192,8 @@ where if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( - "Tendermint has decided on a block including Ethereum events \ - reflecting <= 2/3 of the total stake" + "Tendermint has decided on a block including validator set \ + update vote extensions reflecting <= 2/3 of the total stake" ); return None; } From fc3b483a24532f1fbfeae1ff1cdae0b4e6826df2 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 17:18:21 +0100 Subject: [PATCH 0403/1995] Fix broken doc link --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 62893d9a80..05705acd53 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -172,7 +172,8 @@ mod prepare_block { /// Compresses a set of signed Ethereum events into a single /// [`ethereum_events::VextDigest`], whilst filtering invalid - /// [`Signed`] instances in the process + /// [`namada::proto::Signed`] instances in the + /// process // TODO: rename this as `compress_vote_extensions`, and return // a `VoteExtensionDigest`, which will contain both digests of // ethereum events and validator set update vote extensions From 2dd776cee956f8c089a7c96b685cbbaedb12d122 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 17:20:00 +0100 Subject: [PATCH 0404/1995] Fix broken doc link --- apps/src/lib/wallet/pre_genesis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 6ecb396004..1014a7a8e5 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -138,8 +138,8 @@ impl ValidatorWallet { } } - /// Generate a new [`Validator`] with required pre-genesis keys. Will prompt - /// for password when `!unsafe_dont_encrypt`. + /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will + /// prompt for password when `!unsafe_dont_encrypt`. fn gen(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); From e51957411720888e4208e824fc168cb31f6b683a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 11 Aug 2022 19:49:39 +0100 Subject: [PATCH 0405/1995] Improve comment --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7e16f3d8ed..15243d6964 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -37,7 +37,7 @@ mod prepare_block { let txs = if let ShellMode::Validator { .. } = self.mode { // TODO: add some info logging? - // add ethereum events as protocol txs + // add ethereum events and validator set updates as protocol txs let mut txs = self.build_vote_extensions_txs(req.local_last_commit); From 5cd0749e84276246a36aab96a6fc09628c9a1278 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 09:52:58 +0100 Subject: [PATCH 0406/1995] Expose first block heights of each successive epoch in Epochs --- shared/src/types/storage.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index e789a95dc4..6b6f716459 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -677,6 +677,13 @@ impl Epochs { } None } + + /// Return all starting block heights for each successive Epoch. + /// + /// __INVARIANT:__ The returned values are sorted in ascending order. + pub fn first_block_heights(&self) -> &[BlockHeight] { + &self.first_block_heights + } } #[cfg(feature = "ferveo-tpke")] From e44cba9726429a86a7d180588d6eb1fcbfe0b926 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 09:53:18 +0100 Subject: [PATCH 0407/1995] Change can_send_validator_set_update() again lol --- apps/src/lib/node/ledger/shell/queries.rs | 33 +++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index be7262c454..1b31907c21 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -325,7 +325,15 @@ pub(crate) trait QueriesExt { /// Determines if it is possible to send a validator set update vote /// extension at the provided [`BlockHeight`]. /// - /// This is done by checking if `height` is the last block of its epoch. + /// This is done by checking if we are at the first block of a new epoch, + /// or if we are at block height 1 of the first epoch. + /// + /// The genesis block will not have vote extensions, + /// therefore it is a special case, which we account for + /// by checking if the block height is 1. Otherwise, + /// validator set update votes will always extend + /// Tendermint's PreCommit phase of the first block of + /// an epoch. fn can_send_validator_set_update(&self, height: BlockHeight) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. @@ -499,16 +507,19 @@ where }) } - // TODO: - // - accept `height` param - // - get epoch duration from storage - // - calc offset of block height within epoch - // - we must be at the last block of the epoch - fn can_send_validator_set_update(&self, _height: BlockHeight) -> bool { - todo!() - // let current_height = self.last_height.0 + 1; - // let new_epoch_height = self.next_epoch_min_start_height.0; - // new_epoch_height.wrapping_sub(current_height) == 1 + fn can_send_validator_set_update(&self, height: BlockHeight) -> bool { + // handle genesis block corner case + if height == BlockHeight(1) { + return true; + } + + // the values in `first_epoch_heights` are stored in ascending + // order, so we can just do a binary search over them + self.block + .pred_epochs + .first_block_heights() + .binary_search(&height) + .is_ok() } fn get_epoch_from_height(&self, height: BlockHeight) -> Option { From 073923ed4e487f8e403ef204914892f2b520482a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:00:25 +0100 Subject: [PATCH 0408/1995] Tentatively check block height --- apps/src/lib/node/ledger/shell/queries.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1b31907c21..04180705ac 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -513,13 +513,21 @@ where return true; } + let first_epoch_heights = self.block.pred_epochs.first_block_heights(); + + // tentatively check if the last stored height + // is the one we are looking for + if first_epoch_heights + .last() + .map(|&h| h == height) + .unwrap_or(false) + { + return true; + } + // the values in `first_epoch_heights` are stored in ascending // order, so we can just do a binary search over them - self.block - .pred_epochs - .first_block_heights() - .binary_search(&height) - .is_ok() + first_epoch_heights.binary_search(&height).is_ok() } fn get_epoch_from_height(&self, height: BlockHeight) -> Option { From c021ace06a2b60bc5c6581f0ecd435fb35a0f37e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:13:26 +0100 Subject: [PATCH 0409/1995] Improve the docstrings of verify_vote_extension() --- .../lib/node/ledger/shell/vote_extensions.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 6ea125a0a1..a5d4ad8e26 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -111,9 +111,16 @@ mod extend_votes { } /// This checks that the vote extension: - /// * Correctly deserializes - /// * Was correctly signed by an active validator. - /// * The block height signed over is correct (replay protection) + /// * Correctly deserializes. + /// * The Ethereum events vote extension within was correctly signed by + /// an active validator. + /// * The validator set update vote extension within was correctly + /// signed by an active validator, in case it could have been sent at + /// the current block height. + /// * The Ethereum events vote extension block height signed over is + /// correct (for replay protection). + /// * The validator set update vote extension epoch signed over is + /// correct (for replay protection). /// /// INVARIANT: This method must be stateless. pub fn verify_vote_extension( @@ -136,8 +143,8 @@ mod extend_votes { }; } }; - let new_height = self.storage.last_height + 1; - let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, new_height) + let curr_height = self.storage.last_height + 1; + let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, curr_height) .then(|| true) .unwrap_or_else(|| { tracing::warn!( @@ -148,7 +155,7 @@ mod extend_votes { ); false }); - let validated_valset_upd = self.storage.can_send_validator_set_update(new_height).then(|| { + let validated_valset_upd = self.storage.can_send_validator_set_update(curr_height).then(|| { ext.validator_set_update .and_then(|ext| { self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) From eaece664924a66f4370b2136304a57152171d43c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:14:57 +0100 Subject: [PATCH 0410/1995] Rename new_height to curr_height for clarity --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index a5d4ad8e26..5afd5fb5d0 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -60,11 +60,11 @@ mod extend_votes { .expect("only validators should receive this method call") .to_owned(); - let new_height = self.storage.last_height + 1; + let curr_height = self.storage.last_height + 1; let validator_addr = addr.clone(); let eth_evs = ethereum_events::Vext { - block_height: new_height, + block_height: curr_height, ethereum_events: self.new_ethereum_events(), validator_addr, }; @@ -72,7 +72,7 @@ mod extend_votes { let validator_addr = addr; let vset_upd = self .storage - .can_send_validator_set_update(new_height) + .can_send_validator_set_update(curr_height) .then(|| { let next_epoch = self.storage.get_current_epoch().0.next(); let _validator_set = From 378c16bdcfa15b15d168635972a683ce84ab5d32 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:17:23 +0100 Subject: [PATCH 0411/1995] Add a TODO --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5afd5fb5d0..8a8e25b70a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -69,6 +69,10 @@ mod extend_votes { validator_addr, }; + // TODO: should we move this inside the if block below? + // non-validator nodes don't need to perform these checks; + // similarly, the ethereum events stuff above could be moved + // to the if block below let validator_addr = addr; let vset_upd = self .storage From af3c316170b9ef41dd643b03d699ca71895b7b57 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:50:10 +0100 Subject: [PATCH 0412/1995] Fix epoch value used in validation --- .../shell/vote_extensions/validator_set_update.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index d502bf0f2e..1c59d87624 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -112,10 +112,16 @@ where vote_extensions.into_iter().map(|vote_extension| { self.validate_valset_upd_vext_and_get_it_back( vote_extension, - // NOTE: assumes we are in the new epoch, - // after the prev valset signed off the - // set of the new epoch - self.storage.get_current_epoch().0, + // NOTE: make sure we do not change epochs between + // extending votes and deciding on the validator + // set update through consensus. otherwise, this + // is going to fail. + // + // as an alternative to using epochs, we can use + // block heights as a nonce, that way we can + // always retrieve the proper epoch from the + // block height + self.storage.get_current_epoch().0.next(), ) }) } From 8bcf69a95420915bc90d1e0fbf769ce1b8a49ce2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 10:51:19 +0100 Subject: [PATCH 0413/1995] Short circuit on can_send_validator_set_update() --- .../lib/node/ledger/shell/prepare_proposal.rs | 6 +++-- apps/src/lib/node/ledger/shell/queries.rs | 22 ++++++++++++++++--- .../lib/node/ledger/shell/vote_extensions.rs | 6 ++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 15243d6964..7ab2770215 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,7 +9,7 @@ mod prepare_block { iter_protocol_txs, split_vote_extensions, }; use super::super::*; - use crate::node::ledger::shell::queries::QueriesExt; + use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -102,7 +102,9 @@ mod prepare_block { let validator_set_update = self .storage - .can_send_validator_set_update(self.storage.last_height) + .can_send_validator_set_update(SendValsetUpd::AtPrevHeight( + self.storage.last_height, + )) .then(|| ()) .map(|()| { self.compress_valset_updates(valset_upds) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 04180705ac..5b920f3cdb 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -334,7 +334,7 @@ pub(crate) trait QueriesExt { /// validator set update votes will always extend /// Tendermint's PreCommit phase of the first block of /// an epoch. - fn can_send_validator_set_update(&self, height: BlockHeight) -> bool; + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch_from_height(&self, height: BlockHeight) -> Option; @@ -507,7 +507,12 @@ where }) } - fn can_send_validator_set_update(&self, height: BlockHeight) -> bool { + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { + let (check_prev_heights, height) = match can_send { + SendValsetUpd::Now => (false, self.last_height + 1), + SendValsetUpd::AtPrevHeight(h) => (true, h), + }; + // handle genesis block corner case if height == BlockHeight(1) { return true; @@ -527,10 +532,21 @@ where // the values in `first_epoch_heights` are stored in ascending // order, so we can just do a binary search over them - first_epoch_heights.binary_search(&height).is_ok() + check_prev_heights && first_epoch_heights.binary_search(&height).is_ok() } fn get_epoch_from_height(&self, height: BlockHeight) -> Option { self.block.pred_epochs.get_epoch(height) } } + +/// This enum is used as a parameter to +/// [`QueriesExt::can_send_validator_set_update`]. +pub enum SendValsetUpd { + /// Check if it is possible to send a validator set update + /// vote extension at the current block height. + Now, + /// Check if it is possible to send a validator set update + /// vote extension at any previous block height. + AtPrevHeight(BlockHeight), +} diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 8a8e25b70a..5b55016a99 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -18,7 +18,7 @@ mod extend_votes { use tendermint_proto::abci::ExtendedVoteInfo; use super::super::*; - use crate::node::ledger::shell::queries::QueriesExt; + use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; /// The error yielded from validating faulty vote extensions in the shell #[derive(Error, Debug)] @@ -76,7 +76,7 @@ mod extend_votes { let validator_addr = addr; let vset_upd = self .storage - .can_send_validator_set_update(curr_height) + .can_send_validator_set_update(SendValsetUpd::Now) .then(|| { let next_epoch = self.storage.get_current_epoch().0.next(); let _validator_set = @@ -159,7 +159,7 @@ mod extend_votes { ); false }); - let validated_valset_upd = self.storage.can_send_validator_set_update(curr_height).then(|| { + let validated_valset_upd = self.storage.can_send_validator_set_update(SendValsetUpd::Now).then(|| { ext.validator_set_update .and_then(|ext| { self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) From 75cbfaa4db3df0dd9d0ec4d1c5cb4cd1577ea964 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 13:11:16 +0100 Subject: [PATCH 0414/1995] A bit of clean up on extend_vote() --- .../lib/node/ledger/shell/vote_extensions.rs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 5b55016a99..2397b4c80e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -69,10 +69,6 @@ mod extend_votes { validator_addr, }; - // TODO: should we move this inside the if block below? - // non-validator nodes don't need to perform these checks; - // similarly, the ethereum events stuff above could be moved - // to the if block below let validator_addr = addr; let vset_upd = self .storage @@ -91,27 +87,28 @@ mod extend_votes { } }); - if let ShellMode::Validator { data, .. } = &self.mode { - let protocol_key = &data.keys.protocol_keypair; + let validator_data = match &self.mode { + ShellMode::Validator { data, .. } => data, + _ => unreachable!("only validators receive this method call"), + }; - let vset_upd = vset_upd.map(|ext| { - // TODO: sign validator set update with secp key instead - ext.sign(protocol_key) - }); + let protocol_key = &validator_data.keys.protocol_keypair; - let eth_evs = eth_evs.sign(protocol_key); + let vset_upd = vset_upd.map(|ext| { + // TODO: sign validator set update with secp key instead + ext.sign(protocol_key) + }); - let vote_extension = VoteExtension { - ethereum_events: eth_evs, - validator_set_update: vset_upd, - } - .try_to_vec() - .unwrap(); + let eth_evs = eth_evs.sign(protocol_key); - response::ExtendVote { vote_extension } - } else { - Default::default() + let vote_extension = VoteExtension { + ethereum_events: eth_evs, + validator_set_update: vset_upd, } + .try_to_vec() + .unwrap(); + + response::ExtendVote { vote_extension } } /// This checks that the vote extension: From 9ed714221d989b3815add72bc5f512041b965296 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 13:33:17 +0100 Subject: [PATCH 0415/1995] Refactor validate and verify vexts --- .../lib/node/ledger/shell/vote_extensions.rs | 137 ++++++++++++------ 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 2397b4c80e..3e894af5ef 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -20,6 +20,10 @@ mod extend_votes { use super::super::*; use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; + /// Message to be passed to `.expect()` calls in this module. + const VALIDATOR_EXPECT_MSG: &str = + "Only validators receive this method call."; + /// The error yielded from validating faulty vote extensions in the shell #[derive(Error, Debug)] pub enum VoteExtensionError { @@ -54,61 +58,80 @@ mod extend_votes { &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - let addr = self + let vote_extension = VoteExtension { + ethereum_events: self.extend_vote_with_ethereum_events(), + validator_set_update: self.extend_vote_with_valset_update(), + } + .try_to_vec() + .unwrap(); + + response::ExtendVote { vote_extension } + } + + /// Extend PreCommit votes with [`ethereum_events::Vext`] instances. + pub fn extend_vote_with_ethereum_events( + &mut self, + ) -> Signed { + let validator_addr = self .mode .get_validator_address() - .expect("only validators should receive this method call") + .expect(VALIDATOR_EXPECT_MSG) .to_owned(); let curr_height = self.storage.last_height + 1; - let validator_addr = addr.clone(); - let eth_evs = ethereum_events::Vext { + let ext = ethereum_events::Vext { block_height: curr_height, ethereum_events: self.new_ethereum_events(), validator_addr, }; - let validator_addr = addr; - let vset_upd = self - .storage + let protocol_key = match &self.mode { + ShellMode::Validator { data, .. } => { + &data.keys.protocol_keypair + } + _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), + }; + + ext.sign(protocol_key) + } + + /// Extend PreCommit votes with [`validator_set_update::Vext`] + /// instances. + pub fn extend_vote_with_valset_update( + &mut self, + ) -> Option { + let validator_addr = self + .mode + .get_validator_address() + .expect(VALIDATOR_EXPECT_MSG) + .to_owned(); + + self.storage .can_send_validator_set_update(SendValsetUpd::Now) .then(|| { let next_epoch = self.storage.get_current_epoch().0.next(); let _validator_set = self.storage.get_active_validators(Some(next_epoch)); - validator_set_update::Vext { + let ext = validator_set_update::Vext { validator_addr, // TODO: we need a way to map ethereum addresses to // namada validator addresses voting_powers: std::collections::HashMap::new(), epoch: next_epoch, - } - }); + }; - let validator_data = match &self.mode { - ShellMode::Validator { data, .. } => data, - _ => unreachable!("only validators receive this method call"), - }; - - let protocol_key = &validator_data.keys.protocol_keypair; - - let vset_upd = vset_upd.map(|ext| { - // TODO: sign validator set update with secp key instead - ext.sign(protocol_key) - }); - - let eth_evs = eth_evs.sign(protocol_key); - - let vote_extension = VoteExtension { - ethereum_events: eth_evs, - validator_set_update: vset_upd, - } - .try_to_vec() - .unwrap(); + let protocol_key = match &self.mode { + ShellMode::Validator { data, .. } => { + &data.keys.protocol_keypair + } + _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), + }; - response::ExtendVote { vote_extension } + // TODO: sign validator set update with secp key instead + ext.sign(protocol_key) + }) } /// This checks that the vote extension: @@ -144,8 +167,29 @@ mod extend_votes { }; } }; + + let validated_eth_events = + self.verify_ethereum_events(&req, ext.ethereum_events); + let validated_valset_upd = + self.verify_valset_update(&req, ext.validator_set_update); + + response::VerifyVoteExtension { + status: if validated_eth_events && validated_valset_upd { + VerifyStatus::Accept.into() + } else { + VerifyStatus::Reject.into() + }, + } + } + + /// Check if [`ethereum_events::Vext`] instances are valid. + pub fn verify_ethereum_events( + &self, + req: &request::VerifyVoteExtension, + ext: Signed, + ) -> bool { let curr_height = self.storage.last_height + 1; - let validated_eth_events = self.validate_eth_events_vext(ext.ethereum_events, curr_height) + self.validate_eth_events_vext(ext, curr_height) .then(|| true) .unwrap_or_else(|| { tracing::warn!( @@ -155,19 +199,31 @@ mod extend_votes { "Received Ethereum events vote extension that didn't validate" ); false - }); - let validated_valset_upd = self.storage.can_send_validator_set_update(SendValsetUpd::Now).then(|| { - ext.validator_set_update + }) + } + + /// Check if [`validator_set_update::Vext`] instances are valid. + pub fn verify_valset_update( + &self, + req: &request::VerifyVoteExtension, + ext: Option, + ) -> bool { + self.storage.can_send_validator_set_update(SendValsetUpd::Now).then(|| { + ext .and_then(|ext| { + // we have a valset update vext when we're expecting one, cool, + // let's validate it self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) .then(|| true) }) .unwrap_or_else(|| { + // either validation failed, or we were expecting a valset update + // vext and got none tracing::warn!( ?req.validator_address, ?req.hash, req.height, - "Received validator set update vote extension that didn't validate" + "Missing or invalid validator set update vote extension" ); false }) @@ -176,14 +232,7 @@ mod extend_votes { // vote extension at a particular block height, we will // just return true as the validation result true - }); - response::VerifyVoteExtension { - status: if validated_eth_events && validated_valset_upd { - VerifyStatus::Accept.into() - } else { - VerifyStatus::Reject.into() - }, - } + }) } } From 14dcc9d59139bad16b17e70038f9d981dc76c438 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 14:23:07 +0100 Subject: [PATCH 0416/1995] Test if we reject valset update vexts signed over with invalid epochs --- .../vote_extensions/validator_set_update.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 1c59d87624..b7f49c7d07 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -215,3 +215,67 @@ where }) } } + +#[cfg(test)] +mod test_vote_extensions { + use std::default::Default; + + use borsh::BorshSerialize; + use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::vote_extensions::{ + ethereum_events, validator_set_update, VoteExtension, + }; + use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + use tower_abci::request; + + use crate::node::ledger::shell::test_utils; + + const FIRST_HEIGHT_WITH_VEXTS: BlockHeight = BlockHeight(1); + + /// Test if a [`validator_set_update::Vext`] that incorrectly labels what + /// epoch it was included on in a vote extension is rejected + // TODO: + // - sign with secp key + // - add validator voting powers from storage + #[test] + fn test_reject_incorrect_epoch() { + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let validator_addr = + shell.mode.get_validator_address().unwrap().clone(); + + let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + + let ethereum_events = ethereum_events::Vext::empty( + FIRST_HEIGHT_WITH_VEXTS, + validator_addr.clone(), + ) + .sign(protocol_key); + + let validator_set_update = Some( + validator_set_update::Vext { + // TODO: get voting powers from storage, associated with eth + // addrs + voting_powers: std::collections::HashMap::new(), + validator_addr, + // invalid epoch, should have been: current epoch + 1 + epoch: Epoch(2), + } + .sign(protocol_key), + ); + + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } +} From 8753694ce5e2ee7c85a9c532dc00cb81c09b12b7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 15:30:50 +0100 Subject: [PATCH 0417/1995] Test validaor set update keccak hash --- .../vote_extensions/validator_set_update.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index f994b3bc44..c45e5f4aba 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -250,3 +250,36 @@ mod tag { #[doc(inline)] pub use tag::SerializeWithAbiEncode; + +#[cfg(test)] +mod tests { + use super::*; + + /// Test the keccak hash of a validator set update + // TODO: fix this :| + #[test] + fn test_validator_set_update_keccak_hash() { + // ```js + // const ethers = require('ethers'); + // const keccak256 = require('keccak256') + // + // const abiEncoder = new ethers.utils.AbiCoder(); + // + // const output = abiEncoder.encode( + // ['string', 'address[]', 'uint256[]', 'uint256'], + // ['bridge', [], [], 0], + // ); + // + // const hash = keccak256(output).toString('hex'); + // + // console.log(hash); + // ``` + const EXPECTED: &str = + "36bcf52e7ae929b6df7489d012c8ca63eddb35c1b0baf10f46cac81f6728e0a6"; + + let KeccakHash(got) = + compute_hash(Epoch(0), BRIDGE_CONTRACT_NAMESPACE, vec![], vec![]); + + assert_eq!(&hex::encode(got), EXPECTED); + } +} From 947084f32c2aa8c0ba7f3e603f1b07badb80d11c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 12 Aug 2022 15:44:22 +0100 Subject: [PATCH 0418/1995] Sanity check the keccak hash impl --- .../validator_set_update/encoding.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 7f9e9ff0aa..56499fd0a3 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -85,4 +85,21 @@ mod tests { ]); assert_eq!(expected, got); } + + /// Sanity check our keccak hash implementation. + #[test] + fn test_keccak_hash_impl() { + let expected = + "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; + assert_eq!( + expected, + &hex::encode({ + let mut st = Keccak::v256(); + let mut output = [0; 32]; + st.update(b"hello"); + st.finalize(&mut output); + output + }) + ); + } } From c3e34d1b1aa8bc6097e11243033890c25b6a83e9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 19 Aug 2022 17:24:51 +0100 Subject: [PATCH 0419/1995] Fixing keccak hashing issues We should be hashing the raw bytes of the Ethereum ABI encoding, not its hex encoded data. --- .../vote_extensions/validator_set_update.rs | 1 - .../validator_set_update/encoding.rs | 22 +++++++------------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index c45e5f4aba..2790b03150 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -256,7 +256,6 @@ mod tests { use super::*; /// Test the keccak hash of a validator set update - // TODO: fix this :| #[test] fn test_validator_set_update_keccak_hash() { // ```js diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs index 56499fd0a3..0ba253ee23 100644 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ b/shared/src/types/vote_extensions/validator_set_update/encoding.rs @@ -10,12 +10,8 @@ use crate::types::ethereum_events::KeccakHash; /// Contains a method to encode data to a format compatible with Ethereum. pub trait Encode { - /// The data type to be encoded to. Must deref to a hex string with - /// a `0x` prefix. - type HexString: AsRef; - /// Returns the encoded [`Token`] instances. - fn encode(tokens: &[Token]) -> Self::HexString; + fn encode(tokens: &[Token]) -> Vec; /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. @@ -23,7 +19,7 @@ pub trait Encode { let mut output = [0; 32]; let mut state = Keccak::v256(); - state.update(Self::encode(tokens).as_ref().as_ref()); + state.update(&Self::encode(tokens)); state.finalize(&mut output); KeccakHash(output) @@ -36,13 +32,12 @@ pub trait Encode { let mut output = [0; 32]; let eth_message = { - let encoded = Self::encode(tokens); - let message: &[u8] = encoded.as_ref().as_ref(); + let message = Self::encode(tokens); let mut eth_message = format!("\x19Ethereum Signed Message:\n{}", message.len()) .into_bytes(); - eth_message.extend_from_slice(message); + eth_message.extend_from_slice(&message); eth_message }; @@ -59,11 +54,9 @@ pub trait Encode { pub struct AbiEncode; impl Encode for AbiEncode { - type HexString = String; - - fn encode(tokens: &[Token]) -> Self::HexString { - let encoded_data = hex::encode(ethabi::encode(tokens)); - format!("0x{encoded_data}") + #[inline] + fn encode(tokens: &[Token]) -> Vec { + ethabi::encode(tokens) } } @@ -79,6 +72,7 @@ mod tests { #[test] fn test_abi_encode() { let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + let expected = hex::decode(&expected[2..]).expect("Test failed"); let got = AbiEncode::encode(&[ Token::Uint(U256::from(42u64)), Token::String("test".into()), From 22afc226d0e29975ff2b391558e9c85074ef8427 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 Aug 2022 13:59:37 +0100 Subject: [PATCH 0420/1995] Test if we reject signatures from non-validators in valset upd vexts --- .../vote_extensions/validator_set_update.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index b7f49c7d07..44a9798944 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -229,6 +229,7 @@ mod test_vote_extensions { use tower_abci::request; use crate::node::ledger::shell::test_utils; + use crate::wallet; const FIRST_HEIGHT_WITH_VEXTS: BlockHeight = BlockHeight(1); @@ -278,4 +279,45 @@ mod test_vote_extensions { i32::from(VerifyStatus::Reject) ); } + + /// Test that validator set update vote extensions signed by + /// a non-validator are rejected + #[test] + fn test_valset_upd_must_be_signed_by_validator() { + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let (protocol_key, validator_addr) = { + let bertha_key = wallet::defaults::bertha_keypair(); + let bertha_addr = wallet::defaults::bertha_address(); + (bertha_key, bertha_addr) + }; + let ethereum_events = ethereum_events::Vext::empty( + FIRST_HEIGHT_WITH_VEXTS, + validator_addr.clone(), + ) + .sign(&protocol_key); + let validator_set_update = Some( + validator_set_update::Vext { + // TODO: get voting powers from storage, associated with eth + // addrs + voting_powers: std::collections::HashMap::new(), + validator_addr, + epoch: Epoch(1), + } + .sign(&protocol_key), + ); + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } } From 0559c3e75c661249d8b053db744db1a4e70ea7e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 Aug 2022 14:02:27 +0100 Subject: [PATCH 0421/1995] Rename test --- .../lib/node/ledger/shell/vote_extensions/ethereum_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 0ac8e891ff..5c089a7da1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -404,7 +404,7 @@ mod test_vote_extensions { /// should pass even if the epoch changed resulting in a /// change to the validator set. #[test] - fn test_validate_vote_extensions() { + fn test_validate_eth_events_vexts() { let (mut shell, _, _) = setup(); let signing_key = shell.mode.get_protocol_key().expect("Test failed").clone(); From 8b0d2403dcd85ef74675281c9ca87b3d7ce24e8b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sat, 20 Aug 2022 14:25:38 +0100 Subject: [PATCH 0422/1995] Test validation of validator set update vexts --- .../vote_extensions/validator_set_update.rs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 44a9798944..1c4e351431 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -221,6 +221,9 @@ mod test_vote_extensions { use std::default::Default; use borsh::BorshSerialize; + use namada::ledger::pos; + use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::types::key::RefTo; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, @@ -228,7 +231,9 @@ mod test_vote_extensions { use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tower_abci::request; + use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; const FIRST_HEIGHT_WITH_VEXTS: BlockHeight = BlockHeight(1); @@ -320,4 +325,70 @@ mod test_vote_extensions { i32::from(VerifyStatus::Reject) ); } + + /// Test the validation of a validator set update emitted for + /// some epoch `E`. The test should pass even if the epoch + /// changed to some epoch `E': E' > E`, resulting in a + /// change to the validator set. + #[test] + fn test_validate_valset_upd_vexts() { + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let protocol_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + let validator_addr = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let vote_ext = validator_set_update::Vext { + // TODO: get voting powers from storage, associated with eth + // addrs + voting_powers: std::collections::HashMap::new(), + validator_addr, + epoch: Epoch(1), + } + .sign(&protocol_key); + + // validators from the current epoch sign over validator + // set of the next epoch + assert_eq!(shell.storage.get_current_epoch().0.0, 0); + + // remove all validators of the next epoch + let mut current_validators = shell.storage.read_validator_set(); + current_validators.data.insert( + 1, + Some(pos::types::ValidatorSet { + active: Default::default(), + inactive: Default::default(), + }), + ); + shell.storage.write_validator_set(¤t_validators); + // we advance forward to the next epoch + let mut req = FinalizeBlock::default(); + req.header.time = namada::types::time::DateTimeUtc::now(); + shell.storage.last_height = BlockHeight(11); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + assert_eq!(shell.storage.get_current_epoch().0.0, 1); + assert!( + shell + .storage + .get_validator_from_protocol_pk(&protocol_key.ref_to(), None) + .is_err() + ); + let prev_epoch = shell.storage.get_current_epoch().0 - 1; + assert!( + shell + .shell + .storage + .get_validator_from_protocol_pk( + &protocol_key.ref_to(), + Some(prev_epoch) + ) + .is_ok() + ); + + assert!(shell.validate_valset_upd_vext(vote_ext, prev_epoch + 1)); + } } From 7134c73150d2b3a570a9ed918ddf9c1869d4b591 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 09:03:02 +0100 Subject: [PATCH 0423/1995] Fix Ethereum events vote extensions logic in ProcessProposal --- apps/src/lib/node/ledger/shell/process_proposal.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 051f244348..8aa725b468 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -44,27 +44,27 @@ where "Received block proposal", ); // the number of vote extension digests included in the block proposal - let mut vote_ext_digest_num = 0; + let mut eth_ev_digest_num = 0; let tx_results: Vec = req .txs .iter() .map(|tx_bytes| { ExecTxResult::from( - self.process_single_tx(tx_bytes, &mut vote_ext_digest_num), + self.process_single_tx(tx_bytes, &mut eth_ev_digest_num), ) }) .collect(); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. - let too_many_vext_digests = vote_ext_digest_num > 1; - if too_many_vext_digests { + let invalid_num_of_eth_ev_digests = eth_ev_digest_num != 1; + if invalid_num_of_eth_ev_digests { tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, hash = ?hex::encode(&req.hash), - vote_ext_digest_num, - "Found too many vote extension transactions, proposed block \ + eth_ev_digest_num, + "Found invalid number of Ethereum events vote extension digests, proposed block \ will be rejected" ); } @@ -88,7 +88,7 @@ where ); } - let status = if too_many_vext_digests || invalid_txs { + let status = if invalid_num_of_eth_ev_digests || invalid_txs { ProposalStatus::Reject } else { ProposalStatus::Accept From a21ee071f1ab938f5cf8b22ebad84a97d9571ff6 Mon Sep 17 00:00:00 2001 From: leontiad Date: Mon, 27 Jun 2022 10:00:14 +0200 Subject: [PATCH 0424/1995] wallet: increase keys encryption password iterations --- apps/src/lib/wallet/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 1c521e7515..0f1f43189e 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -235,6 +235,6 @@ fn encryption_salt() -> kdf::Salt { /// Make encryption secret key from a password. fn encryption_key(salt: &kdf::Salt, password: String) -> kdf::SecretKey { kdf::Password::from_slice(password.as_bytes()) - .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 16, 32)) + .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 17, 32)) .expect("Generation of encryption secret key shouldn't fail") } From c02819179914ac6e347a115d8ea0edad0de3f564 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 0425/1995] changelog: add #1173 --- .changelog/unreleased/improvements/1168-pbkdf-iterations.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1168-pbkdf-iterations.md diff --git a/.changelog/unreleased/improvements/1168-pbkdf-iterations.md b/.changelog/unreleased/improvements/1168-pbkdf-iterations.md new file mode 100644 index 0000000000..417e0f8af8 --- /dev/null +++ b/.changelog/unreleased/improvements/1168-pbkdf-iterations.md @@ -0,0 +1,2 @@ +- Wallet: Increase the number of iterations used for keys encryption to the + recommended value. ([#1168](https://github.com/anoma/anoma/issues/1168)) \ No newline at end of file From 803ee950287b17e32cf8a9add5f67b78477f69c4 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 20 May 2022 12:37:32 +0200 Subject: [PATCH 0426/1995] update ibc-rs to v0.14.0 --- Cargo.lock | 16 +- apps/Cargo.toml | 4 +- shared/Cargo.toml | 8 +- shared/src/ledger/ibc/handler.rs | 187 +++++++++------ shared/src/ledger/ibc/storage.rs | 14 +- shared/src/ledger/ibc/vp/channel.rs | 73 ++++-- shared/src/ledger/ibc/vp/mod.rs | 40 ++-- shared/src/ledger/ibc/vp/packet.rs | 44 ++-- shared/src/ledger/ibc/vp/port.rs | 22 +- shared/src/ledger/ibc/vp/sequence.rs | 10 +- shared/src/types/ibc/data.rs | 2 +- tests/src/vm_host_env/ibc.rs | 18 +- tests/src/vm_host_env/mod.rs | 4 +- wasm/tx_template/Cargo.lock | 297 +----------------------- wasm/vp_template/Cargo.lock | 297 +----------------------- wasm/wasm_source/Cargo.lock | 297 +----------------------- wasm_for_tests/wasm_source/Cargo.lock | 313 +------------------------- 17 files changed, 299 insertions(+), 1347 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..48267dab30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2730,8 +2730,8 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes 1.1.0", "derive_more", @@ -2757,22 +2757,22 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ + "base64 0.13.0", "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", "tendermint-proto", - "tonic", ] [[package]] name = "ics23" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", "bytes 1.1.0", @@ -6289,7 +6289,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=yuji/ics23_0.7#b0ca0dc2fbb46b0e38da4b1eaf70b5ef27538554" dependencies = [ "blake2b-rs", "borsh", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..b2f0319e07 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -100,7 +100,7 @@ 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"]} +sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/ics23_0.7", features = ["borsh"]} # sysinfo with disabled multithread feature sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" @@ -117,7 +117,7 @@ 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"} -tracing = "0.1.30" +tracing = "0.1.34" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 53ab95f15c..f8a1149917 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -64,9 +64,9 @@ 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} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} -ics23 = "0.6.7" +ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false} +ics23 = "0.7.0" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} @@ -84,7 +84,7 @@ 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"]} +sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/ics23_0.7", 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", features = ["secp256k1"]} diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index bf45759535..f9661ce289 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -37,15 +37,15 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; +use crate::ibc::core::ics03_connection::version::Version as ConnVersion; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, Attributes as ChannelAttributes, - CloseConfirm as ChanCloseConfirm, CloseInit as ChanCloseInit, - OpenAck as ChanOpenAck, OpenConfirm as ChanOpenConfirm, - OpenInit as ChanOpenInit, OpenTry as ChanOpenTry, SendPacket, - TimeoutPacket, WriteAcknowledgement, + AcknowledgePacket, CloseConfirm as ChanCloseConfirm, + CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, + OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, + OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, }; use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; @@ -406,8 +406,7 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = - port_channel_id(msg.port_id.clone(), channel_id.clone()); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -428,8 +427,7 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = - port_channel_id(msg.port_id.clone(), channel_id.clone()); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -447,7 +445,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenAck fn ack_channel(&self, msg: &MsgChannelOpenAck) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -457,15 +455,23 @@ pub trait IbcActions { })?; let mut channel = ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel - .set_counterparty_channel_id(msg.counterparty_channel_id.clone()); + channel.set_counterparty_channel_id(msg.counterparty_channel_id); open_channel(&mut channel); 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(); + let conn_id = channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id, + )) + })?; + let counterparty = channel.counterparty(); + let event = make_open_ack_channel_event(msg, conn_id, counterparty) + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -474,7 +480,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenConfirm fn confirm_channel(&self, msg: &MsgChannelOpenConfirm) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -490,7 +496,16 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_open_confirm_channel_event(msg).try_into().unwrap(); + let conn_id = channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id, + )) + })?; + let counterparty = channel.counterparty(); + let event = make_open_confirm_channel_event(msg, conn_id, counterparty) + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -499,7 +514,7 @@ pub trait IbcActions { /// Close the channel for ChannelCloseInit fn close_init_channel(&self, msg: &MsgChannelCloseInit) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -515,7 +530,16 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_init_channel_event(msg).try_into().unwrap(); + let conn_id = channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id, + )) + })?; + let counterparty = channel.counterparty(); + let event = make_close_init_channel_event(msg, conn_id, counterparty) + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -527,7 +551,7 @@ pub trait IbcActions { msg: &MsgChannelCloseConfirm, ) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -543,7 +567,17 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_confirm_channel_event(msg).try_into().unwrap(); + let conn_id = channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id, + )) + })?; + let counterparty = channel.counterparty(); + let event = + make_close_confirm_channel_event(msg, conn_id, counterparty) + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -574,12 +608,11 @@ pub trait IbcActions { let packet = Packet { sequence, source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id.clone(), + source_channel: port_channel_id.channel_id, destination_port: counterparty.port_id.clone(), - destination_channel: counterparty + destination_channel: *counterparty .channel_id() - .expect("the counterparty channel should exist") - .clone(), + .expect("the counterparty channel should exist"), data, timeout_height, timeout_timestamp, @@ -630,7 +663,7 @@ pub trait IbcActions { // increment the next sequence receive let port_channel_id = port_channel_id( msg.packet.destination_port.clone(), - msg.packet.destination_channel.clone(), + msg.packet.destination_channel, ); let seq_key = storage::next_sequence_recv_key(&port_channel_id); self.get_and_inc_sequence(&seq_key)?; @@ -676,7 +709,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel.clone(), + msg.packet.source_channel, ); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { @@ -719,7 +752,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel.clone(), + msg.packet.source_channel, ); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { @@ -864,10 +897,8 @@ pub trait IbcActions { } // send a packet - let port_channel_id = port_channel_id( - msg.source_port.clone(), - msg.source_channel.clone(), - ); + let port_channel_id = + port_channel_id(msg.source_port.clone(), msg.source_channel); let packet_data = serde_json::to_vec(&data) .expect("encoding the packet data shouldn't fail"); self.send_packet( @@ -1031,7 +1062,9 @@ pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { ConnState::Init, msg.client_id.clone(), msg.counterparty.clone(), - vec![msg.version.clone()], + msg.version + .clone() + .map_or_else(|| vec![ConnVersion::default()], |v| vec![v]), msg.delay_period, ) } @@ -1097,12 +1130,11 @@ pub fn packet_from_message( Packet { sequence, source_port: msg.source_port.clone(), - source_channel: msg.source_channel.clone(), + source_channel: msg.source_channel, destination_port: counterparty.port_id.clone(), - destination_channel: counterparty + destination_channel: *counterparty .channel_id() - .expect("the counterparty channel should exist") - .clone(), + .expect("the counterparty channel should exist"), data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) .expect("encoding the packet data shouldn't fail"), timeout_height: msg.timeout_height, @@ -1196,7 +1228,7 @@ pub fn make_open_init_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - IbcEvent::OpenInitConnection(ConnOpenInit::from(attributes)) + ConnOpenInit::from(attributes).into() } /// Makes OpenTryConnection event @@ -1211,7 +1243,7 @@ pub fn make_open_try_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - IbcEvent::OpenTryConnection(ConnOpenTry::from(attributes)) + ConnOpenTry::from(attributes).into() } /// Makes OpenAckConnection event @@ -1223,7 +1255,7 @@ pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { ), ..Default::default() }; - IbcEvent::OpenAckConnection(ConnOpenAck::from(attributes)) + ConnOpenAck::from(attributes).into() } /// Makes OpenConfirmConnection event @@ -1234,7 +1266,7 @@ pub fn make_open_confirm_connection_event( connection_id: Some(msg.connection_id.clone()), ..Default::default() }; - IbcEvent::OpenConfirmConnection(ConnOpenConfirm::from(attributes)) + ConnOpenConfirm::from(attributes).into() } /// Makes OpenInitChannel event @@ -1246,9 +1278,10 @@ pub fn make_open_init_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChannelAttributes { + let attributes = ChanOpenInit { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(channel_id.clone()), + channel_id: Some(*channel_id), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1256,9 +1289,8 @@ pub fn make_open_init_channel_event( .counterparty() .channel_id() .cloned(), - ..Default::default() }; - IbcEvent::OpenInitChannel(ChanOpenInit::from(attributes)) + attributes.into() } /// Makes OpenTryChannel event @@ -1270,9 +1302,10 @@ pub fn make_open_try_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChannelAttributes { + let attributes = ChanOpenTry { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(channel_id.clone()), + channel_id: Some(*channel_id), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1280,54 +1313,76 @@ pub fn make_open_try_channel_event( .counterparty() .channel_id() .cloned(), - ..Default::default() }; - IbcEvent::OpenTryChannel(ChanOpenTry::from(attributes)) + attributes.into() } /// Makes OpenAckChannel event -pub fn make_open_ack_channel_event(msg: &MsgChannelOpenAck) -> IbcEvent { - let attributes = ChannelAttributes { +pub fn make_open_ack_channel_event( + msg: &MsgChannelOpenAck, + conn_id: &ConnectionId, + counterparty: &ChanCounterparty, +) -> IbcEvent { + let attributes = ChanOpenAck { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - counterparty_channel_id: Some(msg.counterparty_channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + counterparty_channel_id: Some(msg.counterparty_channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), }; - IbcEvent::OpenAckChannel(ChanOpenAck::from(attributes)) + attributes.into() } /// Makes OpenConfirmChannel event pub fn make_open_confirm_channel_event( msg: &MsgChannelOpenConfirm, + conn_id: &ConnectionId, + counterparty: &ChanCounterparty, ) -> IbcEvent { - let attributes = ChannelAttributes { + let attributes = ChanOpenConfirm { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::OpenConfirmChannel(ChanOpenConfirm::from(attributes)) + attributes.into() } /// Makes CloseInitChannel event -pub fn make_close_init_channel_event(msg: &MsgChannelCloseInit) -> IbcEvent { - let attributes = ChannelAttributes { +pub fn make_close_init_channel_event( + msg: &MsgChannelCloseInit, + conn_id: &ConnectionId, + counterparty: &ChanCounterparty, +) -> IbcEvent { + let attributes = ChanCloseInit { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: msg.channel_id, + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::CloseInitChannel(ChanCloseInit::from(attributes)) + attributes.into() } /// Makes CloseConfirmChannel event pub fn make_close_confirm_channel_event( msg: &MsgChannelCloseConfirm, + conn_id: &ConnectionId, + counterparty: &ChanCounterparty, ) -> IbcEvent { - let attributes = ChannelAttributes { + let attributes = ChanCloseConfirm { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id.clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::CloseConfirmChannel(ChanCloseConfirm::from(attributes)) + attributes.into() } /// Makes SendPacket event diff --git a/shared/src/ledger/ibc/storage.rs b/shared/src/ledger/ibc/storage.rs index 85e4011c5b..51cfc8890e 100644 --- a/shared/src/ledger/ibc/storage.rs +++ b/shared/src/ledger/ibc/storage.rs @@ -188,7 +188,7 @@ pub fn connection_key(conn_id: &ConnectionId) -> Key { pub fn channel_key(port_channel_id: &PortChannelId) -> Key { let path = Path::ChannelEnds(ChannelEndsPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )); ibc_key(path.to_string()) .expect("Creating a key for the channel shouldn't fail") @@ -211,7 +211,7 @@ pub fn capability_key(index: u64) -> Key { pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqSends(SeqSendsPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceSend shouldn't fail") @@ -221,7 +221,7 @@ pub fn next_sequence_send_key(port_channel_id: &PortChannelId) -> Key { pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqRecvs(SeqRecvsPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceRecv shouldn't fail") @@ -231,7 +231,7 @@ pub fn next_sequence_recv_key(port_channel_id: &PortChannelId) -> Key { pub fn next_sequence_ack_key(port_channel_id: &PortChannelId) -> Key { let path = Path::SeqAcks(SeqAcksPath( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )); ibc_key(path.to_string()) .expect("Creating a key for nextSequenceAck shouldn't fail") @@ -245,7 +245,7 @@ pub fn commitment_key( ) -> Key { let path = Path::Commitments(CommitmentsPath { port_id: port_id.clone(), - channel_id: channel_id.clone(), + channel_id: *channel_id, sequence, }); ibc_key(path.to_string()) @@ -260,7 +260,7 @@ pub fn receipt_key( ) -> Key { let path = Path::Receipts(ReceiptsPath { port_id: port_id.clone(), - channel_id: channel_id.clone(), + channel_id: *channel_id, sequence, }); ibc_key(path.to_string()) @@ -275,7 +275,7 @@ pub fn ack_key( ) -> Key { let path = Path::Acks(AcksPath { port_id: port_id.clone(), - channel_id: channel_id.clone(), + channel_id: *channel_id, sequence, }); ibc_key(path.to_string()) diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 354899f31d..780ee1b80a 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -37,11 +37,14 @@ use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConf use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; -use crate::ibc::core::ics05_port::capabilities::Capability; +use crate::ibc::core::ics05_port::capabilities::{ + Capability, ChannelCapability, +}; use crate::ibc::core::ics05_port::context::PortReader; use crate::ibc::core::ics24_host::identifier::{ ChannelId, ClientId, ConnectionId, PortChannelId, PortId, }; +use crate::ibc::core::ics26_routing::context::ModuleId; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; use crate::ibc::timestamp::Timestamp; @@ -134,7 +137,7 @@ where let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( @@ -236,6 +239,13 @@ where tx_data: &[u8], ) -> Result<()> { let prev_channel = self.channel_end_pre(port_channel_id)?; + let conn_id = channel.connection_hops().get(0).ok_or_else(|| { + Error::InvalidChannel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id, + )) + })?; + let counterparty = channel.counterparty(); match channel.state() { State::Open => match prev_channel.state() { State::Init => { @@ -246,7 +256,11 @@ where channel, &msg, )?; - let event = make_open_ack_channel_event(&msg); + let event = make_open_ack_channel_event( + &msg, + conn_id, + counterparty, + ); self.check_emitted_event(event) .map_err(|e| Error::IbcEvent(e.to_string())) } @@ -258,7 +272,11 @@ where channel, &msg, )?; - let event = make_open_confirm_channel_event(&msg); + let event = make_open_confirm_channel_event( + &msg, + conn_id, + counterparty, + ); self.check_emitted_event(event) .map_err(|e| Error::IbcEvent(e.to_string())) } @@ -301,7 +319,11 @@ where Ics26Envelope::Ics4ChannelMsg( ChannelMsg::ChannelCloseInit(msg), ) => { - let event = make_close_init_channel_event(&msg); + let event = make_close_init_channel_event( + &msg, + conn_id, + counterparty, + ); self.check_emitted_event(event) .map_err(|e| Error::IbcEvent(e.to_string())) } @@ -313,7 +335,11 @@ where channel, &msg, )?; - let event = make_close_confirm_channel_event(&msg); + let event = make_close_confirm_channel_event( + &msg, + conn_id, + counterparty, + ); self.check_emitted_event(event) .map_err(|e| Error::IbcEvent(e.to_string())) } @@ -399,7 +425,7 @@ where } let expected_my_side = Counterparty::new( port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id.clone()), + Some(port_channel_id.channel_id), ); self.verify_proofs( msg.proofs.height(), @@ -418,7 +444,7 @@ where ) -> Result<()> { let expected_my_side = Counterparty::new( port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id.clone()), + Some(port_channel_id.channel_id), ); self.verify_proofs( msg.proofs.height(), @@ -437,7 +463,7 @@ where ) -> Result<()> { let expected_my_side = Counterparty::new( port_channel_id.port_id.clone(), - Some(port_channel_id.channel_id.clone()), + Some(port_channel_id.channel_id), ); self.verify_proofs( msg.proofs.height(), @@ -668,7 +694,7 @@ where ) -> Ics04Result { let port_channel_id = PortChannelId { port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1.clone(), + channel_id: port_channel_id.1, }; let key = channel_key(&port_channel_id); match self.ctx.read_post(&key) { @@ -751,12 +777,13 @@ where fn authenticated_capability( &self, port_id: &PortId, - ) -> Ics04Result { - let (_, cap) = self + ) -> Ics04Result { + let (_, port_cap) = self .lookup_module_by_port(port_id) .map_err(|_| Ics04Error::no_port_capability(port_id.clone()))?; - if self.authenticate(port_id.clone(), &cap) { - Ok(cap) + if self.authenticate(port_id.clone(), &port_cap) { + let cap: Capability = port_cap.into(); + Ok(cap.into()) } else { Err(Ics04Error::invalid_port_capability()) } @@ -768,7 +795,7 @@ where ) -> Ics04Result { let port_channel_id = PortChannelId { port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1.clone(), + channel_id: port_channel_id.1, }; let key = next_sequence_send_key(&port_channel_id); self.get_sequence(&key).map_err(|_| { @@ -785,7 +812,7 @@ where ) -> Ics04Result { let port_channel_id = PortChannelId { port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1.clone(), + channel_id: port_channel_id.1, }; let key = next_sequence_recv_key(&port_channel_id); self.get_sequence(&key).map_err(|_| { @@ -802,7 +829,7 @@ where ) -> Ics04Result { let port_channel_id = PortChannelId { port_id: port_channel_id.0.clone(), - channel_id: port_channel_id.1.clone(), + channel_id: port_channel_id.1, }; let key = next_sequence_ack_key(&port_channel_id); self.get_sequence(&key).map_err(|_| { @@ -929,6 +956,18 @@ where Err(_) => Duration::default(), } } + + fn lookup_module_by_channel( + &self, + _channel_id: &ChannelId, + port_id: &PortId, + ) -> Ics04Result<(ModuleId, ChannelCapability)> { + let (module_id, port_cap) = self + .lookup_module_by_port(port_id) + .map_err(Ics04Error::ics05_port)?; + let cap: Capability = port_cap.into(); + Ok((module_id, cap.into())) + } } impl From for Error { diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b6bd15e43b..1c31cf58f1 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -454,7 +454,7 @@ mod tests { } fn get_channel_id() -> ChannelId { - ChannelId::from_str("test_channel").unwrap() + ChannelId::from_str("channel-42").unwrap() } fn get_connection(conn_state: ConnState) -> ConnectionEnd { @@ -494,9 +494,8 @@ mod tests { fn get_channel_counterparty() -> ChanCounterparty { let counterpart_port_id = PortId::from_str("counterpart_test_port") .expect("Creating a port ID failed"); - let counterpart_channel_id = - ChannelId::from_str("counterpart_test_channel") - .expect("Creating a channel ID failed"); + let counterpart_channel_id = ChannelId::from_str("channel-0") + .expect("Creating a channel ID failed"); ChanCounterparty::new(counterpart_port_id, Some(counterpart_channel_id)) } @@ -696,7 +695,7 @@ mod tests { let msg = MsgConnectionOpenInit { client_id: get_client_id(), counterparty: get_conn_counterparty(), - version: ConnVersion::default(), + version: None, delay_period: Duration::new(100, 0), signer: Signer::new("account0"), }; @@ -745,7 +744,7 @@ mod tests { let msg = MsgConnectionOpenInit { client_id: get_client_id(), counterparty: get_conn_counterparty(), - version: ConnVersion::default(), + version: None, delay_period: Duration::new(100, 0), signer: Signer::new("account0"), }; @@ -1154,10 +1153,9 @@ mod tests { let msg = MsgChannelOpenAck { port_id: get_port_id(), channel_id: get_channel_id(), - counterparty_channel_id: get_channel_counterparty() + counterparty_channel_id: *get_channel_counterparty() .channel_id() - .unwrap() - .clone(), + .unwrap(), counterparty_version: ChanVersion::ics20(), proofs, signer: Signer::new("account0"), @@ -1166,8 +1164,13 @@ mod tests { // update the channel to Open let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); + let conn_id = channel + .connection_hops() + .get(0) + .expect("connection should exist"); + let counterparty = channel.counterparty(); write_log.write(&channel_key, bytes).expect("write failed"); - let event = make_open_ack_channel_event(&msg); + let event = make_open_ack_channel_event(&msg, conn_id, counterparty); write_log.set_ibc_event(event.try_into().unwrap()); let tx_code = vec![]; @@ -1240,7 +1243,14 @@ mod tests { let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); write_log.write(&channel_key, bytes).expect("write failed"); - let event = make_open_confirm_channel_event(&msg); + + let conn_id = channel + .connection_hops() + .get(0) + .expect("connection should exist"); + let counterparty = channel.counterparty(); + let event = + make_open_confirm_channel_event(&msg, conn_id, counterparty); write_log.set_ibc_event(event.try_into().unwrap()); let tx_code = vec![]; @@ -1436,7 +1446,7 @@ mod tests { let packet = Packet { sequence, source_port: counterparty.port_id().clone(), - source_channel: counterparty.channel_id().unwrap().clone(), + source_channel: *counterparty.channel_id().unwrap(), destination_port: get_port_id(), destination_channel: get_channel_id(), data: vec![0], @@ -1502,7 +1512,7 @@ mod tests { source_port: get_port_id(), source_channel: get_channel_id(), destination_port: counterparty.port_id().clone(), - destination_channel: counterparty.channel_id().unwrap().clone(), + destination_channel: *counterparty.channel_id().unwrap(), data: vec![0], timeout_height: Height::new(0, 100), timeout_timestamp, @@ -1536,7 +1546,7 @@ mod tests { .unwrap(); let msg = MsgAcknowledgement { packet, - acknowledgement: ack, + acknowledgement: ack.into(), proofs, signer: Signer::new("account0"), }; @@ -1675,7 +1685,7 @@ mod tests { let packet = Packet { sequence: Sequence::from(1), source_port: counterparty.port_id().clone(), - source_channel: counterparty.channel_id().unwrap().clone(), + source_channel: *counterparty.channel_id().unwrap(), destination_port: get_port_id(), destination_channel: get_channel_id(), data: vec![0], diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 9d1b344e98..21ffc7f73b 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -20,7 +20,9 @@ use crate::ibc::core::ics04_channel::handler::verify::{ verify_packet_acknowledgement_proofs, verify_packet_receipt_absence, verify_packet_recv_proofs, }; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; +use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ + Acknowledgement, MsgAcknowledgement, +}; use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; @@ -92,10 +94,7 @@ where let msg = ibc_msg.msg_transfer()?; // make a packet let channel = self - .channel_end(&( - commitment_key.0.clone(), - commitment_key.1.clone(), - )) + .channel_end(&(commitment_key.0.clone(), commitment_key.1)) .map_err(|e| Error::InvalidChannel(e.to_string()))?; let packet = packet_from_message( &msg, @@ -125,10 +124,7 @@ where StateChange::Deleted => { // check the channel state let channel = self - .channel_end(&( - commitment_key.0.clone(), - commitment_key.1.clone(), - )) + .channel_end(&(commitment_key.0.clone(), commitment_key.1)) .map_err(|_| { Error::InvalidChannel(format!( "The channel doesn't exist: Port {}, Channel {}", @@ -206,7 +202,7 @@ where // The receipt should have been stored self.get_packet_receipt(&( ack_key.0.clone(), - ack_key.1.clone(), + ack_key.1, ack_key.2, )) .map_err(|_| { @@ -273,7 +269,7 @@ where })?; let port_channel_id = PortChannelId { port_id: port_channel_seq_id.0.clone(), - channel_id: port_channel_seq_id.1.clone(), + channel_id: port_channel_seq_id.1, }; self.verify_recv_proof( &port_channel_id, @@ -303,7 +299,7 @@ where let port_channel_id = PortChannelId { port_id: port_channel_seq_id.0.clone(), - channel_id: port_channel_seq_id.1.clone(), + channel_id: port_channel_seq_id.1, }; self.verify_ack_proof( &port_channel_id, @@ -333,7 +329,7 @@ where } PortChannelId { port_id: packet.source_port.clone(), - channel_id: packet.source_channel.clone(), + channel_id: packet.source_channel, } } Phase::Recv => { @@ -347,7 +343,7 @@ where } PortChannelId { port_id: packet.destination_port.clone(), - channel_id: packet.destination_channel.clone(), + channel_id: packet.destination_channel, } } }; @@ -364,7 +360,7 @@ where let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( @@ -392,11 +388,11 @@ where let counterparty = match phase { Phase::Send | Phase::Ack => Counterparty::new( packet.destination_port.clone(), - Some(packet.destination_channel.clone()), + Some(packet.destination_channel), ), Phase::Recv => Counterparty::new( packet.source_port.clone(), - Some(packet.source_channel.clone()), + Some(packet.source_channel), ), }; if !channel.counterparty_matches(&counterparty) { @@ -463,7 +459,7 @@ where let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( @@ -484,13 +480,13 @@ where port_channel_id: &PortChannelId, height: Height, packet: &Packet, - ack: Vec, + ack: Acknowledgement, proofs: &Proofs, ) -> Result<()> { let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( @@ -552,12 +548,12 @@ where // the counterparty should be equal to that of the channel let port_channel_id = PortChannelId { port_id: packet.source_port.clone(), - channel_id: packet.source_channel.clone(), + channel_id: packet.source_channel, }; let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( @@ -567,7 +563,7 @@ where })?; let counterparty = Counterparty::new( packet.destination_port.clone(), - Some(packet.destination_channel.clone()), + Some(packet.destination_channel), ); if !channel.counterparty_matches(&counterparty) { return Err(Error::InvalidPacket(format!( @@ -589,7 +585,7 @@ where // check that the counterpart channel has been closed let expected_my_side = Counterparty::new( packet.source_port.clone(), - Some(packet.source_channel.clone()), + Some(packet.source_channel), ); let counterparty = connection.counterparty(); let conn_id = diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs index 2819cdeab5..da5ef4e25f 100644 --- a/shared/src/ledger/ibc/vp/port.rs +++ b/shared/src/ledger/ibc/vp/port.rs @@ -9,10 +9,13 @@ use super::super::storage::{ }; use super::{Ibc, StateChange}; use crate::ibc::core::ics04_channel::context::ChannelReader; -use crate::ibc::core::ics05_port::capabilities::{Capability, CapabilityName}; +use crate::ibc::core::ics05_port::capabilities::{ + Capability, CapabilityName, PortCapability, +}; 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::ibc::core::ics26_routing::context::ModuleId; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; @@ -35,6 +38,8 @@ pub type Result = std::result::Result; /// ConnectionReader result type Ics05Result = core::result::Result; +const MODULE_ID: &str = "ledger"; + impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, @@ -84,14 +89,13 @@ where let cap = capability(key)?; let port_id = self.get_port_by_capability(&cap)?; match self.lookup_module_by_port(&port_id) { - Ok((_, c)) if c == cap => Ok(()), + Ok((_, c)) if c == cap.into() => Ok(()), Ok(_) => Err(Error::InvalidPort(format!( "The port is invalid: ID {}", port_id ))), Err(_) => Err(Error::NoCapability(format!( - "The capability is not mapped: Index {}, Port {}", - cap.index(), + "The capability is not mapped: Port {}", port_id ))), } @@ -154,12 +158,10 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type ModuleId = (); - fn lookup_module_by_port( &self, port_id: &PortId, - ) -> Ics05Result<(Self::ModuleId, Capability)> { + ) -> Ics05Result<(ModuleId, PortCapability)> { let key = port_key(port_id); match self.ctx.read_post(&key) { Ok(Some(value)) => { @@ -167,7 +169,9 @@ where .try_into() .map_err(|_| Ics05Error::implementation_specific())?; let index = u64::from_be_bytes(index); - Ok(((), Capability::from(index))) + let module_id = ModuleId::new(MODULE_ID.into()) + .expect("Creating the module ID shouldn't fail"); + Ok((module_id, Capability::from(index).into())) } Ok(None) => Err(Ics05Error::unknown_port(port_id.clone())), Err(_) => Err(Ics05Error::implementation_specific()), @@ -184,7 +188,7 @@ where fn get_capability(&self, name: &CapabilityName) -> Ics05Result { let port_id = get_port_id(name)?; let (_, capability) = self.lookup_module_by_port(&port_id)?; - Ok(capability) + Ok(capability.into()) } fn authenticate_capability( diff --git a/shared/src/ledger/ibc/vp/sequence.rs b/shared/src/ledger/ibc/vp/sequence.rs index 166de408c6..0e751ea0de 100644 --- a/shared/src/ledger/ibc/vp/sequence.rs +++ b/shared/src/ledger/ibc/vp/sequence.rs @@ -53,7 +53,7 @@ where let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|e| Error::InvalidChannel(e.to_string()))?; let next_seq_pre = self @@ -64,7 +64,7 @@ where let next_seq = self .get_next_sequence_send(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidSequence( @@ -116,7 +116,7 @@ where let next_seq = self .get_next_sequence_recv(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidSequence( @@ -174,7 +174,7 @@ where let next_seq = self .get_next_sequence_ack(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidSequence( @@ -218,7 +218,7 @@ where let channel = self .channel_end(&( port_channel_id.port_id.clone(), - port_channel_id.channel_id.clone(), + port_channel_id.channel_id, )) .map_err(|_| { Error::InvalidChannel(format!( diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 4a9cdd41c6..8d657dd628 100644 --- a/shared/src/types/ibc/data.rs +++ b/shared/src/types/ibc/data.rs @@ -3,7 +3,6 @@ use std::convert::TryFrom; use std::fmt::{self, Display, Formatter}; use prost::Message; -use prost_types::Any; use thiserror::Error; use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; @@ -32,6 +31,7 @@ use crate::ibc::core::ics04_channel::packet::Receipt; use crate::ibc::core::ics26_routing::error::Error as Ics26Error; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::downcast; +use crate::ibc_proto::google::protobuf::Any; use crate::ibc_proto::ibc::core::channel::v1::acknowledgement::Response; use crate::ibc_proto::ibc::core::channel::v1::Acknowledgement; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 13e7bd3882..31130127f5 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -326,7 +326,7 @@ pub fn prepare_opened_channel( writes.insert(key, bytes); // channel let channel_id = channel_id(0); - let port_channel_id = port_channel_id(port_id.clone(), channel_id.clone()); + let port_channel_id = port_channel_id(port_id.clone(), channel_id); let key = channel_key(&port_channel_id); let msg = msg_channel_open_init(port_id.clone(), conn_id.clone()); let mut channel = msg.channel; @@ -397,7 +397,7 @@ pub fn msg_connection_open_init(client_id: ClientId) -> MsgConnectionOpenInit { MsgConnectionOpenInit { client_id, counterparty: dummy_connection_counterparty(), - version: ConnVersion::default(), + version: None, delay_period: Duration::new(100, 0), signer: Signer::new("test"), } @@ -501,10 +501,9 @@ pub fn msg_channel_open_ack( MsgChannelOpenAck { port_id, channel_id, - counterparty_channel_id: dummy_channel_counterparty() + counterparty_channel_id: *dummy_channel_counterparty() .channel_id() - .unwrap() - .clone(), + .unwrap(), counterparty_version: ChanVersion::ics20(), proofs: dummy_proofs(), signer: Signer::new("test"), @@ -563,9 +562,8 @@ fn dummy_channel( pub fn dummy_channel_counterparty() -> ChanCounterparty { let counterpart_port_id = PortId::from_str("counterpart_test_port") .expect("Creating a port ID failed"); - let counterpart_channel_id = - ChannelId::from_str("counterpart_test_channel") - .expect("Creating a channel ID failed"); + let counterpart_channel_id = ChannelId::from_str("channel-42") + .expect("Creating a channel ID failed"); channel_counterparty(counterpart_port_id, counterpart_channel_id) } @@ -612,7 +610,7 @@ pub fn msg_packet_recv(packet: Packet) -> MsgRecvPacket { pub fn msg_packet_ack(packet: Packet) -> MsgAcknowledgement { MsgAcknowledgement { packet, - acknowledgement: vec![0], + acknowledgement: vec![0].into(), proofs: dummy_proofs(), signer: Signer::new("test"), } @@ -637,7 +635,7 @@ pub fn received_packet( Packet { sequence, source_port: counterparty.port_id().clone(), - source_channel: counterparty.channel_id().unwrap().clone(), + source_channel: *counterparty.channel_id().unwrap(), destination_port: port_id, destination_channel: channel_id, data: serde_json::to_vec(&data).unwrap(), diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index ce547520f7..1bd9cc6485 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -885,7 +885,7 @@ mod tests { .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 port_channel_id = ibc::port_channel_id(port_id, channel_id); let channel_key = ibc::channel_key(&port_channel_id).to_string(); tx_host_env::write_bytes( &channel_key, @@ -932,7 +932,7 @@ mod tests { .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 port_channel_id = ibc::port_channel_id(port_id, channel_id); let channel_key = ibc::channel_key(&port_channel_id).to_string(); let mut channel = msg.channel.clone(); ibc::open_channel(&mut channel); diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 4e85d68f2c..7f84960f8f 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -152,27 +152,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.52" @@ -1042,25 +1021,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.6.9", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1174,51 +1134,11 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "derive_more", - "flex-error", - "ibc-proto", - "ics23", - "num-traits", - "prost", - "prost-types", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.7", - "tracing", -] - -[[package]] -name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "prost", - "prost-types", - "serde", - "tendermint-proto", - "tonic", -] - [[package]] name = "ics23" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", "bytes", @@ -1480,28 +1400,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" -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", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -2445,28 +2343,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2476,7 +2358,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=yuji/ics23_0.7#b0ca0dc2fbb46b0e38da4b1eaf70b5ef27538554" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2755,82 +2637,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.5.8" @@ -2840,37 +2646,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util 0.6.9", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2883,43 +2658,11 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.0", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", @@ -2930,9 +2673,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -2949,16 +2692,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-subscriber" version = "0.3.9" @@ -2974,12 +2707,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - [[package]] name = "tx_template" version = "0.7.1" @@ -3086,16 +2813,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 25070a9319..bd74a94947 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -152,27 +152,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.52" @@ -1042,25 +1021,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.6.9", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1174,51 +1134,11 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "derive_more", - "flex-error", - "ibc-proto", - "ics23", - "num-traits", - "prost", - "prost-types", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.7", - "tracing", -] - -[[package]] -name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "prost", - "prost-types", - "serde", - "tendermint-proto", - "tonic", -] - [[package]] name = "ics23" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", "bytes", @@ -1480,28 +1400,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" -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", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -2445,28 +2343,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2476,7 +2358,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=yuji/ics23_0.7#b0ca0dc2fbb46b0e38da4b1eaf70b5ef27538554" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2755,82 +2637,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.5.8" @@ -2840,37 +2646,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util 0.6.9", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2883,43 +2658,11 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.0", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", @@ -2930,9 +2673,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -2949,16 +2692,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-subscriber" version = "0.3.9" @@ -2974,12 +2707,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - [[package]] name = "typenum" version = "1.15.0" @@ -3086,16 +2813,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 269535735e..e10a0e335a 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -152,27 +152,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.52" @@ -1042,25 +1021,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.6.9", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1174,51 +1134,11 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "derive_more", - "flex-error", - "ibc-proto", - "ics23", - "num-traits", - "prost", - "prost-types", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.7", - "tracing", -] - -[[package]] -name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "prost", - "prost-types", - "serde", - "tendermint-proto", - "tonic", -] - [[package]] name = "ics23" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", "bytes", @@ -1480,28 +1400,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" -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", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -2471,28 +2369,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2502,7 +2384,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=yuji/ics23_0.7#b0ca0dc2fbb46b0e38da4b1eaf70b5ef27538554" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2781,82 +2663,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.5.8" @@ -2866,37 +2672,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util 0.6.9", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2909,43 +2684,11 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.0", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", @@ -2956,9 +2699,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -2975,16 +2718,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-subscriber" version = "0.3.9" @@ -3000,12 +2733,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - [[package]] name = "typenum" version = "1.15.0" @@ -3101,16 +2828,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..26325f57bd 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -152,27 +152,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" -[[package]] -name = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" version = "0.1.53" @@ -984,17 +963,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", -] - [[package]] name = "gimli" version = "0.25.0" @@ -1043,25 +1011,6 @@ dependencies = [ "syn", ] -[[package]] -name = "h2" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util 0.7.1", - "tracing", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -1184,51 +1133,11 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "derive_more", - "flex-error", - "ibc-proto", - "ics23", - "num-traits", - "prost", - "prost-types", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.9", - "tracing", -] - -[[package]] -name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" -dependencies = [ - "bytes", - "prost", - "prost-types", - "serde", - "tendermint-proto", - "tonic", -] - [[package]] name = "ics23" -version = "0.6.7" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", "bytes", @@ -1490,29 +1399,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mio" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -2477,28 +2363,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "slab" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" - [[package]] name = "smallvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -2508,7 +2378,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=yuji/ics23_0.7#b0ca0dc2fbb46b0e38da4b1eaf70b5ef27538554" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2751,7 +2621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", "winapi", ] @@ -2787,82 +2657,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" -dependencies = [ - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-macros" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - [[package]] name = "toml" version = "0.5.8" @@ -2872,37 +2666,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tonic" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" -dependencies = [ - "async-stream", - "async-trait", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-timeout", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util 0.6.9", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - [[package]] name = "tonic-build" version = "0.6.2" @@ -2915,43 +2678,11 @@ dependencies = [ "syn", ] -[[package]] -name = "tower" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "pin-project-lite", - "rand", - "slab", - "tokio", - "tokio-util 0.7.1", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - [[package]] name = "tracing" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "log", @@ -2981,16 +2712,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-subscriber" version = "0.3.9" @@ -3006,12 +2727,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - [[package]] name = "typenum" version = "1.15.0" @@ -3107,28 +2822,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" 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" From 684a290ce62f515ae21be459861101b7176f180f Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 10 May 2022 12:02:54 +0200 Subject: [PATCH 0427/1995] fix serialization of commitment --- shared/src/ledger/ibc/handler.rs | 31 +++--- shared/src/ledger/ibc/vp/channel.rs | 30 +++--- shared/src/ledger/ibc/vp/mod.rs | 34 ++---- shared/src/ledger/ibc/vp/packet.rs | 11 +- shared/src/types/ibc/data.rs | 6 +- wasm/tx_template/Cargo.lock | 99 ++++++------------ wasm/vp_template/Cargo.lock | 99 ++++++------------ wasm/wasm_source/Cargo.lock | 99 ++++++------------ wasm_for_tests/wasm_source/Cargo.lock | 142 +++++++++++--------------- 9 files changed, 194 insertions(+), 357 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index f9661ce289..5274c53c74 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -2,7 +2,6 @@ use std::str::FromStr; -use prost::Message; use sha2::Digest; use thiserror::Error; @@ -41,6 +40,7 @@ use crate::ibc::core::ics03_connection::version::Version as ConnVersion; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::events::{ AcknowledgePacket, CloseConfirm as ChanCloseConfirm, CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, @@ -624,11 +624,7 @@ pub trait IbcActions { packet.sequence, ); let commitment = commitment(&packet); - let mut commitment_bytes = vec![]; - 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.into_vec()); let event = make_send_packet_event(packet).try_into().unwrap(); self.emit_ibc_event(event); @@ -658,7 +654,8 @@ pub trait IbcActions { msg.packet.sequence, ); let ack = PacketAck::default().encode_to_vec(); - self.write_ibc_data(&ack_key, ack.clone()); + let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); + self.write_ibc_data(&ack_key, ack_commitment); // increment the next sequence receive let port_channel_id = port_channel_id( @@ -1143,13 +1140,19 @@ pub fn packet_from_message( } /// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> String { - let input = format!( - "{:?},{:?},{:?}", - packet.timeout_timestamp, packet.timeout_height, packet.data, - ); - let r = sha2::Sha256::digest(input.as_bytes()); - format!("{:x}", r) +pub fn commitment(packet: &Packet) -> PacketCommitment { + let mut input = packet + .timeout_timestamp + .nanoseconds() + .to_be_bytes() + .to_vec(); + let revision_number = packet.timeout_height.revision_number.to_be_bytes(); + input.append(&mut revision_number.to_vec()); + let revision_height = packet.timeout_height.revision_height.to_be_bytes(); + input.append(&mut revision_height.to_vec()); + let data = sha2::Sha256::digest(&packet.data); + input.append(&mut data.to_vec()); + sha2::Sha256::digest(&input).to_vec().into() } /// Returns a counterparty of a connection diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 780ee1b80a..81544ae6e3 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -2,7 +2,6 @@ use core::time::Duration; -use prost::Message; use sha2::Digest; use thiserror::Error; @@ -28,6 +27,9 @@ use crate::ibc::core::ics03_connection::error::Error as Ics03Error; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty, State, }; +use crate::ibc::core::ics04_channel::commitment::{ + AcknowledgementCommitment, PacketCommitment, +}; use crate::ibc::core::ics04_channel::context::ChannelReader; use crate::ibc::core::ics04_channel::error::Error as Ics04Error; use crate::ibc::core::ics04_channel::handler::verify::verify_channel_proofs; @@ -54,7 +56,7 @@ use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::tendermint::Time; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{ - Error as IbcDataError, IbcMessage, PacketAck, PacketReceipt, + Error as IbcDataError, IbcMessage, PacketReceipt, }; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; @@ -618,15 +620,10 @@ where pub(super) fn get_packet_commitment_pre( &self, key: &(PortId, ChannelId, Sequence), - ) -> Result { + ) -> Result { let key = commitment_key(&key.0, &key.1, key.2); match self.ctx.read_pre(&key)? { - Some(value) => String::decode(&value[..]).map_err(|e| { - Error::InvalidPacketInfo(format!( - "Decoding the prior commitment failed: {}", - e - )) - }), + Some(value) => Ok(value.into()), None => Err(Error::InvalidPacketInfo(format!( "The prior commitment doesn't exist: Key {}", key @@ -843,11 +840,10 @@ where fn get_packet_commitment( &self, key: &(PortId, ChannelId, Sequence), - ) -> Ics04Result { + ) -> Ics04Result { let commitment_key = commitment_key(&key.0, &key.1, key.2); match self.ctx.read_post(&commitment_key) { - Ok(Some(value)) => String::decode(&value[..]) - .map_err(|_| Ics04Error::implementation_specific()), + Ok(Some(value)) => Ok(value.into()), Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), Err(_) => Err(Ics04Error::implementation_specific()), } @@ -865,22 +861,20 @@ where } } - // TODO should return Vec or Acknowledgment. fix in ibc-rs? fn get_packet_acknowledgement( &self, key: &(PortId, ChannelId, Sequence), - ) -> Ics04Result { + ) -> Ics04Result { let ack_key = ack_key(&key.0, &key.1, key.2); match self.ctx.read_post(&ack_key) { - Ok(Some(_)) => Ok(PacketAck::default().to_string()), + Ok(Some(value)) => Ok(value.into()), Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), Err(_) => Err(Ics04Error::implementation_specific()), } } - fn hash(&self, value: String) -> String { - let r = sha2::Sha256::digest(value.as_bytes()); - format!("{:x}", r) + fn hash(&self, value: Vec) -> Vec { + sha2::Sha256::digest(&value).to_vec() } fn host_height(&self) -> Height { diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 1c31cf58f1..a870202626 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -344,13 +344,12 @@ mod tests { use crate::ibc::Height; use crate::ibc_proto::cosmos::base::v1beta1::Coin; use prost::Message; - use sha2::Digest; - use crate::tendermint_proto::Protobuf; use crate::tendermint::time::Time as TmTime; + use crate::tendermint_proto::Protobuf; use super::get_dummy_header; use super::super::handler::{ - commitment_prefix, init_connection, make_create_client_event, + self, commitment_prefix, init_connection, make_create_client_event, make_open_ack_channel_event, make_open_ack_connection_event, make_open_confirm_channel_event, make_open_confirm_connection_event, make_open_init_channel_event, make_open_init_connection_event, @@ -532,15 +531,6 @@ mod tests { .expect("write failed"); } - fn hash(packet: &Packet) -> String { - let input = format!( - "{:?},{:?},{:?}", - packet.timeout_timestamp, packet.timeout_height, packet.data, - ); - let r = sha2::Sha256::digest(input.as_bytes()); - format!("{:x}", r) - } - #[test] fn test_create_client() { let (storage, mut write_log) = insert_init_states(); @@ -1383,14 +1373,10 @@ mod tests { let counterparty = get_channel_counterparty(); let packet = packet_from_message(&msg, sequence, &counterparty); // insert a commitment - let commitment = hash(&packet); - let mut commitment_bytes = vec![]; - commitment - .encode(&mut commitment_bytes) - .expect("encoding shouldn't fail"); + let commitment = handler::commitment(&packet); let key = commitment_key(&get_port_id(), &get_channel_id(), sequence); write_log - .write(&key, commitment_bytes) + .write(&key, commitment.into_vec()) .expect("write failed"); let tx_code = vec![]; @@ -1529,11 +1515,11 @@ mod tests { let bytes = channel.encode_vec().expect("encoding failed"); write_log.write(&channel_key, bytes).expect("write failed"); // insert a commitment - let commitment = hash(&packet); + let commitment = handler::commitment(&packet); let commitment_key = commitment_key(&get_port_id(), &get_channel_id(), sequence); write_log - .write(&commitment_key, commitment.as_bytes().to_vec()) + .write(&commitment_key, commitment.into_vec()) .expect("write failed"); write_log.commit_tx(); write_log.commit_block(&mut storage).expect("commit failed"); @@ -1620,18 +1606,14 @@ mod tests { let counterparty = get_channel_counterparty(); let packet = packet_from_message(&msg, sequence, &counterparty); // insert a commitment - let commitment = hash(&packet); + let commitment = handler::commitment(&packet); let commitment_key = commitment_key( &packet.source_port, &packet.source_channel, sequence, ); - let mut commitment_bytes = vec![]; - commitment - .encode(&mut commitment_bytes) - .expect("encoding shouldn't fail"); write_log - .write(&commitment_key, commitment_bytes) + .write(&commitment_key, commitment.into_vec()) .expect("write failed"); let event = make_send_packet_event(packet); write_log.set_ibc_event(event.try_into().unwrap()); diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 21ffc7f73b..7c0e6d3992 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -3,7 +3,7 @@ use thiserror::Error; use super::super::handler::{ - make_send_packet_event, make_timeout_event, packet_from_message, + self, make_send_packet_event, make_timeout_event, packet_from_message, }; use super::super::storage::{ port_channel_sequence_id, Error as IbcStorageError, @@ -13,6 +13,7 @@ use crate::ibc::core::ics02_client::height::Height; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty, Order, State, }; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::context::ChannelReader; use crate::ibc::core::ics04_channel::error::Error as Ics04Error; use crate::ibc::core::ics04_channel::handler::verify::{ @@ -434,13 +435,9 @@ where fn validate_packet_commitment( &self, packet: &Packet, - commitment: String, + commitment: PacketCommitment, ) -> Result<()> { - let input = format!( - "{:?},{:?},{:?}", - packet.timeout_timestamp, packet.timeout_height, packet.data, - ); - if commitment == self.hash(input) { + if commitment == handler::commitment(packet) { Ok(()) } else { Err(Error::InvalidPacket( diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 8d657dd628..4c5d5f2469 100644 --- a/shared/src/types/ibc/data.rs +++ b/shared/src/types/ibc/data.rs @@ -333,8 +333,8 @@ pub struct PacketAck(pub Acknowledgement); impl PacketAck { /// Encode the ack pub fn encode_to_vec(&self) -> Vec { - // TODO encode as ibc-go - self.to_string().as_bytes().to_vec() + serde_json::to_vec(&self.0) + .expect("Encoding acknowledgement shouldn't fail") } } @@ -349,7 +349,7 @@ impl Default for PacketAck { // for the string to be used by the current reader impl Display for PacketAck { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "ack") + write!(f, "{}", serde_json::to_string(&self.0).unwrap()) } } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 7f84960f8f..0756a5a93e 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1065,73 +1065,43 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +name = "ibc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "prost", + "prost-types", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.7", "tracing", - "want", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +name = "ibc-proto" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "base64", + "bytes", + "prost", + "prost-types", + "serde", + "tendermint-proto", ] [[package]] @@ -1514,15 +1484,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" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index bd74a94947..2a24f73d48 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1065,73 +1065,43 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +name = "ibc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "prost", + "prost-types", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.7", "tracing", - "want", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +name = "ibc-proto" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "base64", + "bytes", + "prost", + "prost-types", + "serde", + "tendermint-proto", ] [[package]] @@ -1514,15 +1484,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" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index e10a0e335a..7ba444b7bc 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1065,73 +1065,43 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +name = "ibc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "prost", + "prost-types", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.7", "tracing", - "want", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +name = "ibc-proto" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "base64", + "bytes", + "prost", + "prost-types", + "serde", + "tendermint-proto", ] [[package]] @@ -1540,15 +1510,6 @@ dependencies = [ "wee_alloc", ] -[[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" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 26325f57bd..b499094739 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -963,6 +963,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.25.0" @@ -1064,73 +1075,43 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +name = "ibc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "prost", + "prost-types", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.9", "tracing", - "want", ] [[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +name = "ibc-proto" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "hyper", - "pin-project-lite", - "tokio", - "tokio-io-timeout", + "base64", + "bytes", + "prost", + "prost-types", + "serde", + "tendermint-proto", ] [[package]] @@ -1266,7 +1247,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", @@ -1282,7 +1263,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", @@ -1292,7 +1273,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", ] @@ -1300,7 +1281,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", ] @@ -1413,7 +1394,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1462,7 +1443,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1470,7 +1451,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1479,7 +1460,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1497,7 +1478,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1505,7 +1486,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1515,7 +1496,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1523,7 +1504,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", @@ -1534,15 +1515,6 @@ dependencies = [ "wee_alloc", ] -[[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" @@ -2621,7 +2593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] @@ -2828,6 +2800,12 @@ 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" From 528a713bee317824fd92cfd93754ade770e59089 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 11 Jul 2022 11:01:50 +0200 Subject: [PATCH 0428/1995] clean redundancy --- shared/src/ledger/ibc/handler.rs | 85 ++++------ shared/src/ledger/ibc/vp/channel.rs | 220 +++++++++++++------------ shared/src/ledger/ibc/vp/mod.rs | 17 +- shared/src/ledger/storage/write_log.rs | 1 + 4 files changed, 156 insertions(+), 167 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 5274c53c74..9e66630cce 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -462,14 +462,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let conn_id = channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id, - )) - })?; - let counterparty = channel.counterparty(); - let event = make_open_ack_channel_event(msg, conn_id, counterparty) + let event = make_open_ack_channel_event(msg, &channel)? .try_into() .unwrap(); self.emit_ibc_event(event); @@ -496,14 +489,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let conn_id = channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id, - )) - })?; - let counterparty = channel.counterparty(); - let event = make_open_confirm_channel_event(msg, conn_id, counterparty) + let event = make_open_confirm_channel_event(msg, &channel)? .try_into() .unwrap(); self.emit_ibc_event(event); @@ -530,14 +516,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let conn_id = channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id, - )) - })?; - let counterparty = channel.counterparty(); - let event = make_close_init_channel_event(msg, conn_id, counterparty) + let event = make_close_init_channel_event(msg, &channel)? .try_into() .unwrap(); self.emit_ibc_event(event); @@ -567,17 +546,9 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let conn_id = channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id, - )) - })?; - let counterparty = channel.counterparty(); - let event = - make_close_confirm_channel_event(msg, conn_id, counterparty) - .try_into() - .unwrap(); + let event = make_close_confirm_channel_event(msg, &channel)? + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -1323,9 +1294,10 @@ pub fn make_open_try_channel_event( /// Makes OpenAckChannel event pub fn make_open_ack_channel_event( msg: &MsgChannelOpenAck, - conn_id: &ConnectionId, - counterparty: &ChanCounterparty, -) -> IbcEvent { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); let attributes = ChanOpenAck { height: Height::default(), port_id: msg.port_id.clone(), @@ -1334,15 +1306,16 @@ pub fn make_open_ack_channel_event( connection_id: conn_id.clone(), counterparty_port_id: counterparty.port_id().clone(), }; - attributes.into() + Ok(attributes.into()) } /// Makes OpenConfirmChannel event pub fn make_open_confirm_channel_event( msg: &MsgChannelOpenConfirm, - conn_id: &ConnectionId, - counterparty: &ChanCounterparty, -) -> IbcEvent { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); let attributes = ChanOpenConfirm { height: Height::default(), port_id: msg.port_id.clone(), @@ -1351,15 +1324,16 @@ pub fn make_open_confirm_channel_event( counterparty_port_id: counterparty.port_id().clone(), counterparty_channel_id: counterparty.channel_id().cloned(), }; - attributes.into() + Ok(attributes.into()) } /// Makes CloseInitChannel event pub fn make_close_init_channel_event( msg: &MsgChannelCloseInit, - conn_id: &ConnectionId, - counterparty: &ChanCounterparty, -) -> IbcEvent { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); let attributes = ChanCloseInit { height: Height::default(), port_id: msg.port_id.clone(), @@ -1368,15 +1342,16 @@ pub fn make_close_init_channel_event( counterparty_port_id: counterparty.port_id().clone(), counterparty_channel_id: counterparty.channel_id().cloned(), }; - attributes.into() + Ok(attributes.into()) } /// Makes CloseConfirmChannel event pub fn make_close_confirm_channel_event( msg: &MsgChannelCloseConfirm, - conn_id: &ConnectionId, - counterparty: &ChanCounterparty, -) -> IbcEvent { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); let attributes = ChanCloseConfirm { height: Height::default(), port_id: msg.port_id.clone(), @@ -1385,7 +1360,15 @@ pub fn make_close_confirm_channel_event( counterparty_port_id: counterparty.port_id.clone(), counterparty_channel_id: counterparty.channel_id().cloned(), }; - attributes.into() + Ok(attributes.into()) +} + +fn get_connection_id_from_channel( + channel: &ChannelEnd, +) -> Result<&ConnectionId> { + channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel("No connection for the channel".to_owned()) + }) } /// Makes SendPacket event diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 81544ae6e3..3a43834da5 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -9,6 +9,7 @@ use super::super::handler::{ make_close_confirm_channel_event, make_close_init_channel_event, make_open_ack_channel_event, make_open_confirm_channel_event, make_open_init_channel_event, make_open_try_channel_event, + make_timeout_event, }; use super::super::storage::{ ack_key, channel_counter_key, channel_key, client_update_height_key, @@ -241,122 +242,135 @@ where tx_data: &[u8], ) -> Result<()> { let prev_channel = self.channel_end_pre(port_channel_id)?; - let conn_id = channel.connection_hops().get(0).ok_or_else(|| { - Error::InvalidChannel(format!( - "No connection for the channel: Port/Channel {}", - port_channel_id, - )) - })?; - let counterparty = channel.counterparty(); - match channel.state() { - State::Open => match prev_channel.state() { - State::Init => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_channel_open_ack()?; - self.verify_channel_ack_proof( + + let ibc_msg = IbcMessage::decode(tx_data)?; + let event = match ibc_msg.0 { + Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenAck(msg)) => { + if !channel.state().is_open() + || !prev_channel.state_matches(&State::Init) + { + return Err(Error::InvalidStateChange(format!( + "The state change of the channel is invalid for \ + ChannelOpenAck: Port/Channel {}", port_channel_id, - channel, - &msg, - )?; - let event = make_open_ack_channel_event( - &msg, - conn_id, - counterparty, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) + ))); } - State::TryOpen => { - let ibc_msg = IbcMessage::decode(tx_data)?; - let msg = ibc_msg.msg_channel_open_confirm()?; - self.verify_channel_confirm_proof( + self.verify_channel_ack_proof(port_channel_id, channel, &msg)?; + Some(make_open_ack_channel_event(&msg, channel).map_err( + |_| { + Error::InvalidChannel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id + )) + }, + )?) + } + Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenConfirm( + msg, + )) => { + if !channel.state().is_open() + || !prev_channel.state_matches(&State::TryOpen) + { + return Err(Error::InvalidStateChange(format!( + "The state change of the channel is invalid for \ + ChannelOpenConfirm: Port/Channel {}", port_channel_id, - channel, - &msg, - )?; - let event = make_open_confirm_channel_event( - &msg, - conn_id, - counterparty, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) + ))); } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid: Port/Channel \ - {}", + self.verify_channel_confirm_proof( port_channel_id, - ))), - }, - State::Closed => { - if !prev_channel.state_matches(&State::Open) { + channel, + &msg, + )?; + Some(make_open_confirm_channel_event(&msg, channel).map_err( + |_| { + Error::InvalidChannel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id + )) + }, + )?) + } + Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { + if !channel.state_matches(&State::Closed) + || !prev_channel.state().is_open() + { return Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid: \ - Port/Channel {}", + "The state change of the channel is invalid for \ + Timeout: Port/Channel {}", port_channel_id, ))); } - let ibc_msg = IbcMessage::decode(tx_data)?; - match ibc_msg.0 { - // The timeout event will be checked in the commitment - // validation - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { - let commitment_key = ( - msg.packet.source_port, - msg.packet.source_channel, - msg.packet.sequence, - ); - self.validate_commitment_absence(commitment_key) - } - Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket( - msg, - )) => { - let commitment_key = ( - msg.packet.source_port, - msg.packet.source_channel, - msg.packet.sequence, - ); - self.validate_commitment_absence(commitment_key) - } - Ics26Envelope::Ics4ChannelMsg( - ChannelMsg::ChannelCloseInit(msg), - ) => { - let event = make_close_init_channel_event( - &msg, - conn_id, - counterparty, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - Ics26Envelope::Ics4ChannelMsg( - ChannelMsg::ChannelCloseConfirm(msg), - ) => { - self.verify_channel_close_proof( - port_channel_id, - channel, - &msg, - )?; - let event = make_close_confirm_channel_event( - &msg, - conn_id, - counterparty, - ); - self.check_emitted_event(event) - .map_err(|e| Error::IbcEvent(e.to_string())) - } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid: \ - Port/Channel {}", + let commitment_key = ( + msg.packet.source_port.clone(), + msg.packet.source_channel, + msg.packet.sequence, + ); + self.validate_commitment_absence(commitment_key)?; + Some(make_timeout_event(msg.packet)) + } + Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg)) => { + let commitment_key = ( + msg.packet.source_port, + msg.packet.source_channel, + msg.packet.sequence, + ); + self.validate_commitment_absence(commitment_key)?; + None + } + Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseInit( + msg, + )) => Some(make_close_init_channel_event(&msg, channel).map_err( + |_| { + Error::InvalidChannel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id + )) + }, + )?), + Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseConfirm( + msg, + )) => { + self.verify_channel_close_proof( + port_channel_id, + channel, + &msg, + )?; + Some(make_close_confirm_channel_event(&msg, channel).map_err( + |_| { + Error::InvalidChannel(format!( + "No connection for the channel: Port/Channel {}", + port_channel_id + )) + }, + )?) + } + _ => { + return Err(Error::InvalidStateChange(format!( + "The state change of the channel happened with unexpected \ + IBC message: Port/Channel {}", + port_channel_id, + ))); + } + }; + + match event { + Some(event) => self + .check_emitted_event(event) + .map_err(|e| Error::IbcEvent(e.to_string()))?, + None => { + if self.ctx.write_log.get_ibc_event().is_some() { + return Err(Error::InvalidStateChange(format!( + "The state change of the channel happened with an \ + unexpected IBC event: Port/Channel {}, event {:?}", port_channel_id, - ))), + self.ctx.write_log.get_ibc_event(), + ))); } } - _ => Err(Error::InvalidStateChange(format!( - "The state change of the channel is invalid: Port/Channel {}", - port_channel_id, - ))), } + + Ok(()) } fn validate_commitment_absence( diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index a870202626..7e2baf4666 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1154,13 +1154,9 @@ mod tests { // update the channel to Open let channel = get_channel(ChanState::Open, Order::Ordered); let bytes = channel.encode_vec().expect("encoding failed"); - let conn_id = channel - .connection_hops() - .get(0) - .expect("connection should exist"); - let counterparty = channel.counterparty(); write_log.write(&channel_key, bytes).expect("write failed"); - let event = make_open_ack_channel_event(&msg, conn_id, counterparty); + let event = + make_open_ack_channel_event(&msg, &channel).expect("no connection"); write_log.set_ibc_event(event.try_into().unwrap()); let tx_code = vec![]; @@ -1234,13 +1230,8 @@ mod tests { let bytes = channel.encode_vec().expect("encoding failed"); write_log.write(&channel_key, bytes).expect("write failed"); - let conn_id = channel - .connection_hops() - .get(0) - .expect("connection should exist"); - let counterparty = channel.counterparty(); - let event = - make_open_confirm_channel_event(&msg, conn_id, counterparty); + let event = make_open_confirm_channel_event(&msg, &channel) + .expect("no connection"); write_log.set_ibc_event(event.try_into().unwrap()); let tx_code = vec![]; diff --git a/shared/src/ledger/storage/write_log.rs b/shared/src/ledger/storage/write_log.rs index e5b5217069..098204b707 100644 --- a/shared/src/ledger/storage/write_log.rs +++ b/shared/src/ledger/storage/write_log.rs @@ -334,6 +334,7 @@ impl WriteLog { HashMap::with_capacity(100), ); self.block_write_log.extend(tx_write_log); + self.take_ibc_event(); } /// Drop the current transaction's write log when it's declined by any of From 915cdfe5ac0f9f3da1fe127454edb2bcd2d2d456 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 10 Aug 2022 12:38:06 +0200 Subject: [PATCH 0429/1995] give channel proof --- shared/src/ledger/ibc/vp/packet.rs | 24 +++++++++++++++++++++++- tests/src/vm_host_env/ibc.rs | 19 ++++++++++++++++--- tests/src/vm_host_env/mod.rs | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 7c0e6d3992..207727e91c 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -62,6 +62,8 @@ pub enum Error { IbcStorage(IbcStorageError), #[error("IBC event error: {0}")] IbcEvent(String), + #[error("IBC proof error: {0}")] + Proof(String), } /// IBC packet functions result @@ -601,13 +603,14 @@ where channel.version().clone(), ); + let proofs_closed = make_proofs_for_channel(&proofs)?; verify_channel_proofs( self, height, &channel, &connection, &expected_channel, - &proofs, + &proofs_closed, ) .map_err(Error::ProofVerificationFailure)?; } @@ -693,6 +696,25 @@ where } } +/// The proof for the counterpart channel should be in proofs.other_proof +/// `verify_channel_proofs()` requires the proof is in proofs.object_proof +fn make_proofs_for_channel(proofs: &Proofs) -> Result { + let proof_closed = match proofs.other_proof() { + Some(p) => p.clone(), + None => { + return Err(Error::Proof( + "No proof for the counterpart channel".to_string(), + )); + } + }; + Proofs::new(proof_closed, None, None, None, proofs.height()).map_err(|e| { + Error::Proof(format!( + "Creating Proofs for the counterpart channel failed: error {}", + e + )) + }) +} + impl From for Error { fn from(err: IbcStorageError) -> Self { Self::IbcStorage(err) diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 31130127f5..e1c372a85f 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -595,8 +595,9 @@ pub fn msg_transfer( } } -pub fn set_timeout_height(msg: &mut MsgTransfer) { - msg.timeout_height = Height::new(1, 1); +pub fn set_timeout_timestamp(msg: &mut MsgTransfer) { + msg.timeout_timestamp = + (msg.timeout_timestamp - Duration::from_secs(101)).unwrap(); } pub fn msg_packet_recv(packet: Packet) -> MsgRecvPacket { @@ -657,10 +658,22 @@ pub fn msg_timeout_on_close( packet: Packet, next_sequence_recv: Sequence, ) -> MsgTimeoutOnClose { + // add the channel proof + let height = Height::new(0, 1); + let consensus_proof = + ConsensusProof::new(vec![0].try_into().unwrap(), height).unwrap(); + let proofs = Proofs::new( + vec![0].try_into().unwrap(), + Some(vec![0].try_into().unwrap()), + Some(consensus_proof), + Some(vec![0].try_into().unwrap()), + height, + ) + .unwrap(); MsgTimeoutOnClose { packet, next_sequence_recv, - proofs: dummy_proofs(), + proofs, signer: Signer::new("test"), } } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1bd9cc6485..93db4f6e40 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -1617,7 +1617,7 @@ mod tests { // Start a transaction to send a packet let mut msg = ibc::msg_transfer(port_id, channel_id, token.to_string(), &sender); - ibc::set_timeout_height(&mut msg); + ibc::set_timeout_timestamp(&mut msg); let mut tx_data = vec![]; msg.clone() .to_any() From 8c6db6cff80febad7c6963e08ea444dd515b87f9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 15:06:55 +0200 Subject: [PATCH 0430/1995] Revert "wallet: increase keys encryption password iterations" This reverts commit 5ca829cd191eac895056cd6759716854094b91c7. --- 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 0f1f43189e..1c521e7515 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 << 17, 32)) + .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 16, 32)) .expect("Generation of encryption secret key shouldn't fail") } From 14789273a0a601c4c4bab4273b97e53a0979f27b Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 15:07:06 +0200 Subject: [PATCH 0431/1995] Revert "changelog: add #1173" This reverts commit 4c50b0c609a0b6f8e1836db74e659368e8b691a4. --- .changelog/unreleased/improvements/1168-pbkdf-iterations.md | 2 -- 1 file changed, 2 deletions(-) delete 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 deleted file mode 100644 index 417e0f8af8..0000000000 --- a/.changelog/unreleased/improvements/1168-pbkdf-iterations.md +++ /dev/null @@ -1,2 +0,0 @@ -- 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 164432b894d727d2c1d4d3b20c432a79c6d8a504 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 13:28:36 +0100 Subject: [PATCH 0432/1995] Remove then() method in favor of if expr --- .../lib/node/ledger/shell/prepare_proposal.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7ab2770215..266b32c067 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -100,16 +100,17 @@ mod prepare_block { .compress_ethereum_events(eth_events) .expect(NOT_ENOUGH_VOTING_POWER_MSG); - let validator_set_update = self - .storage - .can_send_validator_set_update(SendValsetUpd::AtPrevHeight( - self.storage.last_height, - )) - .then(|| ()) - .map(|()| { - self.compress_valset_updates(valset_upds) - .expect(NOT_ENOUGH_VOTING_POWER_MSG) - }); + let validator_set_update = + if self.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight(self.storage.last_height), + ) { + Some( + self.compress_valset_updates(valset_upds) + .expect(NOT_ENOUGH_VOTING_POWER_MSG), + ) + } else { + None + }; let protocol_key = self .mode From 4c739be7b1b5f7b44a41bf7f2f873d877077af61 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 14:02:26 +0100 Subject: [PATCH 0433/1995] Guard against underflows in valset upd vext validation --- .../ledger/shell/vote_extensions/validator_set_update.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 1c4e351431..1250290346 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -58,6 +58,13 @@ where (VotingPower, validator_set_update::SignedVext), VoteExtensionError, > { + if new_epoch.0 == 0 { + tracing::error!( + "We should always be signing over validator set updates with \ + the next epoch" + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } if ext.data.epoch != new_epoch { let ext_epoch = ext.data.epoch; tracing::error!( From 013e4f15c8a83315e678f46301a3ac4a3a49e0ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 14:41:42 +0100 Subject: [PATCH 0434/1995] Guard against underflows (cont) --- .../shell/vote_extensions/validator_set_update.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 1250290346..c8efc31392 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -157,7 +157,15 @@ where vote_extensions: Vec, ) -> Option { let total_voting_power = { - let prev_valset_epoch = self.storage.get_current_epoch().0 - 1; + let current_valset_epoch = self.storage.get_current_epoch().0; + if current_valset_epoch == Epoch(0) { + tracing::error!( + epoch = ?current_valset_epoch, + "Cannot compress validator set update vote extensions at the given epoch" + ); + return None; + } + let prev_valset_epoch = current_valset_epoch - 1; u64::from( self.storage.get_total_voting_power(Some(prev_valset_epoch)), ) From 7771d70e9df3473b86b6214b643a8ee5c1762ecf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 15:07:28 +0100 Subject: [PATCH 0435/1995] Add stub tests --- .../shell/vote_extensions/validator_set_update.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index c8efc31392..92d81260cb 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -406,4 +406,18 @@ mod test_vote_extensions { assert!(shell.validate_valset_upd_vext(vote_ext, prev_epoch + 1)); } + + /// Test if a [`validator_set_update::Vext`] with an incorrect signature + /// is rejected + #[test] + fn test_reject_bad_signatures() { + // TODO + } + + /// Test if a [`validator_set_update::Vext`] is signed with a secp key + /// that belongs to an active validator of some previous epoch + #[test] + fn test_secp_key_belongs_to_active_validator() { + // TODO + } } From 06d700e31fcfbd542ddffb33aca510b9f469ac0f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 16:24:33 +0100 Subject: [PATCH 0436/1995] Test that we reject valset upd vote extensions with bad signatures --- .../vote_extensions/validator_set_update.rs | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 92d81260cb..d97182598a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -282,6 +282,7 @@ mod test_vote_extensions { // invalid epoch, should have been: current epoch + 1 epoch: Epoch(2), } + // TODO: sign with secp key .sign(protocol_key), ); @@ -409,9 +410,51 @@ mod test_vote_extensions { /// Test if a [`validator_set_update::Vext`] with an incorrect signature /// is rejected + // TODO: + // - sign with secp key + // - add validator voting powers from storage #[test] fn test_reject_bad_signatures() { - // TODO + let (mut shell, _, _) = test_utils::setup(); + shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let validator_addr = + shell.mode.get_validator_address().unwrap().clone(); + + let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + + let ethereum_events = ethereum_events::Vext::empty( + FIRST_HEIGHT_WITH_VEXTS, + validator_addr.clone(), + ) + .sign(protocol_key); + + let validator_set_update = { + let mut ext = validator_set_update::Vext { + // TODO: get voting powers from storage, associated with eth + // addrs + voting_powers: std::collections::HashMap::new(), + validator_addr, + epoch: Epoch(1), + } + // TODO: sign with secp key + .sign(protocol_key); + ext.sig = test_utils::invalidate_signature(ext.sig); + Some(ext) + }; + + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); } /// Test if a [`validator_set_update::Vext`] is signed with a secp key From 409e6594f1ad3c128f1b61c86d125a4bc81721f9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Aug 2022 13:59:41 +0100 Subject: [PATCH 0437/1995] WIP: Fix tests --- apps/src/lib/node/ledger/shell/mod.rs | 56 +++++++++++++------ .../lib/node/ledger/shell/process_proposal.rs | 26 +++++++++ 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2e208c1b8c..57d34c6719 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -845,7 +845,7 @@ mod test_utils { /// by the shell. /// - A sender that can send Ethereum events into the ledger, mocking /// the Ethereum fullnode process - pub fn new() -> ( + pub fn new_at_height(height: BlockHeight) -> ( Self, UnboundedReceiver>, UnboundedSender, @@ -856,27 +856,37 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB + let mut shell = Shell::::new( + config::Ledger::new( + base_dir, + Default::default(), + TendermintMode::Validator, + ), + top_level_directory().join("wasm"), + sender, + Some(eth_receiver), + None, + vp_wasm_compilation_cache, + tx_wasm_compilation_cache, + ); + shell.storage.last_height = height; ( - Self { - shell: Shell::::new( - config::Ledger::new( - base_dir, - Default::default(), - TendermintMode::Validator, - ), - top_level_directory().join("wasm"), - sender, - Some(eth_receiver), - None, - vp_wasm_compilation_cache, - tx_wasm_compilation_cache, - ), - }, + Self { shell }, receiver, eth_sender, ) } + /// Same as [`TestShell::new_at_height`], but returns a shell at block height 0. + #[inline] + pub fn new() -> ( + Self, + UnboundedReceiver>, + UnboundedSender, + ) { + Self::new_at_height(BlockHeight(1)) + } + /// Forward a InitChain request and expect a success pub fn init_chain(&mut self, req: RequestInitChain) { self.shell @@ -950,12 +960,12 @@ mod test_utils { /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup() -> ( + pub(super) fn setup_at_height(height: BlockHeight) -> ( TestShell, UnboundedReceiver>, UnboundedSender, ) { - let (mut test, receiver, eth_receiver) = TestShell::new(); + let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); test.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, @@ -967,6 +977,16 @@ mod test_utils { (test, receiver, eth_receiver) } + /// Same as [`setup`], but returns a shell at block height 0. + #[inline] + pub(super) fn setup() -> ( + TestShell, + UnboundedReceiver>, + UnboundedSender, + ) { + setup_at_height(BlockHeight(0)) + } + /// This is just to be used in testing. It is not /// a meaningful default. impl Default for FinalizeBlock { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 8aa725b468..5890e1df15 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -59,6 +59,7 @@ where // a proposal from some round's leader. let invalid_num_of_eth_ev_digests = eth_ev_digest_num != 1; if invalid_num_of_eth_ev_digests { + println!("Invalid num of eth ev digests"); tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -80,6 +81,7 @@ where !error.is_recoverable() }); if invalid_txs { + println!("Invalid txs"); tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -431,8 +433,32 @@ mod test_process_proposal { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestShell, }; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; use crate::wallet; + fn get_empty_eth_ev_digest(shell: &TestShell) -> TxBytes { + let protocol_key = shell + .mode + .get_protocol_key() + .expect("Test failed"); + let addr = shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(); + let ext = ethereum_events::Vext::empty(BlockHeight(1), addr.clone()).sign(protocol_key); + ethereum_events::VextDigest { + signatures: { + let mut s = HashMap::new(); + s.insert(addr, ext.sig); + s + }, + events: vec![], + } + .sign(protocol_key) + .to_bytes() + } + /// Test that if a proposal contains more than one /// `ethereum_events::VextDigest`, we reject it. #[test] From d10b6adc20c6e1acc73528c7903e88ab5d13656e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Aug 2022 14:31:08 +0100 Subject: [PATCH 0438/1995] Get block height from u64 --- shared/src/types/storage.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 6b6f716459..41c0009f75 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -105,6 +105,12 @@ impl From for BlockHash { } } +impl From for BlockHeight { + fn from(height: u64) -> Self { + BlockHeight(height) + } +} + impl TryFrom for BlockHeight { type Error = String; From b58eaedf11b71d6fdc859a1521d51c0ed96646b4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Aug 2022 14:32:42 +0100 Subject: [PATCH 0439/1995] Set up the test shell at some arbitrary block height --- apps/src/lib/node/ledger/shell/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 57d34c6719..99efd5fc44 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -845,7 +845,9 @@ mod test_utils { /// by the shell. /// - A sender that can send Ethereum events into the ledger, mocking /// the Ethereum fullnode process - pub fn new_at_height(height: BlockHeight) -> ( + pub fn new_at_height>( + height: H, + ) -> ( Self, UnboundedReceiver>, UnboundedSender, @@ -869,15 +871,12 @@ mod test_utils { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ); - shell.storage.last_height = height; - ( - Self { shell }, - receiver, - eth_sender, - ) + shell.storage.last_height = height.into(); + (Self { shell }, receiver, eth_sender) } - /// Same as [`TestShell::new_at_height`], but returns a shell at block height 0. + /// Same as [`TestShell::new_at_height`], but returns a shell at block + /// height 0. #[inline] pub fn new() -> ( Self, @@ -960,12 +959,15 @@ mod test_utils { /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. - pub(super) fn setup_at_height(height: BlockHeight) -> ( + pub(super) fn setup_at_height>( + height: H, + ) -> ( TestShell, UnboundedReceiver>, UnboundedSender, ) { - let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); + let (mut test, receiver, eth_receiver) = + TestShell::new_at_height(height); test.init_chain(RequestInitChain { time: Some(Timestamp { seconds: 0, From 082404f2bd0e9ae97734f6fa28292ab44e1ebb85 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Aug 2022 15:47:07 +0100 Subject: [PATCH 0440/1995] Fixing process proposal unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 109 ++++++------------ 1 file changed, 38 insertions(+), 71 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5890e1df15..b561f7264b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -59,7 +59,6 @@ where // a proposal from some round's leader. let invalid_num_of_eth_ev_digests = eth_ev_digest_num != 1; if invalid_num_of_eth_ev_digests { - println!("Invalid num of eth ev digests"); tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -81,7 +80,6 @@ where !error.is_recoverable() }); if invalid_txs { - println!("Invalid txs"); tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -418,14 +416,6 @@ mod test_process_proposal { use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; - #[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"))] @@ -437,24 +427,25 @@ mod test_process_proposal { use crate::wallet; fn get_empty_eth_ev_digest(shell: &TestShell) -> TxBytes { - let protocol_key = shell - .mode - .get_protocol_key() - .expect("Test failed"); + let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let addr = shell .mode .get_validator_address() .expect("Test failed") .clone(); - let ext = ethereum_events::Vext::empty(BlockHeight(1), addr.clone()).sign(protocol_key); - ethereum_events::VextDigest { + let ext = ethereum_events::Vext::empty( + shell.storage.last_height, + addr.clone(), + ) + .sign(protocol_key); + ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); s.insert(addr, ext.sig); s }, events: vec![], - } + }) .sign(protocol_key) .to_bytes() } @@ -668,7 +659,7 @@ mod test_process_proposal { /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -692,10 +683,10 @@ mod test_process_proposal { .to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { - txs: vec![tx.clone()], + txs: vec![tx.clone(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -719,7 +710,7 @@ mod test_process_proposal { /// Test that a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -777,9 +768,9 @@ mod test_process_proposal { panic!("Test failed"); }; let request = ProcessProposal { - txs: vec![new_tx.to_bytes()], + txs: vec![new_tx.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [response] = shell + let response = if let [response, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -807,8 +798,8 @@ mod test_process_proposal { /// non-zero, [`process_proposal`] rejects that tx #[test] fn test_wrapper_unknown_address() { - let (mut shell, _, _) = TestShell::new(); - let keypair = crate::wallet::defaults::keys().remove(0).1; + let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction data".as_bytes().to_owned()), @@ -827,9 +818,9 @@ mod test_process_proposal { .sign(&keypair) .expect("Test failed"); let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -856,15 +847,7 @@ mod test_process_proposal { /// [`process_proposal`] rejects that tx #[test] fn test_wrapper_insufficient_balance_address() { - let (mut shell, _, _) = TestShell::new(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -886,10 +869,10 @@ mod test_process_proposal { .expect("Test failed"); let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -917,7 +900,7 @@ mod test_process_proposal { /// validated, [`process_proposal`] rejects it #[test] fn test_decrypted_txs_out_of_order() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { @@ -940,9 +923,9 @@ mod test_process_proposal { txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(tx)))); } let req_1 = ProcessProposal { - txs: vec![txs[0].to_bytes()], + txs: vec![txs[0].to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response_1 = if let [resp] = shell + let response_1 = if let [resp, _] = shell .process_proposal(req_1) .expect("Test failed") .as_slice() @@ -954,13 +937,13 @@ mod test_process_proposal { assert_eq!(response_1.result.code, u32::from(ErrorCodes::Ok)); let req_2 = ProcessProposal { - txs: vec![txs[2].to_bytes()], + txs: vec![txs[2].to_bytes(), get_empty_eth_ev_digest(&shell)], }; let response_2 = if let Err(TestError::RejectProposal(resp)) = shell.process_proposal(req_2) { - if let [resp] = resp.as_slice() { + if let [resp, _] = resp.as_slice() { resp.clone() } else { panic!("Test failed") @@ -983,7 +966,7 @@ mod test_process_proposal { /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = gen_keypair(); let tx = Tx::new( @@ -1007,10 +990,10 @@ mod test_process_proposal { Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); let request = ProcessProposal { - txs: vec![tx.to_bytes()], + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -1034,15 +1017,7 @@ mod test_process_proposal { /// undecryptable but still accepted #[test] fn test_invalid_hash_commitment() { - let (mut shell, _, _) = TestShell::new(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -1073,9 +1048,9 @@ mod test_process_proposal { }; let request = ProcessProposal { - txs: vec![tx.to_bytes()], + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -1109,15 +1084,7 @@ mod test_process_proposal { /// marked undecryptable and the errors handled correctly #[test] fn test_undecryptable() { - let (mut shell, _, _) = TestShell::new(); - shell.init_chain(RequestInitChain { - time: Some(Timestamp { - seconds: 0, - nanos: 0, - }), - chain_id: ChainId::default().to_string(), - ..Default::default() - }); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); // not valid tx bytes @@ -1145,9 +1112,9 @@ mod test_process_proposal { wrapper.sign(&keypair).expect("Test failed") }; let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![signed.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() @@ -1214,7 +1181,7 @@ mod test_process_proposal { /// Process Proposal should reject a RawTx, but not panic #[test] fn test_raw_tx_rejected() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(1u64); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -1222,9 +1189,9 @@ mod test_process_proposal { ); let tx = Tx::from(TxType::Raw(tx)); let request = ProcessProposal { - txs: vec![tx.to_bytes()], + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], }; - let response = if let [resp] = shell + let response = if let [resp, _] = shell .process_proposal(request) .expect("Test failed") .as_slice() From 23f48567d7419e2c6ead7cd78a0cbb6543c25658 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Aug 2022 17:02:55 +0100 Subject: [PATCH 0441/1995] WIP: Fix PrepareProposal tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 972666dfe7..66f01c8ba4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -238,17 +238,61 @@ mod prepare_block { use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; + fn get_local_last_commit( + shell: &TestShell, + ) -> Option { + let evts = { + let validator_addr = shell + .mode + .get_validator_address() + .expect("Test failed") + .to_owned(); + + let curr_height = shell.storage.last_height + 1; + + let ext = + ethereum_events::Vext::empty(curr_height, validator_addr); + + let protocol_key = match &shell.mode { + ShellMode::Validator { data, .. } => { + &data.keys.protocol_keypair + } + _ => panic!("Test failed"), + }; + + ext.sign(protocol_key) + }; + + let vote_extension = VoteExtension { + ethereum_events: evts, + validator_set_update: None, + } + .try_to_vec() + .expect("Test failed"); + + let vote = ExtendedVoteInfo { + vote_extension, + ..Default::default() + }; + + Some(ExtendedCommitInfo { + votes: vec![vote], + ..Default::default() + }) + } + /// 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 (mut shell, _, _) = test_utils::setup_at_height(3u64); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), ); let req = RequestPrepareProposal { + local_last_commit: get_local_last_commit(&shell), txs: vec![tx.to_bytes()], max_tx_bytes: 0, ..Default::default() @@ -610,7 +654,7 @@ mod prepare_block { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -637,6 +681,7 @@ mod prepare_block { ) .to_bytes(); let req = RequestPrepareProposal { + local_last_commit: get_local_last_commit(&shell), txs: vec![wrapper.clone()], max_tx_bytes: 0, ..Default::default() @@ -652,7 +697,7 @@ mod prepare_block { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; @@ -660,6 +705,7 @@ mod prepare_block { let mut req = RequestPrepareProposal { txs: vec![], max_tx_bytes: 0, + local_last_commit: get_local_last_commit(&shell), ..Default::default() }; // create a request with two new wrappers from mempool and From 23f702767b1d3a80008a7bec70ed8208e4669751 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 09:16:52 +0100 Subject: [PATCH 0442/1995] Fix PrepareProposal tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 66f01c8ba4..d22309a78d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -248,10 +248,10 @@ mod prepare_block { .expect("Test failed") .to_owned(); - let curr_height = shell.storage.last_height + 1; + let prev_height = shell.storage.last_height; let ext = - ethereum_events::Vext::empty(curr_height, validator_addr); + ethereum_events::Vext::empty(prev_height, validator_addr); let protocol_key = match &shell.mode { ShellMode::Validator { data, .. } => { @@ -298,8 +298,9 @@ mod prepare_block { ..Default::default() }; assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(tx.to_bytes())] + // NOTE: we process mempool txs after protocol txs + shell.prepare_proposal(req).tx_records.remove(1), + record::remove(tx.to_bytes()) ); } @@ -687,8 +688,9 @@ mod prepare_block { ..Default::default() }; assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper)] + // NOTE: we process mempool txs after protocol txs + shell.prepare_proposal(req).tx_records.remove(1), + record::remove(wrapper) ); } @@ -750,6 +752,8 @@ mod prepare_block { .prepare_proposal(req) .tx_records .iter() + // NOTE: skip Ethereum events protocol tx + .skip(1) .filter_map( |TxRecord { tx: tx_bytes, From d2cf8485b4242fcb2aadbc45d0dc736fb3032455 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 09:37:32 +0100 Subject: [PATCH 0443/1995] Fix logic in unit tests --- .../node/ledger/shell/vote_extensions/ethereum_events.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 5c089a7da1..f3c407514c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -358,7 +358,7 @@ mod test_vote_extensions { /// Test that Ethereum events signed by a non-validator are rejected #[test] fn test_eth_events_must_be_signed_by_validator() { - let (shell, _, _) = setup(); + let (shell, _, _) = setup_at_height(3u64); let signing_key = gen_keypair(); let address = shell .mode @@ -405,7 +405,7 @@ mod test_vote_extensions { /// change to the validator set. #[test] fn test_validate_eth_events_vexts() { - let (mut shell, _, _) = setup(); + let (mut shell, _, _) = setup_at_height(3u64); let signing_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let address = shell @@ -472,7 +472,7 @@ mod test_vote_extensions { /// block it was included on in a vote extension is rejected #[test] fn reject_incorrect_block_number() { - let (shell, _, _) = setup(); + let (shell, _, _) = setup_at_height(3u64); let address = shell.mode.get_validator_address().unwrap().clone(); let ethereum_events = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { From 9178ecb17d743071049019180276c60da40280e6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 11:21:37 +0100 Subject: [PATCH 0444/1995] Add a new QueriesExt method to improve code readability --- apps/src/lib/node/ledger/shell/queries.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 5b920f3cdb..e3a6e5d757 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -338,6 +338,9 @@ pub(crate) trait QueriesExt { /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch_from_height(&self, height: BlockHeight) -> Option; + + /// Retrieves the [`BlockHeight`] that is currently being decided. + fn get_current_decision_height(&self) -> BlockHeight; } impl QueriesExt for Storage @@ -538,6 +541,10 @@ where fn get_epoch_from_height(&self, height: BlockHeight) -> Option { self.block.pred_epochs.get_epoch(height) } + + fn get_current_decision_height(&self) -> BlockHeight { + self.last_height + 1 + } } /// This enum is used as a parameter to From 05eb8197becfb25ddbfc6d53b9d7dc6db0a10102 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 11:24:46 +0100 Subject: [PATCH 0445/1995] Use get_current_decision_height() in some places --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 3e894af5ef..bf035814bf 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -78,10 +78,8 @@ mod extend_votes { .expect(VALIDATOR_EXPECT_MSG) .to_owned(); - let curr_height = self.storage.last_height + 1; - let ext = ethereum_events::Vext { - block_height: curr_height, + block_height: self.storage.get_current_decision_height(), ethereum_events: self.new_ethereum_events(), validator_addr, }; @@ -188,8 +186,7 @@ mod extend_votes { req: &request::VerifyVoteExtension, ext: Signed, ) -> bool { - let curr_height = self.storage.last_height + 1; - self.validate_eth_events_vext(ext, curr_height) + self.validate_eth_events_vext(ext, self.storage.get_current_decision_height()) .then(|| true) .unwrap_or_else(|| { tracing::warn!( From 87391cca0ae0593b3abc17fba9f3969fddc4365c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 11:30:05 +0100 Subject: [PATCH 0446/1995] Test rejection of Ethereum events vote extensions issued at genesis --- .../shell/vote_extensions/ethereum_events.rs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index f3c407514c..dc8ff8d2b3 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -237,6 +237,7 @@ where mod test_vote_extensions { use std::convert::TryInto; + use assert_matches::assert_matches; use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; @@ -251,6 +252,7 @@ mod test_vote_extensions { use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; + use crate::node::ledger::shell::vote_extensions::VoteExtensionError; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; /// Test that we successfully receive ethereum events @@ -374,7 +376,7 @@ mod test_vote_extensions { receiver: EthAddress([2; 20]), }], }], - block_height: shell.storage.last_height + 1, + block_height: shell.storage.get_current_decision_height(), validator_addr: address.clone(), } .sign(&signing_key); @@ -413,7 +415,7 @@ mod test_vote_extensions { .get_validator_address() .expect("Test failed") .clone(); - let signed_height = shell.storage.last_height + 1; + let signed_height = shell.storage.get_current_decision_height(); let vote_ext = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), @@ -504,4 +506,34 @@ mod test_vote_extensions { i32::from(VerifyStatus::Reject) ); } + + /// Test if we reject Ethereum events vote extensions + /// issued at genesis + #[test] + fn test_reject_genesis_vexts() { + let (shell, _, _) = setup(); + let address = shell + .mode + .get_validator_address() + .expect("Test failed") + .to_owned(); + let eth_evts = ethereum_events::Vext::empty(0u64.into(), address) + .sign(shell.mode.get_protocol_key().expect("Test failed")); + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + validator_set_update: None, + ethereum_events: eth_evts.clone(), + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + let result = shell + .validate_eth_events_vext_and_get_it_back(eth_evts, 0u64.into()); + assert_matches!(result, Err(VoteExtensionError::IssuedAtGenesis)); + } } From 648f5149f1a561ef6249c6656f7b8aace9ae5338 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 11:39:14 +0100 Subject: [PATCH 0447/1995] Small changes --- apps/src/lib/node/ledger/shell/queries.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e3a6e5d757..978f343065 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -512,7 +512,7 @@ where fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { let (check_prev_heights, height) = match can_send { - SendValsetUpd::Now => (false, self.last_height + 1), + SendValsetUpd::Now => (false, self.get_current_decision_height()), SendValsetUpd::AtPrevHeight(h) => (true, h), }; @@ -538,10 +538,12 @@ where check_prev_heights && first_epoch_heights.binary_search(&height).is_ok() } + #[inline] fn get_epoch_from_height(&self, height: BlockHeight) -> Option { self.block.pred_epochs.get_epoch(height) } + #[inline] fn get_current_decision_height(&self) -> BlockHeight { self.last_height + 1 } From 8d14f3f76d8f3087b883c70c4a1d7f65c63b964b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 14:18:13 +0100 Subject: [PATCH 0448/1995] WIP: Test if we can send validator set update at a certain block height --- apps/src/lib/node/ledger/shell/queries.rs | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 978f343065..401bd88aee 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -559,3 +559,92 @@ pub enum SendValsetUpd { /// vote extension at any previous block height. AtPrevHeight(BlockHeight), } + +#[cfg(test)] +mod test_queries { + use super::*; + use crate::node::ledger::shell::test_utils; + use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + + /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as + /// expected. + #[test] + fn test_can_send_validator_set_update() { + let (mut shell, _, _) = test_utils::setup_at_height(0u64); + + let epoch_assertions = [ + // (current block height, can send valset upd) + (1, true), + (2, false), + (3, false), + (4, false), + (5, false), + (6, false), + (7, false), + (8, false), + (9, false), + (10, false), + (11, false), + // we will change epoch here + (12, true), + (13, false), + (14, false), + (15, false), + (16, false), + ]; + + // test `SendValsetUpd::Now` + for (curr_block_height, can_send) in epoch_assertions.iter().copied() { + println!("Current height: {curr_block_height}"); + assert_eq!( + shell + .storage + .can_send_validator_set_update(SendValsetUpd::Now), + can_send + ); + shell.storage.last_height = curr_block_height.into(); + if curr_block_height == 11u64 { + let validator_set = { + let events_epoch = shell + .storage + .get_epoch_from_height(shell.storage.last_height) + .expect("Test failed"); + + let params = shell.storage.read_pos_params(); + let mut epochs = shell.storage.read_validator_set(); + let mut data = + epochs.get(events_epoch).cloned().expect("Test failed"); + + data.active = data + .active + .iter() + .cloned() + .map(|v| WeightedValidator { + voting_power: VotingPower::from(0u64), + ..v + }) + .collect(); + + epochs.set(data, events_epoch, ¶ms); + epochs + }; + shell.storage.write_validator_set(&validator_set); + + let mut req = FinalizeBlock::default(); + req.header.time = namada::types::time::DateTimeUtc::now(); + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + } + } + + // test `SendValsetUpd::AtPrevHeight` + for (curr_block_height, can_send) in epoch_assertions.iter().copied() { + assert_eq!( + shell.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight(curr_block_height.into()) + ), + can_send + ); + } + } +} From 40136bffe5654842db88f114075a92afae9bb965 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Aug 2022 15:32:30 +0100 Subject: [PATCH 0449/1995] Assert we changed epochs --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d22309a78d..0d4e791139 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -612,6 +612,13 @@ mod prepare_block { shell.finalize_block(req).expect("Test failed"); shell.commit(); + assert_eq!( + shell.storage.get_epoch_from_height( + shell.storage.get_current_decision_height() + ), + Some(Epoch(1)) + ); + // test prepare proposal let (protocol_key, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); From 10aca9739bcfb28be4dff00a9d6ae3c83e9edbe6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 09:11:57 +0100 Subject: [PATCH 0450/1995] Debugging test --- apps/src/lib/node/ledger/shell/queries.rs | 98 +++++++++++------------ 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 401bd88aee..373a10a7d2 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -521,11 +521,12 @@ where return true; } - let first_epoch_heights = self.block.pred_epochs.first_block_heights(); + let fst_heights_of_each_epoch = + self.block.pred_epochs.first_block_heights(); // tentatively check if the last stored height // is the one we are looking for - if first_epoch_heights + if fst_heights_of_each_epoch .last() .map(|&h| h == height) .unwrap_or(false) @@ -533,9 +534,10 @@ where return true; } - // the values in `first_epoch_heights` are stored in ascending - // order, so we can just do a binary search over them - check_prev_heights && first_epoch_heights.binary_search(&height).is_ok() + // the values in `fst_block_heights_of_each_epoch` are stored in + // ascending order, so we can just do a binary search over them + check_prev_heights + && fst_heights_of_each_epoch.binary_search(&height).is_ok() } #[inline] @@ -573,63 +575,54 @@ mod test_queries { let (mut shell, _, _) = test_utils::setup_at_height(0u64); let epoch_assertions = [ - // (current block height, can send valset upd) - (1, true), - (2, false), - (3, false), - (4, false), - (5, false), - (6, false), - (7, false), - (8, false), - (9, false), - (10, false), - (11, false), + // (current epoch, current block height, can send valset upd) + (0, 1, true), + (0, 2, false), + (0, 3, false), + (0, 4, false), + (0, 5, false), + (0, 6, false), + (0, 7, false), + (0, 8, false), + (0, 9, false), + (0, 10, false), + (0, 11, false), + (0, 12, false), + (0, 13, false), + (0, 14, false), + (0, 15, false), // we will change epoch here - (12, true), - (13, false), - (14, false), - (15, false), - (16, false), + (1, 16, true), + (1, 17, false), + (1, 18, false), + (1, 19, false), + (1, 20, false), ]; // test `SendValsetUpd::Now` - for (curr_block_height, can_send) in epoch_assertions.iter().copied() { + for (curr_epoch, curr_block_height, can_send) in + epoch_assertions.iter().copied() + { + shell.storage.last_height = BlockHeight(curr_block_height - 1); + println!("Epochs heights: {:?}", shell.storage.block.pred_epochs.first_block_heights()); println!("Current height: {curr_block_height}"); + assert_eq!( + curr_block_height, + shell.storage.get_current_decision_height().0 + ); + assert_eq!( + shell + .storage + .get_epoch_from_height(curr_block_height.into()), + Some(Epoch(curr_epoch)) + ); assert_eq!( shell .storage .can_send_validator_set_update(SendValsetUpd::Now), can_send ); - shell.storage.last_height = curr_block_height.into(); - if curr_block_height == 11u64 { - let validator_set = { - let events_epoch = shell - .storage - .get_epoch_from_height(shell.storage.last_height) - .expect("Test failed"); - - let params = shell.storage.read_pos_params(); - let mut epochs = shell.storage.read_validator_set(); - let mut data = - epochs.get(events_epoch).cloned().expect("Test failed"); - - data.active = data - .active - .iter() - .cloned() - .map(|v| WeightedValidator { - voting_power: VotingPower::from(0u64), - ..v - }) - .collect(); - - epochs.set(data, events_epoch, ¶ms); - epochs - }; - shell.storage.write_validator_set(&validator_set); - + if curr_block_height == 15u64 { let mut req = FinalizeBlock::default(); req.header.time = namada::types::time::DateTimeUtc::now(); shell.finalize_block(req).expect("Test failed"); @@ -638,7 +631,8 @@ mod test_queries { } // test `SendValsetUpd::AtPrevHeight` - for (curr_block_height, can_send) in epoch_assertions.iter().copied() { + for (_, curr_block_height, can_send) in epoch_assertions.iter().copied() + { assert_eq!( shell.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight(curr_block_height.into()) From e0a097ef14853e55fdd869236e11a38912e61ea7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 10:13:35 +0100 Subject: [PATCH 0451/1995] Test if we can send a validator set update at a given block height --- apps/src/lib/node/ledger/shell/queries.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 373a10a7d2..661a078b9a 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -574,6 +574,8 @@ mod test_queries { fn test_can_send_validator_set_update() { let (mut shell, _, _) = test_utils::setup_at_height(0u64); + const CHANGE_EPOCH_HEIGHT: u64 = 15; + let epoch_assertions = [ // (current epoch, current block height, can send valset upd) (0, 1, true), @@ -590,7 +592,7 @@ mod test_queries { (0, 12, false), (0, 13, false), (0, 14, false), - (0, 15, false), + (0, CHANGE_EPOCH_HEIGHT, false), // we will change epoch here (1, 16, true), (1, 17, false), @@ -604,8 +606,6 @@ mod test_queries { epoch_assertions.iter().copied() { shell.storage.last_height = BlockHeight(curr_block_height - 1); - println!("Epochs heights: {:?}", shell.storage.block.pred_epochs.first_block_heights()); - println!("Current height: {curr_block_height}"); assert_eq!( curr_block_height, shell.storage.get_current_decision_height().0 @@ -622,7 +622,7 @@ mod test_queries { .can_send_validator_set_update(SendValsetUpd::Now), can_send ); - if curr_block_height == 15u64 { + if curr_block_height == CHANGE_EPOCH_HEIGHT { let mut req = FinalizeBlock::default(); req.header.time = namada::types::time::DateTimeUtc::now(); shell.finalize_block(req).expect("Test failed"); @@ -631,8 +631,15 @@ mod test_queries { } // test `SendValsetUpd::AtPrevHeight` - for (_, curr_block_height, can_send) in epoch_assertions.iter().copied() + for (curr_epoch, curr_block_height, can_send) in + epoch_assertions.iter().copied() { + assert_eq!( + shell + .storage + .get_epoch_from_height(curr_block_height.into()), + Some(Epoch(curr_epoch)) + ); assert_eq!( shell.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight(curr_block_height.into()) From e26ad3c2038426a9713eba6d705a94ba0fb938e3 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 0452/1995] 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 56e9862eb9..851a3cb1c9 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -361,6 +361,7 @@ where }; self.db.write_block(state)?; self.last_height = self.block.height; + self.last_epoch = self.block.epoch; self.header = None; Ok(()) } @@ -600,8 +601,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 3cf6ad982c6efebe4dd430d42a8da28876070a7c 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 0453/1995] 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 851a3cb1c9..ab4048304f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -827,7 +827,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, @@ -835,9 +834,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 = @@ -851,7 +851,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); @@ -862,18 +862,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 cb1678b59ce97e9e057b95a1b868a56594b7f4ef 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 0454/1995] changelog: add #1249 --- .changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md diff --git a/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md b/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md new file mode 100644 index 0000000000..d4419e52a5 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md @@ -0,0 +1,2 @@ +- Fix the `last_epoch` field in the shell to only be updated when the block is + committed. ([#1249](https://github.com/anoma/anoma/pull/1249)) \ No newline at end of file From 9575781e1db7511af99554d34adf0a9e8f109677 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 0455/1995] 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 ab4048304f..32b1a79d07 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -612,7 +612,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()?; @@ -831,10 +831,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 e693a213203b9234a01fdb643f544c8d4ab7f644 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 0456/1995] changelog: add #384 --- .../unreleased/bug-fixes/384-fix-new-epoch-start-height.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md diff --git a/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md b/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md new file mode 100644 index 0000000000..cf2bb8f399 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md @@ -0,0 +1,2 @@ +- Fix the value recorded for epoch start block height. + ([#384](https://github.com/anoma/namada/issues/384)) \ No newline at end of file From c4b467cc18c5d4bfacf07c8ae64c0001ee0e4c41 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 13:11:23 +0100 Subject: [PATCH 0457/1995] Rename get_epoch_from_height() to get_epoch() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 12 +++++------- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- apps/src/lib/node/ledger/shell/queries.rs | 12 ++++-------- .../ledger/shell/vote_extensions/ethereum_events.rs | 8 +++----- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0d4e791139..056902fefa 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -581,10 +581,8 @@ mod prepare_block { // artificially change the voting power of the default validator to // zero, change the block height, and commit a dummy block, // to move to a new epoch - let events_epoch = shell - .storage - .get_epoch_from_height(FIRST_HEIGHT) - .expect("Test failed"); + let events_epoch = + shell.storage.get_epoch(FIRST_HEIGHT).expect("Test failed"); let validator_set = { let params = shell.storage.read_pos_params(); let mut epochs = shell.storage.read_validator_set(); @@ -613,9 +611,9 @@ mod prepare_block { shell.commit(); assert_eq!( - shell.storage.get_epoch_from_height( - shell.storage.get_current_decision_height() - ), + shell + .storage + .get_epoch(shell.storage.get_current_decision_height()), Some(Epoch(1)) ); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index b561f7264b..a3fb0ea1ca 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -183,9 +183,9 @@ where let mut voting_power = FractionalVotingPower::default(); let total_power = { - let epoch = self.storage.get_epoch_from_height( - BlockHeight(self.storage.last_height.0), - ); + let epoch = self + .storage + .get_epoch(BlockHeight(self.storage.last_height.0)); u64::from(self.storage.get_total_voting_power(epoch)) }; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 661a078b9a..b7b1077a9d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -337,7 +337,7 @@ pub(crate) trait QueriesExt { fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. - fn get_epoch_from_height(&self, height: BlockHeight) -> Option; + fn get_epoch(&self, height: BlockHeight) -> Option; /// Retrieves the [`BlockHeight`] that is currently being decided. fn get_current_decision_height(&self) -> BlockHeight; @@ -541,7 +541,7 @@ where } #[inline] - fn get_epoch_from_height(&self, height: BlockHeight) -> Option { + fn get_epoch(&self, height: BlockHeight) -> Option { self.block.pred_epochs.get_epoch(height) } @@ -611,9 +611,7 @@ mod test_queries { shell.storage.get_current_decision_height().0 ); assert_eq!( - shell - .storage - .get_epoch_from_height(curr_block_height.into()), + shell.storage.get_epoch(curr_block_height.into()), Some(Epoch(curr_epoch)) ); assert_eq!( @@ -635,9 +633,7 @@ mod test_queries { epoch_assertions.iter().copied() { assert_eq!( - shell - .storage - .get_epoch_from_height(curr_block_height.into()), + shell.storage.get_epoch(curr_block_height.into()), Some(Epoch(curr_epoch)) ); assert_eq!( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index dc8ff8d2b3..857cf327e8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -81,7 +81,7 @@ where return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator - let epoch = self.storage.get_epoch_from_height(last_height); + let epoch = self.storage.get_epoch(last_height); let (voting_power, pk) = self .storage .get_validator_from_address(validator, epoch) @@ -164,10 +164,8 @@ where &self, vote_extensions: Vec>, ) -> Option { - let events_epoch = self - .storage - .get_epoch_from_height(self.storage.last_height) - .expect( + let events_epoch = + self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", ); From c23ee3cb6d56fe5571679f91af5c647be6efcbe3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 24 Aug 2022 14:17:24 +0100 Subject: [PATCH 0458/1995] specs: split out ethereum-bridge.md Update overview of the bridge to mention NAM and wNAM --- documentation/specs/src/SUMMARY.md | 6 + .../src/interoperability/ethereum-bridge.md | 373 +----------------- .../ethereum-bridge/bootstrapping.md | 4 + .../ethereum_events_attestation.md | 157 ++++++++ .../ethereum_smart_contracts.md | 27 ++ .../ethereum-bridge/relayer.md | 27 ++ .../ethereum-bridge/security.md | 10 + .../ethereum-bridge/transfers.md | 142 +++++++ 8 files changed, 379 insertions(+), 367 deletions(-) create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/relayer.md create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/security.md create mode 100644 documentation/specs/src/interoperability/ethereum-bridge/transfers.md diff --git a/documentation/specs/src/SUMMARY.md b/documentation/specs/src/SUMMARY.md index 65e2de1ddf..025331e69b 100644 --- a/documentation/specs/src/SUMMARY.md +++ b/documentation/specs/src/SUMMARY.md @@ -16,6 +16,12 @@ - [Trusted setup](./masp/trusted-setup.md) - [Interoperability](./interoperability.md) - [Ethereum bridge](./interoperability/ethereum-bridge.md) + - [Security](./interoperability/ethereum-bridge/security.md) + - [Bootstrapping](./interoperability/ethereum-bridge/bootstrapping.md) + - [Ethereum smart contracts](./interoperability/ethereum-bridge/ethereum_smart_contracts.md) + - [Ethereum events attestation](./interoperability/ethereum-bridge/ethereum_events_attestation.md) + - [Relayer](./interoperability/ethereum-bridge/relayer.md) + - [Transfers](./interoperability/ethereum-bridge/transfers.md) - [IBC](./interoperability/ibc.md) - [Economics](./economics.md) - [Fee system](./economics/fee-system.md) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index 5877998b7a..c92bf75712 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -1,388 +1,27 @@ # Ethereum bridge -The Namada - Ethereum bridge exists to mint ERC20 tokens on Namada +The Namada - Ethereum bridge exists to mint wrapped ERC20 tokens on Namada which naturally can be redeemed on Ethereum at a later time. Furthermore, it -allows the minting of wrapped tokens on Ethereum backed by escrowed assets on -Namada. +allows the minting of wrapped Namada-native NAM (wNAM) on Ethereum backed +by escrowed NAM on Namada. The Namada Ethereum bridge system consists of: + * An Ethereum full node run by each Namada validator, for including relevant Ethereum events into Namada. * A set of validity predicates on Namada which roughly implements [ICS20](https://docs.cosmos.network/v0.42/modules/ibc/) fungible token transfers. * A set of Ethereum smart contracts. -* A relayer for submitting transactions to Ethereum +* A relayer for submitting transactions to Ethereum. This basic bridge architecture should provide for almost-Namada consensus security for the bridge and free Ethereum state reads on Namada, plus bidirectional message passing with reasonably low gas costs on the Ethereum side. -## Security -On Namada, the validators are full nodes of Ethereum and their stake is also -accounting for security of the bridge. If they carry out a forking attack -on Namada to steal locked tokens of Ethereum their stake will be slashed on Namada. -On the Ethereum side, we will add a limit to the amount of assets that can be -locked to limit the damage a forking attack on Namada can do. To make an attack -more cumbersome we will also add a limit on how fast wrapped Ethereum assets can -be redeemed from Namada. This will not add more security, but rather make the -attack more inconvenient. - -## Ethereum Events Attestation -We want to store events from the smart contracts of our bridge onto Namada. We -will include events that have been seen by at least one validator, but will not -act on them until they have been seen by at least 2/3 of voting power. - -There will be multiple types of events emitted. Validators should -ignore improperly formatted events. Raw events from Ethereum are converted to a -Rust enum type (`EthereumEvent`) by Namada validators before being included -in vote extensions or stored on chain. - -```rust -pub enum EthereumEvent { - // we will have different variants here corresponding to different types - // of raw events we receive from Ethereum - TransfersToNamada(Vec) - // ... -} -``` - -Each event will be stored with a list of the validators that have ever seen it -as well as the fraction of total voting power that has ever seen it. -Once an event has been seen by 2/3 of voting power, it is locked into a -`seen` state, and acted upon. - -There is no adjustment across epoch boundaries - e.g. if an event is seen by 1/3 -of voting power in epoch n, then seen by a different 1/3 of voting power in -epoch m>n, the event will be considered `seen` in total. Validators may never -vote more than once for a given event. - -### Minimum confirmations -There will be a protocol-specified minimum number of confirmations that events -must reach on the Ethereum chain, before validators can vote to include them -on Namada. This minimum number of confirmations will be changeable via -governance. - -`TransferToNamada` events may include a custom minimum number of -confirmations, that must be at least the protocol-specified minimum number of -confirmations. - -Validators must not vote to include events that have not met the required -number of confirmations. Voting on unconfirmed events is considered a -slashable offence. - -### Storage -To make including new events easy, we take the approach of always overwriting -the state with the new state rather than applying state diffs. The storage -keys involved are: -``` -# all values are Borsh-serialized -/eth_msgs/\$msg_hash/body : EthereumEvent -/eth_msgs/\$msg_hash/seen_by : Vec

-/eth_msgs/\$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) -/eth_msgs/\$msg_hash/seen: bool -``` - -`\$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant -`EthereumEvent`. - -Changes to this `/eth_msgs` storage subspace are only ever made by -nodes as part of the ledger code based on the aggregate of vote -extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen -in block `n+1` in a deterministic manner based on the vote extensions of the -Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong -to the `EthBridge` validity predicate. It should disallow any changes to -this storage from wasm transactions. - -### Including events into storage - -For every Namada block proposal, the vote extension of a validator should include -the events of the Ethereum blocks they have seen via their full node such that: -1. The storage value `/eth_msgs/\$msg_hash/seen_by` does not include their - address. -2. It's correctly formatted. -3. It's reached the required number of confirmations on the Ethereum chain - -Each event that a validator is voting to include must be individually signed by -them. If the validator is not voting to include any events, they must still -provide a signed voted extension indicating this. - -The vote extension data field will be a Borsh-serialization of something like the following. -```rust -pub struct VoteExtension(Vec); - -/// A struct used by validators to sign that they have seen a particular -/// ethereum event. These are included in vote extensions -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] -pub struct SignedEthEvent { - /// The address of the signing validator - signer: Address, - /// The proportion of the total voting power held by the validator - power: FractionalVotingPower, - /// The event being signed and the block height at which - /// it was seen. We include the height as part of enforcing - /// that a block proposer submits vote extensions from - /// **the previous round only** - event: Signed<(EthereumEvent, BlockHeight)>, -} -``` - -These vote extensions will be given to the next block proposer who will -aggregate those that it can verify and will inject a protocol transaction -(the "vote extensions" transaction). - -```rust -pub struct MultiSigned { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sigs: Vec, -} - -pub struct MultiSignedEthEvent { - /// Address and voting power of the signing validators - pub signers: Vec<(Address, FractionalVotingPower)>, - /// Events as signed by validators - pub event: MultiSigned<(EthereumEvent, BlockHeight)>, -} - -pub enum ProtocolTxType { - EthereumEvents(Vec) -} -``` - -This vote extensions transaction will be signed by the block proposer. -Validators will check this transaction and the validity of the new votes as -part of `ProcessProposal`, this includes checking: -- signatures -- that votes are really from active validators -- the calculation of backed voting power - -It is also checked that each vote extension came from the previous round, -requiring validators to sign over the Namada block height with their vote -extension. Furthermore, the vote extensions included by the block proposer -should have at least 2 / 3 of the total voting power of the previous round -backing it. Otherwise the block proposer would not have passed the -`FinalizeBlock` phase of the last round. These checks are to prevent censorship -of events from validators by the block proposer. - -In `FinalizeBlock`, we derive a second transaction (the "state update" -transaction) from the vote extensions transaction that: -- calculates the required changes to `/eth_msgs` storage and applies it -- acts on any `/eth_msgs/\$msg_hash` where `seen` is going from `false` to `true` - (e.g. appropriately minting wrapped Ethereum assets) - -This state update transaction will not be recorded on chain but will be -deterministically derived from the vote extensions transaction, which is -recorded on chain. All ledger nodes will derive and apply this transaction to -their own local blockchain state, whenever they receive a block with a vote -extensions transaction. This transaction cannot require a protocol signature -as even non-validator full nodes of Namada will be expected to do this. - -The value of `/eth_msgs/\$msg_hash/seen` will also indicate if the event -has been acted on on the Namada side. The appropriate transfers of tokens to the -given user will be included on chain free of charge and requires no -additional actions from the end user. - -## Namada Validity Predicates - -There will be three internal accounts with associated native validity predicates: - -- `#EthSentinel` - whose validity predicate will verify the inclusion of events -from Ethereum. This validity predicate will control the `/eth_msgs` storage -subspace. -- `#EthBridge` - the storage of which will contain ledgers of balances for -wrapped Ethereum assets (ERC20 tokens) structured in a -["multitoken"](https://github.com/anoma/anoma/issues/1102) hierarchy -- `#EthBridgeEscrow` which will hold in escrow wrapped Namada tokens which have -been sent to Ethereum. - -### Transferring assets from Ethereum to Namada - -#### Wrapped ERC20 -The "transfer" transaction mints the appropriate amount to the corresponding -multitoken balance key for the receiver, based on the specifics of a -`TransferToNamada` Ethereum event. - -```rust -pub struct EthAddress(pub [u8; 20]); - -/// Represents Ethereum assets on the Ethereum blockchain -pub enum EthereumAsset { - /// An ERC20 token and the address of its contract - ERC20(EthAddress), -} - -/// An event transferring some kind of value from Ethereum to Anoma -pub struct TransferToNamada { - /// Quantity of ether in the transfer - pub amount: Amount, - /// Address on Ethereum of the asset - pub asset: EthereumAsset, - /// The Namada address receiving wrapped assets on Anoma - pub receiver: Address, -} -``` - -##### Example - -For 10 DAI i.e. ERC20([0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f)) to `atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt` -``` -#EthBridge - /erc20 - /0x6b175474e89094c44da98b954eedeac495271d0f - /balances - /atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt - += 10 -``` - -#### Namada tokens -Any wrapped Namada tokens being redeemed from Ethereum must have an equivalent amount of the native token held in escrow by `#EthBridgeEscrow`. -The protocol transaction should simply make a transfer from `#EthBridgeEscrow` to the `receiver` for the appropriate amount and asset. - -### Transferring from Namada to Ethereum - -To redeem wrapped Ethereum assets, a user should make a transaction to burn -their wrapped tokens, which the `#EthBridge` validity predicate will accept. - -Once this burn is done, it is incumbent on the end user to -request an appropriate "proof" of the transaction. This proof must be -submitted to the appropriate Ethereum smart contract by the user to -redeem their native Ethereum assets. This also means all Ethereum gas costs -are the responsibility of the end user. - -The proofs to be used will be custom bridge headers that are calculated -deterministically from the block contents, including messages sent by Namada and -possibly validator set updates. They will be designed for maximally -efficient Ethereum decoding and verification. - -For each block on Namada, validators must submit the corresponding bridge -header signed with a special secp256k1 key as part of their vote extension. -Validators must reject votes which do not contain correctly signed bridge -headers. The finalized bridge header with aggregated signatures will appear in the -next block as a protocol transaction. Aggregation of signatures is the -responsibility of the next block proposer. - -The bridge headers need only be produced when the proposed block contains -requests to transfer value over the bridge to Ethereum. The exception is -when validator sets change. Since the Ethereum smart contract should -accept any header signed by bridge header signed by 2 / 3 of the staking -validators, it needs up-to-date knowledge of: -- The current validators' public keys -- The current stake of each validator - -This means the at the end of every Namada epoch, a special transaction must be -sent to the Ethereum contract detailing the new public keys and stake of the -new validator set. This message must also be signed by at least 2 / 3 of the -current validators as a "transfer of power". It is to be included in validators -vote extensions as part of the bridge header. Signing an invalid validator -transition set will be consider a slashable offense. - -Due to asynchronicity concerns, this message should be submitted well in -advance of the actual epoch change, perhaps even at the beginning of each -new epoch. Bridge headers to ethereum should include the current Namada epoch -so that the smart contract knows how to verify the headers. In short, there -is a pipelining mechanism in the smart contract. - -Such a message is not prompted by any user transaction and thus will have -to be carried out by a _bridge relayer_. Once the transfer of power -message is on chain, any time afterwards a Namada bridge process may take -it to craft the appropriate message to the Ethereum smart contracts. - -The details on bridge relayers are below in the corresponding section. - -Signing incorrect headers is considered a slashable offense. Anyone witnessing -an incorrect header that is signed may submit a complaint (a type of transaction) -to initiate slashing of the validator who made the signature. - -#### Namada tokens - -Mints of a wrapped Namada token on Ethereum (including NAM, Namada's native token) -will be represented by a data type like: - -```rust -struct MintWrappedNam { - /// The Namada address owning the token - owner: NamadaAddress, - /// The address on Ethereum receiving the wrapped tokens - receiver: EthereumAddress, - /// The address of the token to be wrapped - token: NamadaAddress, - /// The number of wrapped Namada tokens to mint on Ethereum - amount: Amount, -} -``` - -If a user wishes to mint a wrapped Namada token on Ethereum, they must submit a transaction on Namada that: -- stores `MintWrappedNam` on chain somewhere - TBD -- sends the correct amount of Namada token to `#EthBridgeEscrow` - -Just as in redeeming Ethereum assets above, it is incumbent on the end user to -request an appropriate proof of the transaction. This proof must be -submitted to the appropriate Ethereum smart contract by the user. -The corresponding amount of wrapped NAM tokens will be transferred to the -`receiver` on Ethereum by the smart contract. - -## Namada Bridge Relayers - -Validator changes must be turned into a message that can be communicated to -smart contracts on Ethereum. These smart contracts need this information -to verify proofs of actions taken on Namada. - -Since this is protocol level information, it is not user prompted and thus -should not be the responsibility of any user to submit such a transaction. -However, any user may choose to submit this transaction anyway. - -This necessitates a Namada node whose job it is to submit these transactions on -Ethereum at the conclusion of each Namada epoch. This node is called the -__Designated Relayer__. In theory, since this message is publicly available on the blockchain, -anyone can submit this transaction, but only the Designated Relayer will be -directly compensated by Namada. - -All Namada validators will have an option to serve as bridge relayer and -the Namada ledger will include a process that does the relaying. Since all -Namada validators are running Ethereum full nodes, they can monitor -that the message was relayed correctly by the Designated Relayer. - -During the `FinalizeBlock` call in the ledger, if the epoch changes, a -flag should be set alerting the next block proposer that they are the -Designated Relayer for this epoch. If their message gets accepted by the -Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward -them. The reward amount shall be a protocol parameter that can be changed -via governance. It should be high enough to cover necessary gas fees. - -## Ethereum Smart Contracts -The set of Ethereum contracts should perform the following functions: -- Verify bridge header proofs from Namada so that Namada messages can - be submitted to the contract. -- Verify and maintain evolving validator sets with corresponding stake - and public keys. -- Emit log messages readable by Namada -- Handle ICS20-style token transfer messages appropriately with escrow & - unescrow on the Ethereum side -- Allow for message batching - -Furthermore, the Ethereum contracts will whitelist ETH and tokens that -flow across the bridge as well as ensure limits on transfer volume per epoch. - -An Ethereum smart contract should perform the following steps to verify -a proof from Namada: -1. Check the epoch included in the proof. -2. Look up the validator set corresponding to said epoch. -3. Verify that the signatures included amount to at least 2 / 3 of the - total stake. -4. Check the validity of each signature. - -If all the above verifications succeed, the contract may affect the -appropriate state change, emit logs, etc. - -## Starting the bridge - -Before the bridge can start running, some storage may need to be initialized in -Namada. TBD. +## Resources which may be helpful -## Resources which may be helpful: - [Gravity Bridge Solidity contracts](https://github.com/Gravity-Bridge/Gravity-Bridge/tree/main/solidity) - [ICS20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) - [Rainbow Bridge contracts](https://github.com/aurora-is-near/rainbow-bridge/tree/master/contracts) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md new file mode 100644 index 0000000000..7cfe599aa8 --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -0,0 +1,4 @@ +# Bootstrapping the bridge + +Before the bridge can start running, some storage may need to be initialized in +Namada. TBD. \ No newline at end of file diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md new file mode 100644 index 0000000000..6194511d16 --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md @@ -0,0 +1,157 @@ +# Ethereum Events Attestation + +We want to store events from the smart contracts of our bridge onto Namada. We +will include events that have been seen by at least one validator, but will not +act on them until they have been seen by at least 2/3 of voting power. + +There will be multiple types of events emitted. Validators should +ignore improperly formatted events. Raw events from Ethereum are converted to a +Rust enum type (`EthereumEvent`) by Namada validators before being included +in vote extensions or stored on chain. + +```rust +pub enum EthereumEvent { + // we will have different variants here corresponding to different types + // of raw events we receive from Ethereum + TransfersToNamada(Vec) + // ... +} +``` + +Each event will be stored with a list of the validators that have ever seen it +as well as the fraction of total voting power that has ever seen it. +Once an event has been seen by 2/3 of voting power, it is locked into a +`seen` state, and acted upon. + +There is no adjustment across epoch boundaries - e.g. if an event is seen by 1/3 +of voting power in epoch n, then seen by a different 1/3 of voting power in +epoch m>n, the event will be considered `seen` in total. Validators may never +vote more than once for a given event. + +## Minimum confirmations +There will be a protocol-specified minimum number of confirmations that events +must reach on the Ethereum chain, before validators can vote to include them +on Namada. This minimum number of confirmations will be changeable via +governance. + +`TransferToNamada` events may include a custom minimum number of +confirmations, that must be at least the protocol-specified minimum number of +confirmations. + +Validators must not vote to include events that have not met the required +number of confirmations. Voting on unconfirmed events is considered a +slashable offence. + +###Storage +To make including new events easy, we take the approach of always overwriting +the state with the new state rather than applying state diffs. The storage +keys involved are: +``` +# all values are Borsh-serialized +/eth_msgs/\$msg_hash/body : EthereumEvent +/eth_msgs/\$msg_hash/seen_by : Vec
+/eth_msgs/\$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) +/eth_msgs/\$msg_hash/seen: bool +``` + +`\$msg_hash` is the SHA256 digest of the Borsh serialization of the relevant +`EthereumEvent`. + +Changes to this `/eth_msgs` storage subspace are only ever made by +nodes as part of the ledger code based on the aggregate of vote +extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen +in block `n+1` in a deterministic manner based on the vote extensions of the +Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong +to the `EthBridge` validity predicate. It should disallow any changes to +this storage from wasm transactions. + +## Including events into storage + +For every Namada block proposal, the vote extension of a validator should include +the events of the Ethereum blocks they have seen via their full node such that: +1. The storage value `/eth_msgs/\$msg_hash/seen_by` does not include their + address. +2. It's correctly formatted. +3. It's reached the required number of confirmations on the Ethereum chain + +Each event that a validator is voting to include must be individually signed by +them. If the validator is not voting to include any events, they must still +provide a signed voted extension indicating this. + +The vote extension data field will be a Borsh-serialization of something like the following. +```rust +pub struct VoteExtension(Vec); + +/// A struct used by validators to sign that they have seen a particular +/// ethereum event. These are included in vote extensions +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct SignedEthEvent { + /// The address of the signing validator + signer: Address, + /// The proportion of the total voting power held by the validator + power: FractionalVotingPower, + /// The event being signed and the block height at which + /// it was seen. We include the height as part of enforcing + /// that a block proposer submits vote extensions from + /// **the previous round only** + event: Signed<(EthereumEvent, BlockHeight)>, +} +``` + +These vote extensions will be given to the next block proposer who will +aggregate those that it can verify and will inject a protocol transaction +(the "vote extensions" transaction). + +```rust +pub struct MultiSigned { + /// Arbitrary data to be signed + pub data: T, + /// The signature of the data + pub sigs: Vec, +} + +pub struct MultiSignedEthEvent { + /// Address and voting power of the signing validators + pub signers: Vec<(Address, FractionalVotingPower)>, + /// Events as signed by validators + pub event: MultiSigned<(EthereumEvent, BlockHeight)>, +} + +pub enum ProtocolTxType { + EthereumEvents(Vec) +} +``` + +This vote extensions transaction will be signed by the block proposer. +Validators will check this transaction and the validity of the new votes as +part of `ProcessProposal`, this includes checking: +- signatures +- that votes are really from active validators +- the calculation of backed voting power + +It is also checked that each vote extension came from the previous round, +requiring validators to sign over the Namada block height with their vote +extension. Furthermore, the vote extensions included by the block proposer +should have at least 2 / 3 of the total voting power of the previous round +backing it. Otherwise the block proposer would not have passed the +`FinalizeBlock` phase of the last round. These checks are to prevent censorship +of events from validators by the block proposer. + +In `FinalizeBlock`, we derive a second transaction (the "state update" +transaction) from the vote extensions transaction that: + +- calculates the required changes to `/eth_msgs` storage and applies it +- acts on any `/eth_msgs/\$msg_hash` where `seen` is going from `false` to `true` + (e.g. appropriately minting wrapped Ethereum assets) + +This state update transaction will not be recorded on chain but will be +deterministically derived from the vote extensions transaction, which is +recorded on chain. All ledger nodes will derive and apply this transaction to +their own local blockchain state, whenever they receive a block with a vote +extensions transaction. This transaction cannot require a protocol signature +as even non-validator full nodes of Namada will be expected to do this. + +The value of `/eth_msgs/\$msg_hash/seen` will also indicate if the event +has been acted on on the Namada side. The appropriate transfers of tokens to the +given user will be included on chain free of charge and requires no +additional actions from the end user. diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md new file mode 100644 index 0000000000..962b64149d --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -0,0 +1,27 @@ +# Ethereum Smart Contracts + +The set of Ethereum contracts should perform the following functions: + +- Verify bridge header proofs from Namada so that Namada messages can + be submitted to the contract. +- Verify and maintain evolving validator sets with corresponding stake + and public keys. +- Emit log messages readable by Namada +- Handle ICS20-style token transfer messages appropriately with escrow & + unescrow on the Ethereum side +- Allow for message batching + +Furthermore, the Ethereum contracts will whitelist ETH and tokens that +flow across the bridge as well as ensure limits on transfer volume per epoch. + +An Ethereum smart contract should perform the following steps to verify +a proof from Namada: + +1. Check the epoch included in the proof. +2. Look up the validator set corresponding to said epoch. +3. Verify that the signatures included amount to at least 2 / 3 of the + total stake. +4. Check the validity of each signature. + +If all the above verifications succeed, the contract may affect the +appropriate state change, emit logs, etc. \ No newline at end of file diff --git a/documentation/specs/src/interoperability/ethereum-bridge/relayer.md b/documentation/specs/src/interoperability/ethereum-bridge/relayer.md new file mode 100644 index 0000000000..a39beff2c7 --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/relayer.md @@ -0,0 +1,27 @@ +# Namada Bridge Relayers + +Validator changes must be turned into a message that can be communicated to +smart contracts on Ethereum. These smart contracts need this information +to verify proofs of actions taken on Namada. + +Since this is protocol level information, it is not user prompted and thus +should not be the responsibility of any user to submit such a transaction. +However, any user may choose to submit this transaction anyway. + +This necessitates a Namada node whose job it is to submit these transactions on +Ethereum at the conclusion of each Namada epoch. This node is called the +__Designated Relayer__. In theory, since this message is publicly available on the blockchain, +anyone can submit this transaction, but only the Designated Relayer will be +directly compensated by Namada. + +All Namada validators will have an option to serve as bridge relayer and +the Namada ledger will include a process that does the relaying. Since all +Namada validators are running Ethereum full nodes, they can monitor +that the message was relayed correctly by the Designated Relayer. + +During the `FinalizeBlock` call in the ledger, if the epoch changes, a +flag should be set alerting the next block proposer that they are the +Designated Relayer for this epoch. If their message gets accepted by the +Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward +them. The reward amount shall be a protocol parameter that can be changed +via governance. It should be high enough to cover necessary gas fees. \ No newline at end of file diff --git a/documentation/specs/src/interoperability/ethereum-bridge/security.md b/documentation/specs/src/interoperability/ethereum-bridge/security.md new file mode 100644 index 0000000000..0b022b2e0e --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/security.md @@ -0,0 +1,10 @@ +# Security + +On Namada, the validators are full nodes of Ethereum and their stake is also +accounting for security of the bridge. If they carry out a forking attack +on Namada to steal locked tokens of Ethereum their stake will be slashed on Namada. +On the Ethereum side, we will add a limit to the amount of assets that can be +locked to limit the damage a forking attack on Namada can do. To make an attack +more cumbersome we will also add a limit on how fast wrapped Ethereum assets can +be redeemed from Namada. This will not add more security, but rather make the +attack more inconvenient. diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers.md new file mode 100644 index 0000000000..116dcaa32c --- /dev/null +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers.md @@ -0,0 +1,142 @@ +# Transfers + +## Namada Validity Predicates + +There will be three internal accounts with associated native validity predicates: + +- `#EthSentinel` - whose validity predicate will verify the inclusion of events +from Ethereum. This validity predicate will control the `/eth_msgs` storage +subspace. +- `#EthBridge` - the storage of which will contain ledgers of balances for +wrapped Ethereum assets (ERC20 tokens) structured in a +["multitoken"](https://github.com/anoma/anoma/issues/1102) hierarchy +- `#EthBridgeEscrow` which will hold in escrow wrapped Namada tokens which have +been sent to Ethereum. + +### Transferring assets from Ethereum to Namada + +#### Wrapped ERC20 + +The "transfer" transaction mints the appropriate amount to the corresponding +multitoken balance key for the receiver, based on the specifics of a +`TransferToNamada` Ethereum event. + +```rust +pub struct EthAddress(pub [u8; 20]); + +/// Represents Ethereum assets on the Ethereum blockchain +pub enum EthereumAsset { + /// An ERC20 token and the address of its contract + ERC20(EthAddress), +} + +/// An event transferring some kind of value from Ethereum to Anoma +pub struct TransferToNamada { + /// Quantity of ether in the transfer + pub amount: Amount, + /// Address on Ethereum of the asset + pub asset: EthereumAsset, + /// The Namada address receiving wrapped assets on Anoma + pub receiver: Address, +} +``` + +##### Example + +For 10 DAI i.e. ERC20([0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f)) to `atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt` +``` +#EthBridge + /erc20 + /0x6b175474e89094c44da98b954eedeac495271d0f + /balances + /atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt + += 10 +``` + +#### Namada tokens + +Any wrapped Namada tokens being redeemed from Ethereum must have an equivalent amount of the native token held in escrow by `#EthBridgeEscrow`. +The protocol transaction should simply make a transfer from `#EthBridgeEscrow` to the `receiver` for the appropriate amount and asset. + +### Transferring from Namada to Ethereum + +To redeem wrapped Ethereum assets, a user should make a transaction to burn +their wrapped tokens, which the `#EthBridge` validity predicate will accept. + +Once this burn is done, it is incumbent on the end user to +request an appropriate "proof" of the transaction. This proof must be +submitted to the appropriate Ethereum smart contract by the user to +redeem their native Ethereum assets. This also means all Ethereum gas costs +are the responsibility of the end user. + +The proofs to be used will be custom bridge headers that are calculated +deterministically from the block contents, including messages sent by Namada and +possibly validator set updates. They will be designed for maximally +efficient Ethereum decoding and verification. + +For each block on Namada, validators must submit the corresponding bridge +header signed with a special secp256k1 key as part of their vote extension. +Validators must reject votes which do not contain correctly signed bridge +headers. The finalized bridge header with aggregated signatures will appear in the +next block as a protocol transaction. Aggregation of signatures is the +responsibility of the next block proposer. + +The bridge headers need only be produced when the proposed block contains +requests to transfer value over the bridge to Ethereum. The exception is +when validator sets change. Since the Ethereum smart contract should +accept any header signed by bridge header signed by 2 / 3 of the staking +validators, it needs up-to-date knowledge of: +- The current validators' public keys +- The current stake of each validator + +This means the at the end of every Namada epoch, a special transaction must be +sent to the Ethereum contract detailing the new public keys and stake of the +new validator set. This message must also be signed by at least 2 / 3 of the +current validators as a "transfer of power". It is to be included in validators +vote extensions as part of the bridge header. Signing an invalid validator +transition set will be consider a slashable offense. + +Due to asynchronicity concerns, this message should be submitted well in +advance of the actual epoch change, perhaps even at the beginning of each +new epoch. Bridge headers to ethereum should include the current Namada epoch +so that the smart contract knows how to verify the headers. In short, there +is a pipelining mechanism in the smart contract. + +Such a message is not prompted by any user transaction and thus will have +to be carried out by a _bridge relayer_. Once the transfer of power +message is on chain, any time afterwards a Namada bridge process may take +it to craft the appropriate message to the Ethereum smart contracts. + +The details on bridge relayers are below in the corresponding section. + +Signing incorrect headers is considered a slashable offense. Anyone witnessing +an incorrect header that is signed may submit a complaint (a type of transaction) +to initiate slashing of the validator who made the signature. + +#### Namada tokens + +Mints of a wrapped Namada token on Ethereum (including NAM, Namada's native token) +will be represented by a data type like: + +```rust +struct MintWrappedNam { + /// The Namada address owning the token + owner: NamadaAddress, + /// The address on Ethereum receiving the wrapped tokens + receiver: EthereumAddress, + /// The address of the token to be wrapped + token: NamadaAddress, + /// The number of wrapped Namada tokens to mint on Ethereum + amount: Amount, +} +``` + +If a user wishes to mint a wrapped Namada token on Ethereum, they must submit a transaction on Namada that: +- stores `MintWrappedNam` on chain somewhere - TBD +- sends the correct amount of Namada token to `#EthBridgeEscrow` + +Just as in redeeming Ethereum assets above, it is incumbent on the end user to +request an appropriate proof of the transaction. This proof must be +submitted to the appropriate Ethereum smart contract by the user. +The corresponding amount of wrapped NAM tokens will be transferred to the +`receiver` on Ethereum by the smart contract. \ No newline at end of file From a15060a0587822490c3c10c5575feacb7f188018 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 16:27:55 +0100 Subject: [PATCH 0459/1995] Make ledger compile & fix conflicts --- apps/src/lib/node/ledger/shell/mod.rs | 3 +- .../lib/node/ledger/shell/prepare_proposal.rs | 21 ++++++++--- .../lib/node/ledger/shell/process_proposal.rs | 35 ++++++++++++++----- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a4822dbf4b..439426d6f3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -47,8 +47,7 @@ use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; use tendermint_proto::abci::{ - Misbehavior as Evidence, MisbehaviorType as EvidenceType, - RequestPrepareProposal, ValidatorUpdate, + Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use tendermint_proto::crypto::public_key; use tendermint_proto::types::ConsensusParams; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ad68b2d294..530e04d05b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,11 +1,22 @@ //! Implementation of the `PrepareProposal` ABCI++ method for the Shell +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::proto::Tx; +use namada::types::storage::BlockHeight; +use namada::types::transaction::tx_types::TxType; +use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; +use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use tendermint_proto::abci::{ExtendedCommitInfo, TxRecord}; +use tendermint_proto::abci::{ + ExtendedCommitInfo, RequestPrepareProposal, TxRecord, +}; -use super::super::vote_extensions::{iter_protocol_txs, split_vote_extensions}; use super::super::*; use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; +use crate::node::ledger::shell::vote_extensions::{ + iter_protocol_txs, split_vote_extensions, +}; +use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; impl Shell @@ -205,16 +216,18 @@ pub(super) mod record { mod test_prepare_proposal { use std::collections::HashSet; + use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos::namada_proof_of_stake::types::{ VotingPower, WeightedValidator, }; + use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::proto::{Signed, SignedTxData}; use namada::types::address::xan; use namada::types::ethereum_events::EthereumEvent; - use namada::types::key::common; + use namada::types::key::{common, RefTo}; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{Fee, TxType}; + use namada::types::transaction::{Fee, TxType, WrapperTx}; use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 89b8658324..ad3670c077 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -28,9 +28,10 @@ where /// included txs violates the order decided upon in the previous /// block. pub fn process_proposal( - &mut self, + &self, req: RequestProcessProposal, ) -> ResponseProcessProposal { + let mut tx_queue_iter = self.storage.tx_queue.iter(); tracing::info!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -44,9 +45,12 @@ where .txs .iter() .map(|tx_bytes| { - ExecTxResult::from( - self.process_single_tx(tx_bytes, &mut eth_ev_digest_num), + self.process_single_tx( + tx_bytes, + &mut tx_queue_iter, + &mut eth_ev_digest_num, ) + .into() }) .collect(); @@ -102,6 +106,20 @@ where } } + /// Check all the given txs. + 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, + &mut tx_queue_iter, + &mut 0, + )) + }) + .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. /// @@ -122,10 +140,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, + pub(crate) fn process_single_tx<'a>( + &self, tx_bytes: &[u8], - vote_ext_digest_num: &mut usize, + tx_queue_iter: &mut impl Iterator, + eth_ev_digest_num: &mut usize, ) -> TxResult { let maybe_tx = Tx::try_from(tx_bytes).map_or_else( |err| { @@ -169,7 +188,7 @@ where }, TxType::Protocol(protocol_tx) => match protocol_tx.tx { ProtocolTxType::EthereumEvents(digest) => { - *vote_ext_digest_num += 1; + *eth_ev_digest_num += 1; let extensions = digest.decompress(self.storage.last_height); @@ -233,7 +252,7 @@ where info: "Unsupported protocol transaction type".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 { From be6e2122bc3eba30ac430f778bd4d83ee2c3814b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 18:49:08 +0100 Subject: [PATCH 0460/1995] Add clippy suggestions --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index bf035814bf..35116a6e7d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -263,7 +263,7 @@ mod extend_votes { .map(ProtocolTxType::ValidatorSetUpdate), ] .into_iter() - .flat_map(|tx| tx) + .flatten() } /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering From be5d37f81f858dde6fdd669dda8cd43a849ecd61 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 18:53:56 +0100 Subject: [PATCH 0461/1995] 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 130d40e020..4afa71d6a5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.fcd8dc44135712535a3f11b3c09bd1586227920345f35b1869699e9e6576823f.wasm", - "tx_from_intent.wasm": "tx_from_intent.711d5547792b191a027811ad42d8d1c7f864c718dfe60e696eaf6b20b92e0f6d.wasm", - "tx_ibc.wasm": "tx_ibc.bbd5cfa146a0b55438f1029097b70c340fa6041b920d5fe995037fe7d79df401.wasm", - "tx_init_account.wasm": "tx_init_account.98b6b0e4d029abb350b7b0dc2afd89071fe7e43bde668707c3d2f79d0102a6dc.wasm", - "tx_init_nft.wasm": "tx_init_nft.927dd73040a57f506ef2575c99f22d16222789d23327b6453c43683ff8905421.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3f0e195d6cca570274703b6a1dd17c8d16a188ae8d414a839232c1c5a59c0992.wasm", - "tx_init_validator.wasm": "tx_init_validator.dd4029f70ca69540d53a845d0f851ed928cff6f9ad9651513c3de5ded977968e.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.a0fe66fcea000d6d8fad2e55b4ad8949f81db5a3d165152d08cbc086f6fe2892.wasm", - "tx_transfer.wasm": "tx_transfer.b598144a60771e1389b15cd84b675b51eaac1c158e56f16229a98c1bd9d5c34c.wasm", - "tx_unbond.wasm": "tx_unbond.f8a09cfcecd1f612b95981defe32c8ec4eb27cd037682c757e0ade52af30ccc9.wasm", - "tx_update_vp.wasm": "tx_update_vp.006cfc6e4e19e53f3561e456879e4eccf1af3ce23f4c61ed20e56e199d5d4d6a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.62bdcd303f7ff46ada4d47b3c823b98a90e0e58433380d591f422fc1532cb8d8.wasm", - "tx_withdraw.wasm": "tx_withdraw.11d6d8be25dbd57db7a2b85dd41e9aabe312ee0164c1ac5bee11d983480f9556.wasm", - "vp_nft.wasm": "vp_nft.40d8025fc6fec1f574626ec5fce69b593db60781ca6bd5d35df89974c6c94d45.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.44c58e9395e38488f5bbf3ef26719e8ee66212edbdd3b6e3632d3723f798223e.wasm", - "vp_token.wasm": "vp_token.387879e40873bb63b5a683117aecd417c7d43bf2a203c63da0d089e32030842a.wasm", - "vp_user.wasm": "vp_user.0d2714234226ee86a07f8f95f426053727a3060e4e69272f9ba11e0d2eeae45e.wasm" + "tx_bond.wasm": "tx_bond.15ccca8d893c1094f7e6c3f1a2574914c680deb339319c28191d92c0ebec11cd.wasm", + "tx_from_intent.wasm": "tx_from_intent.00ce860a9d29d1be1ffce84e6dff25741efc4c306c2e2ca19aa75c5d9760efa9.wasm", + "tx_ibc.wasm": "tx_ibc.e85cef69262d552283fed5c0596627c19297944126b44632d9dd2231af93becc.wasm", + "tx_init_account.wasm": "tx_init_account.e97c68791422c8ae6c13e4d4e9649635fd73b73ba884659e62ad297d713b5d23.wasm", + "tx_init_nft.wasm": "tx_init_nft.22544a45b4166531cd413d0ec43134c8aca16c86864b46b42e77e03131968571.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2af29834c28c832e386bb234bda98c88eb9249b423016dd8bedddf4ac585dbaa.wasm", + "tx_init_validator.wasm": "tx_init_validator.0bf3b73e8d51103a8ab14bbc84e54775d3ccadba03e9a3f28eb132bc362511d8.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.8601639233a9ba3678ef98981daff9d7ce0f28505345c43babd4e4fa755da411.wasm", + "tx_transfer.wasm": "tx_transfer.9024556ecbe79edd54e4e8607cde1f4b79cd38abdc7b46a4029ff71c088492e2.wasm", + "tx_unbond.wasm": "tx_unbond.c8bfb892fed3ce86c71417ff4e965e64b81e3d272005e49e7f357bf0ba1e2649.wasm", + "tx_update_vp.wasm": "tx_update_vp.0fbcc81c316fc6060ed830f817ac4b3049d7b8bd18f0455d7061110420bb13de.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7dac4e78004363c371c12b3d37b1ac8ee17fa804932852d5019dad23757eaff9.wasm", + "tx_withdraw.wasm": "tx_withdraw.cae3fbaf9be42e791a10e103b4c3089764f87e5a401e4c1066d86a3b3a01cb40.wasm", + "vp_nft.wasm": "vp_nft.a7930ce19249d1120816b3404c9c3bd5324b4051d15a2096ba7ca4b981291518.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8965d8a4e0b1c9398cd8d07d884c65dbf69be6f017473f14e40977053ed3e0e4.wasm", + "vp_token.wasm": "vp_token.a11d96fcff58f201dbed2092f414f9ef378eb16be0bf436db0738135dae959aa.wasm", + "vp_user.wasm": "vp_user.86de35731e303f729898d0c694abf10a2fdf07038937633ffbeb32e47d3a34b2.wasm" } \ No newline at end of file From 6f0a47fc063536250b30f29566b4ca63c16dc7bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Aug 2022 18:26:35 +0000 Subject: [PATCH 0462/1995] [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 4afa71d6a5..c21393e8a0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.15ccca8d893c1094f7e6c3f1a2574914c680deb339319c28191d92c0ebec11cd.wasm", - "tx_from_intent.wasm": "tx_from_intent.00ce860a9d29d1be1ffce84e6dff25741efc4c306c2e2ca19aa75c5d9760efa9.wasm", - "tx_ibc.wasm": "tx_ibc.e85cef69262d552283fed5c0596627c19297944126b44632d9dd2231af93becc.wasm", - "tx_init_account.wasm": "tx_init_account.e97c68791422c8ae6c13e4d4e9649635fd73b73ba884659e62ad297d713b5d23.wasm", - "tx_init_nft.wasm": "tx_init_nft.22544a45b4166531cd413d0ec43134c8aca16c86864b46b42e77e03131968571.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2af29834c28c832e386bb234bda98c88eb9249b423016dd8bedddf4ac585dbaa.wasm", - "tx_init_validator.wasm": "tx_init_validator.0bf3b73e8d51103a8ab14bbc84e54775d3ccadba03e9a3f28eb132bc362511d8.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.8601639233a9ba3678ef98981daff9d7ce0f28505345c43babd4e4fa755da411.wasm", - "tx_transfer.wasm": "tx_transfer.9024556ecbe79edd54e4e8607cde1f4b79cd38abdc7b46a4029ff71c088492e2.wasm", - "tx_unbond.wasm": "tx_unbond.c8bfb892fed3ce86c71417ff4e965e64b81e3d272005e49e7f357bf0ba1e2649.wasm", - "tx_update_vp.wasm": "tx_update_vp.0fbcc81c316fc6060ed830f817ac4b3049d7b8bd18f0455d7061110420bb13de.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7dac4e78004363c371c12b3d37b1ac8ee17fa804932852d5019dad23757eaff9.wasm", - "tx_withdraw.wasm": "tx_withdraw.cae3fbaf9be42e791a10e103b4c3089764f87e5a401e4c1066d86a3b3a01cb40.wasm", - "vp_nft.wasm": "vp_nft.a7930ce19249d1120816b3404c9c3bd5324b4051d15a2096ba7ca4b981291518.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.8965d8a4e0b1c9398cd8d07d884c65dbf69be6f017473f14e40977053ed3e0e4.wasm", - "vp_token.wasm": "vp_token.a11d96fcff58f201dbed2092f414f9ef378eb16be0bf436db0738135dae959aa.wasm", - "vp_user.wasm": "vp_user.86de35731e303f729898d0c694abf10a2fdf07038937633ffbeb32e47d3a34b2.wasm" + "tx_bond.wasm": "tx_bond.f5c6d09ea3edec9f1a2054433a204c3074be74bdf84cc31d85cdbb57826b66e9.wasm", + "tx_from_intent.wasm": "tx_from_intent.314765bf6d98c3ed8921ec690088db228593e94520131f4210357dcf15297080.wasm", + "tx_ibc.wasm": "tx_ibc.49284526958de6c419783cc77b53869054e0942a633b1ad9f50ee86ea0b38b30.wasm", + "tx_init_account.wasm": "tx_init_account.2ee9452093351afc9c4465d87c174750ba8299d323cbd6a6572bb0e86e04de5f.wasm", + "tx_init_nft.wasm": "tx_init_nft.115aa449551d6246cd4183f9781b383a06bec921917a715e5e5e5348b64b8419.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.9a29feb5c7d292b2c36e129e2f863bfb24ae846fa9d47b1907314c2ee8bd6382.wasm", + "tx_init_validator.wasm": "tx_init_validator.80cbab1b8469db413777eb09d64ff1e25c6f2e8448dc11681e3e5ebb53970ff5.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9d3a9440df51586f8a76c080b1de6ebca116e484f177eccb1b666bdcf81c9a29.wasm", + "tx_transfer.wasm": "tx_transfer.f39dcb8338e21018e04c4834aa9c587581dfd9b83d07aff2f1c8be179ff0e86f.wasm", + "tx_unbond.wasm": "tx_unbond.32e671deae591d83f20b6a034c9a923498df7f51743817b534029fe5a76c4ffd.wasm", + "tx_update_vp.wasm": "tx_update_vp.f73a121138f8b9d2fa0a50ae0b1dfb0b69ca4fd6559f5600aec61cd2aeece960.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1f3876e600041d28138c510dbd47531172fb9774062ded936e48773a0b82df2a.wasm", + "tx_withdraw.wasm": "tx_withdraw.e3cef11e09eddf21bc2c433f76b36eda01c89bf0a1e9d87397e21120bc035faf.wasm", + "vp_nft.wasm": "vp_nft.e28c3cf27dc98d6db226cebc8c64eb759aa5d8b4c32210f9f66a12feb851f23f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c5fc2b058dc6db59b852d065e9fa0efd9ecbb5391c66fb501d1cf09b5c381fc.wasm", + "vp_token.wasm": "vp_token.96de2be005cdd359061106e4b278e3fda4a4456ac84123baa528be6aecc1bbf1.wasm", + "vp_user.wasm": "vp_user.3d8a41b4f48b6793bc49cd70c25c75a4183b0c63251e052ed19828de655c2efd.wasm" } \ No newline at end of file From cf0e79ec1c6b0405e510836cc1feea61fc9ad293 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 10:51:40 +0100 Subject: [PATCH 0463/1995] Make can send valset upd vext test more robust --- apps/src/lib/node/ledger/shell/queries.rs | 35 ++++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 65db2f303f..36c3e36687 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -565,8 +565,6 @@ mod test_queries { fn test_can_send_validator_set_update() { let (mut shell, _, _) = test_utils::setup_at_height(0u64); - const CHANGE_EPOCH_HEIGHT: u64 = 15; - let epoch_assertions = [ // (current epoch, current block height, can send valset upd) (0, 1, true), @@ -580,21 +578,30 @@ mod test_queries { (0, 9, false), (0, 10, false), (0, 11, false), - (0, 12, false), - (0, 13, false), - (0, 14, false), - (0, CHANGE_EPOCH_HEIGHT, false), // we will change epoch here - (1, 16, true), + (1, 12, true), + (1, 13, false), + (1, 14, false), + (1, 15, false), + (1, 16, false), (1, 17, false), (1, 18, false), (1, 19, false), (1, 20, false), + (1, 21, false), + (1, 22, false), + (1, 23, false), + (1, 24, false), + // we will change epoch here + (2, 25, true), + (2, 26, false), + (2, 27, false), + (2, 28, false), ]; // test `SendValsetUpd::Now` - for (curr_epoch, curr_block_height, can_send) in - epoch_assertions.iter().copied() + for (idx, (curr_epoch, curr_block_height, can_send)) in + epoch_assertions.iter().copied().enumerate() { shell.storage.last_height = BlockHeight(curr_block_height - 1); assert_eq!( @@ -611,11 +618,17 @@ mod test_queries { .can_send_validator_set_update(SendValsetUpd::Now), can_send ); - if curr_block_height == CHANGE_EPOCH_HEIGHT { + if epoch_assertions + .get(idx + 1) + .map(|&(_, _, change_epoch)| change_epoch) + .unwrap_or(false) + { + let time = namada::types::time::DateTimeUtc::now(); let mut req = FinalizeBlock::default(); - req.header.time = namada::types::time::DateTimeUtc::now(); + req.header.time = time; shell.finalize_block(req).expect("Test failed"); shell.commit(); + shell.storage.next_epoch_min_start_time = time; } } From 516dd23e0e6a1084f3ca8b8a75291e3dd5853d87 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 08:42:33 +0100 Subject: [PATCH 0464/1995] WIP: Handle validator set update digests in ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ad3670c077..279eb4c35d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -7,7 +7,7 @@ use tendermint_proto::abci::{ ExecTxResult, RequestProcessProposal, ResponseProcessProposal, }; -use super::queries::QueriesExt; +use super::queries::{QueriesExt, SendValsetUpd}; use super::*; impl Shell @@ -247,6 +247,21 @@ where } } } + ProtocolTxType::ValidatorSetUpdate(_digest) => { + if !self.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight(self.storage.last_height), + ) { + return TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected a validator set \ + update vote extension issued at an invalid \ + block height" + .into(), + }; + } + + todo!() + } _ => TxResult { code: ErrorCodes::InvalidTx.into(), info: "Unsupported protocol transaction type".into(), From 8363a03fd7534b9b3e5d6a32b3aaafbe3af1b2b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 21 Aug 2022 10:03:03 +0100 Subject: [PATCH 0465/1995] Add digest counters to ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 279eb4c35d..99f0448da2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,5 +1,6 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell + use namada::types::transaction::protocol::ProtocolTxType; use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::response_process_proposal::ProposalStatus; @@ -10,6 +11,16 @@ use tendermint_proto::abci::{ use super::queries::{QueriesExt, SendValsetUpd}; use super::*; +/// Contains stateful data about the number of vote extension +/// digests found as protocol transactions in a proposed block. +#[derive(Default)] +struct DigestCounters { + /// The number of Ethereum events vote extensions found thus far. + eth_ev_digest_num: usize, + /// The number of validator set update vote extensions found thus far. + valset_upd_digest_num: usize, +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -40,7 +51,7 @@ where "Received block proposal", ); // the number of vote extension digests included in the block proposal - let mut eth_ev_digest_num = 0; + let mut counters = DigestCounters::default(); let tx_results: Vec = req .txs .iter() @@ -56,18 +67,32 @@ where // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. - let invalid_num_of_eth_ev_digests = eth_ev_digest_num != 1; + let invalid_num_of_eth_ev_digests = + self.check_eth_events_num(&counters); if invalid_num_of_eth_ev_digests { tracing::warn!( proposer = ?hex::encode(&req.proposer_address), height = req.height, hash = ?hex::encode(&req.hash), - eth_ev_digest_num, + eth_ev_digest_num = counters.eth_ev_digest_num, "Found invalid number of Ethereum events vote extension digests, proposed block \ will be rejected" ); } + let invalid_num_of_valset_upd_digests = + self.check_valset_upd_num(&counters); + if invalid_num_of_valset_upd_digests { + tracing::warn!( + proposer = ?hex::encode(&req.proposer_address), + height = req.height, + hash = ?hex::encode(&req.hash), + valset_upd_digest_num = counters.valset_upd_digest_num, + "Found invalid number of validator set update vote extension digests, proposed block \ + will be rejected" + ); + } + // Erroneous transactions were detected when processing // the leader's proposal. We allow txs that do not // deserialize properly, that have invalid signatures @@ -87,7 +112,11 @@ where ); } - let status = if invalid_num_of_eth_ev_digests || invalid_txs { + let will_reject_proposal = invalid_num_of_eth_ev_digests + || invalid_num_of_valset_upd_digests + || invalid_txs; + + let status = if will_reject_proposal { ProposalStatus::Reject } else { ProposalStatus::Accept @@ -144,7 +173,7 @@ where &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, - eth_ev_digest_num: &mut usize, + counters: &mut DigestCounters, ) -> TxResult { let maybe_tx = Tx::try_from(tx_bytes).map_or_else( |err| { @@ -188,7 +217,7 @@ where }, TxType::Protocol(protocol_tx) => match protocol_tx.tx { ProtocolTxType::EthereumEvents(digest) => { - *eth_ev_digest_num += 1; + counters.eth_ev_digest_num += 1; let extensions = digest.decompress(self.storage.last_height); @@ -339,6 +368,24 @@ where ) -> shim::response::RevertProposal { Default::default() } + + /// Checks if we have found the correct number of Ethereum events + /// vote extensions in [`DigestCounters`]. + fn check_eth_events_num(&self, c: &DigestCounters) -> bool { + c.eth_ev_digest_num == 1 + } + + /// Checks if we have found the correct number of validator set update + /// vote extensions in [`DigestCounters`]. + fn check_valset_upd_num(&self, c: &DigestCounters) -> bool { + if self.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight(self.storage.last_height), + ) { + c.valset_upd_digest_num == 1 + } else { + true + } + } } /// We test the failure cases of [`process_proposal`]. The happy flows From 67a865eb1fb6a9d42b8000e7e393a6c48b8d517f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 10:24:00 +0100 Subject: [PATCH 0466/1995] Fix counters logic --- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 99f0448da2..972f7e7ffd 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -68,7 +68,7 @@ where // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. let invalid_num_of_eth_ev_digests = - self.check_eth_events_num(&counters); + !self.has_proper_eth_events_num(&counters); if invalid_num_of_eth_ev_digests { tracing::warn!( proposer = ?hex::encode(&req.proposer_address), @@ -81,7 +81,7 @@ where } let invalid_num_of_valset_upd_digests = - self.check_valset_upd_num(&counters); + !self.has_proper_valset_upd_num(&counters); if invalid_num_of_valset_upd_digests { tracing::warn!( proposer = ?hex::encode(&req.proposer_address), @@ -371,13 +371,13 @@ where /// Checks if we have found the correct number of Ethereum events /// vote extensions in [`DigestCounters`]. - fn check_eth_events_num(&self, c: &DigestCounters) -> bool { + fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { c.eth_ev_digest_num == 1 } /// Checks if we have found the correct number of validator set update /// vote extensions in [`DigestCounters`]. - fn check_valset_upd_num(&self, c: &DigestCounters) -> bool { + fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { if self.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight(self.storage.last_height), ) { From 53af029f4619908f3381914ddb91ecb45fe766b3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 16:47:47 +0100 Subject: [PATCH 0467/1995] Rename: SendValsetUpd::AtPrevHeight -> SendValsetUpd::AtFixedHeight --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 ++-- apps/src/lib/node/ledger/shell/queries.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 972f7e7ffd..fd0ec00bee 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -278,7 +278,7 @@ where } ProtocolTxType::ValidatorSetUpdate(_digest) => { if !self.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight(self.storage.last_height), + SendValsetUpd::AtFixedHeight(self.storage.last_height), ) { return TxResult { code: ErrorCodes::InvalidVoteExtension.into(), @@ -379,7 +379,7 @@ where /// vote extensions in [`DigestCounters`]. fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { if self.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight(self.storage.last_height), + SendValsetUpd::AtFixedHeight(self.storage.last_height), ) { c.valset_upd_digest_num == 1 } else { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 36c3e36687..609bf42cc7 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -504,7 +504,7 @@ where fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { let (check_prev_heights, height) = match can_send { SendValsetUpd::Now => (false, self.get_current_decision_height()), - SendValsetUpd::AtPrevHeight(h) => (true, h), + SendValsetUpd::AtFixedHeight(h) => (true, h), }; // handle genesis block corner case @@ -549,8 +549,8 @@ pub enum SendValsetUpd { /// vote extension at the current block height. Now, /// Check if it is possible to send a validator set update - /// vote extension at any previous block height. - AtPrevHeight(BlockHeight), + /// vote extension at any given block height. + AtFixedHeight(BlockHeight), } #[cfg(test)] @@ -632,7 +632,7 @@ mod test_queries { } } - // test `SendValsetUpd::AtPrevHeight` + // test `SendValsetUpd::AtFixedHeight` for (curr_epoch, curr_block_height, can_send) in epoch_assertions.iter().copied() { @@ -642,7 +642,7 @@ mod test_queries { ); assert_eq!( shell.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight(curr_block_height.into()) + SendValsetUpd::AtFixedHeight(curr_block_height.into()) ), can_send ); From cc244cf530e8a4c0d5e0bc17c160082bacf1f0dd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 25 Aug 2022 16:56:18 +0100 Subject: [PATCH 0468/1995] Rebase fixes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 530e04d05b..a650348d74 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -107,7 +107,7 @@ where let validator_set_update = if self.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight(self.storage.last_height), + SendValsetUpd::AtFixedHeight(self.storage.last_height), ) { Some( self.compress_valset_updates(valset_upds) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index fd0ec00bee..80ece9482d 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -14,7 +14,7 @@ use super::*; /// Contains stateful data about the number of vote extension /// digests found as protocol transactions in a proposed block. #[derive(Default)] -struct DigestCounters { +pub(crate) struct DigestCounters { /// The number of Ethereum events vote extensions found thus far. eth_ev_digest_num: usize, /// The number of validator set update vote extensions found thus far. @@ -59,7 +59,7 @@ where self.process_single_tx( tx_bytes, &mut tx_queue_iter, - &mut eth_ev_digest_num, + &mut counters, ) .into() }) @@ -143,7 +143,7 @@ where ExecTxResult::from(self.process_single_tx( tx_bytes, &mut tx_queue_iter, - &mut 0, + &mut Default::default(), )) }) .collect() From 90a5ab53748c40e1a1584534ad935def731e8394 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 09:05:04 +0100 Subject: [PATCH 0469/1995] Handle validator set update vote extensions in ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 90 +++++++++++++++---- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 80ece9482d..1d90b95293 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -226,9 +226,8 @@ where let mut voting_power = FractionalVotingPower::default(); let total_power = { - let epoch = self - .storage - .get_epoch(BlockHeight(self.storage.last_height.0)); + let epoch = + self.storage.get_epoch(self.storage.last_height); u64::from(self.storage.get_total_voting_power(epoch)) }; @@ -266,7 +265,7 @@ where } else { // TODO: maybe return a summary of the reasons for // dropping a vote extension. we have access to the - // motives in `filtered_extensions` + // motives in `valid_extensions` TxResult { code: ErrorCodes::InvalidVoteExtension.into(), info: "Process proposal rejected this proposal \ @@ -276,7 +275,7 @@ where } } } - ProtocolTxType::ValidatorSetUpdate(_digest) => { + ProtocolTxType::ValidatorSetUpdate(digest) => { if !self.storage.can_send_validator_set_update( SendValsetUpd::AtFixedHeight(self.storage.last_height), ) { @@ -289,7 +288,68 @@ where }; } - todo!() + counters.valset_upd_digest_num += 1; + + // NOTE: make sure we do not change epochs between + // extending votes and deciding on the validator + // set update through consensus. otherwise, vext + // validation is going to fail, and we essentially + // halt the chain... + let next_epoch = self.storage.get_current_epoch().0.next(); + + let extensions = digest.decompress(next_epoch); + let valid_extensions = + self.validate_valset_upd_vext_list(extensions); + + let mut voting_power = FractionalVotingPower::default(); + let total_power = self + .storage + .get_total_voting_power(Some(next_epoch - 1)) + .into(); + + if valid_extensions.into_iter().all(|maybe_ext| { + maybe_ext + .map(|(power, _)| { + voting_power += FractionalVotingPower::new( + u64::from(power), + total_power, + ) + .expect( + "The voting power we obtain from storage \ + should always be valid", + ); + }) + .is_ok() + }) { + if voting_power > FractionalVotingPower::TWO_THIRDS { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this \ + transaction" + .into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this \ + proposal because the backing stake of \ + the vote extensions published in the \ + proposal was insufficient" + .into(), + } + } + } else { + // TODO: maybe return a summary of the reasons for + // dropping a vote extension. we have access to the + // motives in `filtered_extensions` + TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this proposal \ + because at least one of the vote \ + extensions included was invalid." + .into(), + } + } } _ => TxResult { code: ErrorCodes::InvalidTx.into(), @@ -649,7 +709,7 @@ mod test_process_proposal { /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -695,7 +755,7 @@ mod test_process_proposal { /// Test that a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -778,7 +838,7 @@ mod test_process_proposal { /// non-zero, [`process_proposal`] rejects that tx #[test] fn test_wrapper_unknown_address() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -822,7 +882,7 @@ mod test_process_proposal { /// [`process_proposal`] rejects that tx #[test] fn test_wrapper_insufficient_balance_address() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -869,7 +929,7 @@ mod test_process_proposal { /// validated, [`process_proposal`] rejects it #[test] fn test_decrypted_txs_out_of_order() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { @@ -934,7 +994,7 @@ mod test_process_proposal { /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( @@ -985,7 +1045,7 @@ mod test_process_proposal { /// undecryptable but still accepted #[test] fn test_invalid_hash_commitment() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -1031,7 +1091,7 @@ mod test_process_proposal { /// marked undecryptable and the errors handled correctly #[test] fn test_undecryptable() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); // not valid tx bytes @@ -1106,7 +1166,7 @@ mod test_process_proposal { /// Process Proposal should reject a RawTx, but not panic #[test] fn test_raw_tx_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(1u64); + let (mut shell, _, _) = test_utils::setup_at_height(3u64); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), From e86491a75c6d9d3286897d35dc776c666f50ce50 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 09:40:22 +0100 Subject: [PATCH 0470/1995] WIP: Change valset upd nonce from Epoch to BlockHeight --- .../vote_extensions/validator_set_update.rs | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 65827f6223..a4f8c7677f 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -15,6 +15,8 @@ use crate::proto::Signed; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, KeccakHash}; use crate::types::key::common::{self, Signature}; +use crate::types::storage::BlockHeight; +#[allow(dead_code)] use crate::types::storage::Epoch; // the namespace strings plugged into validator set hashes @@ -36,7 +38,7 @@ pub struct VextDigest { impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. - pub fn decompress(self, epoch: Epoch) -> Vec { + pub fn decompress(self, block_height: BlockHeight) -> Vec { let VextDigest { signatures, voting_powers, @@ -49,7 +51,7 @@ impl VextDigest { let data = Vext { validator_addr, voting_powers, - epoch, + block_height, }; extensions.push(SignedVext::new_from(data, signature)); } @@ -84,15 +86,16 @@ pub struct Vext { /// until we're able to map a Tendermint address to a validator /// address (see ) pub validator_addr: Address, - /// The new [`Epoch`]. + /// The value of the Namada [`BlockHeight`] at the creation of this + /// [`Vext`]. /// /// Since this is a monotonically growing sequence number, /// it is signed together with the rest of the data to /// prevent replay attacks on validator set updates. /// - /// Additionally, we can use this [`Epoch`] value to query the appropriate - /// validator set to verify signatures with. - pub epoch: Epoch, + /// Additionally, we can use this [`BlockHeight`] value to query the + /// appropriate validator set to verify signatures with. + pub block_height: BlockHeight, } impl Vext { @@ -113,11 +116,11 @@ pub type VotingPowersMap = HashMap; pub trait VotingPowersMapExt { /// Returns the keccak hash of this [`VotingPowersMap`] /// to be signed by an Ethereum validator key. - fn get_bridge_hash(&self, epoch: Epoch) -> KeccakHash; + fn get_bridge_hash(&self, block_height: BlockHeight) -> KeccakHash; /// Returns the keccak hash of this [`VotingPowersMap`] /// to be signed by an Ethereum governance key. - fn get_governance_hash(&self, epoch: Epoch) -> KeccakHash; + fn get_governance_hash(&self, block_height: BlockHeight) -> KeccakHash; /// Returns the list of Ethereum validator addresses and their respective /// voting power (in this order), with an Ethereum ABI compatible encoding. @@ -126,11 +129,11 @@ pub trait VotingPowersMapExt { impl VotingPowersMapExt for VotingPowersMap { #[inline] - fn get_bridge_hash(&self, epoch: Epoch) -> KeccakHash { + fn get_bridge_hash(&self, block_height: BlockHeight) -> KeccakHash { let (validators, voting_powers) = self.get_abi_encoded(); compute_hash( - epoch, + block_height, BRIDGE_CONTRACT_NAMESPACE, validators, voting_powers, @@ -138,9 +141,9 @@ impl VotingPowersMapExt for VotingPowersMap { } #[inline] - fn get_governance_hash(&self, epoch: Epoch) -> KeccakHash { + fn get_governance_hash(&self, block_height: BlockHeight) -> KeccakHash { compute_hash( - epoch, + block_height, GOVERNANCE_CONTRACT_NAMESPACE, // TODO: get governance validators vec![], @@ -188,10 +191,10 @@ impl VotingPowersMapExt for VotingPowersMap { } } -/// Convert an [`Epoch`] to a [`Token`]. +/// Convert a [`BlockHeight`] to a [`Token`]. #[inline] -fn epoch_to_token(epoch: Epoch) -> Token { - Token::Uint(u64::from(epoch).into()) +fn bheight_to_token(BlockHeight(h): BlockHeight) -> Token { + Token::Uint(h.into()) } /// Compute the keccak hash of a validator set update. @@ -201,7 +204,7 @@ fn epoch_to_token(epoch: Epoch) -> Token { // - #[inline] fn compute_hash( - epoch: Epoch, + block_height: BlockHeight, namespace: &str, validators: Vec, voting_powers: Vec, @@ -210,7 +213,7 @@ fn compute_hash( Token::String(namespace.into()), Token::Array(validators), Token::Array(voting_powers), - epoch_to_token(epoch), + bheight_to_token(block_height), ]) } @@ -220,7 +223,7 @@ mod tag { use serde::{Deserialize, Serialize}; use super::encoding::{AbiEncode, Encode, Token}; - use super::{epoch_to_token, Vext, VotingPowersMapExt}; + use super::{bheight_to_token, Vext, VotingPowersMapExt}; use crate::proto::SignedSerialize; use crate::types::ethereum_events::KeccakHash; @@ -236,12 +239,18 @@ mod tag { let KeccakHash(output) = AbiEncode::signed_keccak256(&[ Token::String("updateValidatorsSet".into()), Token::FixedBytes( - ext.voting_powers.get_bridge_hash(ext.epoch).0.to_vec(), + ext.voting_powers + .get_bridge_hash(ext.block_height) + .0 + .to_vec(), ), Token::FixedBytes( - ext.voting_powers.get_governance_hash(ext.epoch).0.to_vec(), + ext.voting_powers + .get_governance_hash(ext.block_height) + .0 + .to_vec(), ), - epoch_to_token(ext.epoch), + bheight_to_token(ext.block_height), ]); output } From 03904d7e4a7a9676223d962c0ae428068e156908 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 09:43:40 +0100 Subject: [PATCH 0471/1995] Ensure we sign valset upd vexts with the correct key kind --- .../src/types/vote_extensions/validator_set_update.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index a4f8c7677f..aed8d58ae8 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -99,12 +99,16 @@ pub struct Vext { } impl Vext { - /// Creates a new signed [`Vext`]. + /// Ensures `sk` is a Secp256k1 key, then creates a new signed [`Vext`]. /// /// For more information, read the docs of [`SignedVext::new`]. #[inline] - pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { - SignedVext::new(sk, self.clone()) + pub fn sign(&self, sk: &common::SecretKey) -> Option { + use common::SecretKey::*; + match sk { + Secp256k1(_) => Some(SignedVext::new(sk, self.clone())), + _ => None, + } } } From 6a17bf8890c0f342dc2f9e83795f420784f008c9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 09:47:45 +0100 Subject: [PATCH 0472/1995] Fix valset upd keccak hash test --- .../types/vote_extensions/validator_set_update.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index aed8d58ae8..21945a1784 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -279,7 +279,7 @@ mod tests { // // const output = abiEncoder.encode( // ['string', 'address[]', 'uint256[]', 'uint256'], - // ['bridge', [], [], 0], + // ['bridge', [], [], 1], // ); // // const hash = keccak256(output).toString('hex'); @@ -287,10 +287,14 @@ mod tests { // console.log(hash); // ``` const EXPECTED: &str = - "36bcf52e7ae929b6df7489d012c8ca63eddb35c1b0baf10f46cac81f6728e0a6"; + "694d9bc27d5da7444e5742b13394b2c8a7e73b43d6acd52b6e23b26b612f7c86"; - let KeccakHash(got) = - compute_hash(Epoch(0), BRIDGE_CONTRACT_NAMESPACE, vec![], vec![]); + let KeccakHash(got) = compute_hash( + 1u64.into(), + BRIDGE_CONTRACT_NAMESPACE, + vec![], + vec![], + ); assert_eq!(&hex::encode(got), EXPECTED); } From 23e38e41b56c9f4bc79199a57926cdf40f3a1e23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 11:24:31 +0100 Subject: [PATCH 0473/1995] Remove #[allow(dead_code)] --- .../ledger/shell/vote_extensions/validator_set_update.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index d97182598a..bd245a80ed 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -30,7 +30,6 @@ where /// * The voting powers are normalized to 2^32, and sorted in descending /// order #[inline] - #[allow(dead_code)] pub fn validate_valset_upd_vext( &self, ext: validator_set_update::SignedVext, @@ -43,7 +42,6 @@ where /// This method behaves exactly like [`Self::validate_valset_upd_vext`], /// with the added bonus of returning the vote extension back, if it /// is valid. - #[allow(dead_code)] // TODO: // - verify if the voting powers in the vote extension are the same // as the ones in storage. we can't do this yet, because we need to map @@ -105,7 +103,6 @@ where /// valid validator set update vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. #[inline] - #[allow(dead_code)] pub fn validate_valset_upd_vext_list( &self, vote_extensions: impl IntoIterator @@ -136,7 +133,6 @@ where /// Takes a list of signed validator set update vote extensions, /// and filters out invalid instances. #[inline] - #[allow(dead_code)] pub fn filter_invalid_valset_upd_vexts( &self, vote_extensions: impl IntoIterator @@ -151,7 +147,6 @@ where /// single [`validator_set_update::VextDigest`], whilst filtering /// invalid [`validator_set_update::SignedVext`] instances in the /// process. - #[allow(dead_code)] pub fn compress_valset_updates( &self, vote_extensions: Vec, From 8d49022ae1327811df70bd93d4d03ca492da95ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 11:25:07 +0100 Subject: [PATCH 0474/1995] Allow unused import for docstrings --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 21945a1784..75451a5575 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -16,7 +16,7 @@ use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, KeccakHash}; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; -#[allow(dead_code)] +#[allow(unused_imports)] use crate::types::storage::Epoch; // the namespace strings plugged into validator set hashes From 8a12c69a61d3577a059ef44eb85efe6d03208b7f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 12:36:41 +0100 Subject: [PATCH 0475/1995] Change valset upd vext nonce in validation code --- .../vote_extensions/validator_set_update.rs | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index bd245a80ed..275e3d7b61 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; -use namada::types::storage::Epoch; +use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; use namada::types::voting_power::FractionalVotingPower; @@ -33,9 +33,9 @@ where pub fn validate_valset_upd_vext( &self, ext: validator_set_update::SignedVext, - new_epoch: Epoch, + last_height: BlockHeight, ) -> bool { - self.validate_valset_upd_vext_and_get_it_back(ext, new_epoch) + self.validate_valset_upd_vext_and_get_it_back(ext, last_height) .is_ok() } @@ -51,25 +51,23 @@ where pub fn validate_valset_upd_vext_and_get_it_back( &self, ext: validator_set_update::SignedVext, - new_epoch: Epoch, + last_height: BlockHeight, ) -> std::result::Result< (VotingPower, validator_set_update::SignedVext), VoteExtensionError, > { - if new_epoch.0 == 0 { + if ext.data.block_height != last_height { + let ext_height = ext.data.block_height; tracing::error!( - "We should always be signing over validator set updates with \ - the next epoch" + "Validator set update vote extension issued for a block \ + height {ext_height} different from the expected height \ + {last_height}" ); return Err(VoteExtensionError::UnexpectedSequenceNumber); } - if ext.data.epoch != new_epoch { - let ext_epoch = ext.data.epoch; - tracing::error!( - "Validator set update vote extension issued for an epoch \ - {ext_epoch} different from the expected epoch {new_epoch}" - ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + if last_height.0 == 0 { + tracing::error!("Dropping vote extension issued at genesis"); + return Err(VoteExtensionError::IssuedAtGenesis); } // get the public key associated with this validator let validator = &ext.data.validator_addr; @@ -116,16 +114,7 @@ where vote_extensions.into_iter().map(|vote_extension| { self.validate_valset_upd_vext_and_get_it_back( vote_extension, - // NOTE: make sure we do not change epochs between - // extending votes and deciding on the validator - // set update through consensus. otherwise, this - // is going to fail. - // - // as an alternative to using epochs, we can use - // block heights as a nonce, that way we can - // always retrieve the proper epoch from the - // block height - self.storage.get_current_epoch().0.next(), + self.storage.last_height, ) }) } @@ -152,18 +141,13 @@ where vote_extensions: Vec, ) -> Option { let total_voting_power = { - let current_valset_epoch = self.storage.get_current_epoch().0; - if current_valset_epoch == Epoch(0) { - tracing::error!( - epoch = ?current_valset_epoch, - "Cannot compress validator set update vote extensions at the given epoch" + let last_height_epoch = + self.storage.get_epoch(self.storage.last_height).expect( + "The epoch of the last block height should always be known", ); - return None; - } - let prev_valset_epoch = current_valset_epoch - 1; - u64::from( - self.storage.get_total_voting_power(Some(prev_valset_epoch)), - ) + self.storage + .get_total_voting_power(Some(last_height_epoch)) + .into() }; let mut voting_power = FractionalVotingPower::default(); From 19d9f371b2fd08984e70f220771f7417a09a375b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 13:16:01 +0100 Subject: [PATCH 0476/1995] WIP: Changing nonce to block height --- .../lib/node/ledger/shell/process_proposal.rs | 14 +++---- .../vote_extensions/validator_set_update.rs | 38 ++++++++++--------- .../vote_extensions/validator_set_update.rs | 6 ++- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1d90b95293..162cfdecf1 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -290,21 +290,17 @@ where counters.valset_upd_digest_num += 1; - // NOTE: make sure we do not change epochs between - // extending votes and deciding on the validator - // set update through consensus. otherwise, vext - // validation is going to fail, and we essentially - // halt the chain... - let next_epoch = self.storage.get_current_epoch().0.next(); - - let extensions = digest.decompress(next_epoch); + let extensions = + digest.decompress(self.storage.last_height); let valid_extensions = self.validate_valset_upd_vext_list(extensions); let mut voting_power = FractionalVotingPower::default(); let total_power = self .storage - .get_total_voting_power(Some(next_epoch - 1)) + .get_total_voting_power( + self.storage.get_epoch(self.storage.last_height), + ) .into(); if valid_extensions.into_iter().all(|maybe_ext| { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 275e3d7b61..d86228b306 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -71,10 +71,12 @@ where } // get the public key associated with this validator let validator = &ext.data.validator_addr; - let prev_epoch = Some(Epoch(new_epoch.0 - 1)); + let last_height_epoch = self.storage.get_epoch(last_height).expect( + "The epoch of the last block height should always be known", + ); let (voting_power, pk) = self .storage - .get_validator_from_address(validator, prev_epoch) + .get_validator_from_address(validator, Some(last_height_epoch)) .map_err(|err| { tracing::error!( ?err, @@ -218,7 +220,7 @@ mod test_vote_extensions { use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::types::key::RefTo; - use namada::types::storage::{BlockHeight, Epoch}; + use namada::types::storage::BlockHeight; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, }; @@ -233,14 +235,14 @@ mod test_vote_extensions { const FIRST_HEIGHT_WITH_VEXTS: BlockHeight = BlockHeight(1); /// Test if a [`validator_set_update::Vext`] that incorrectly labels what - /// epoch it was included on in a vote extension is rejected + /// block height it was included on in a vote extension is rejected // TODO: // - sign with secp key // - add validator voting powers from storage #[test] - fn test_reject_incorrect_epoch() { - let (mut shell, _, _) = test_utils::setup(); - shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + fn test_reject_incorrect_block_height() { + let (mut shell, _, _) = + test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); @@ -258,8 +260,8 @@ mod test_vote_extensions { // addrs voting_powers: std::collections::HashMap::new(), validator_addr, - // invalid epoch, should have been: current epoch + 1 - epoch: Epoch(2), + // invalid height, should have been: `FIRST_HEIGHT_WITH_VEXTS` + block_height: FIRST_HEIGHT_WITH_VEXTS + 1, } // TODO: sign with secp key .sign(protocol_key), @@ -284,8 +286,8 @@ mod test_vote_extensions { /// a non-validator are rejected #[test] fn test_valset_upd_must_be_signed_by_validator() { - let (mut shell, _, _) = test_utils::setup(); - shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let (mut shell, _, _) = + test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); let (protocol_key, validator_addr) = { let bertha_key = wallet::defaults::bertha_keypair(); let bertha_addr = wallet::defaults::bertha_address(); @@ -301,8 +303,8 @@ mod test_vote_extensions { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), + block_height: FIRST_HEIGHT_WITH_VEXTS, validator_addr, - epoch: Epoch(1), } .sign(&protocol_key), ); @@ -327,8 +329,8 @@ mod test_vote_extensions { /// change to the validator set. #[test] fn test_validate_valset_upd_vexts() { - let (mut shell, _, _) = test_utils::setup(); - shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let (mut shell, _, _) = + test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let validator_addr = shell @@ -340,8 +342,8 @@ mod test_vote_extensions { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), + block_height: FIRST_HEIGHT_WITH_VEXTS, validator_addr, - epoch: Epoch(1), } .sign(&protocol_key); @@ -394,8 +396,8 @@ mod test_vote_extensions { // - add validator voting powers from storage #[test] fn test_reject_bad_signatures() { - let (mut shell, _, _) = test_utils::setup(); - shell.storage.last_height = FIRST_HEIGHT_WITH_VEXTS; + let (mut shell, _, _) = + test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); @@ -412,8 +414,8 @@ mod test_vote_extensions { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), + block_height: FIRST_HEIGHT_WITH_VEXTS, validator_addr, - epoch: Epoch(1), } // TODO: sign with secp key .sign(protocol_key); diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 75451a5575..1681172400 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -89,12 +89,16 @@ pub struct Vext { /// The value of the Namada [`BlockHeight`] at the creation of this /// [`Vext`]. /// + /// An important invariant is that this [`BlockHeight`] will always + /// correspond to an epoch before the new validator set is installed. + /// /// Since this is a monotonically growing sequence number, /// it is signed together with the rest of the data to /// prevent replay attacks on validator set updates. /// /// Additionally, we can use this [`BlockHeight`] value to query the - /// appropriate validator set to verify signatures with. + /// epoch with the appropriate validator set to verify signatures with + /// (i.e. the previous validator set). pub block_height: BlockHeight, } From 957e326aed6809832ea91e4499a0c11ff6c6ae23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 13:16:04 +0100 Subject: [PATCH 0477/1995] Revert "Ensure we sign valset upd vexts with the correct key kind" This reverts commit 03904d7e4a7a9676223d962c0ae428068e156908. We will install this commit again once PR #371 lands. --- .../src/types/vote_extensions/validator_set_update.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 1681172400..b51fc83df1 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -103,16 +103,12 @@ pub struct Vext { } impl Vext { - /// Ensures `sk` is a Secp256k1 key, then creates a new signed [`Vext`]. + /// Creates a new signed [`Vext`]. /// /// For more information, read the docs of [`SignedVext::new`]. #[inline] - pub fn sign(&self, sk: &common::SecretKey) -> Option { - use common::SecretKey::*; - match sk { - Secp256k1(_) => Some(SignedVext::new(sk, self.clone())), - _ => None, - } + pub fn sign(&self, sk: &common::SecretKey) -> SignedVext { + SignedVext::new(sk, self.clone()) } } From 842011160782c805ef826617af3a0716a3ecf454 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 13:44:29 +0100 Subject: [PATCH 0478/1995] Finish nonce changes --- .../lib/node/ledger/shell/vote_extensions.rs | 6 ++- .../vote_extensions/validator_set_update.rs | 37 ++++++++----------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 35116a6e7d..041b6a3f9c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -117,7 +117,9 @@ mod extend_votes { // TODO: we need a way to map ethereum addresses to // namada validator addresses voting_powers: std::collections::HashMap::new(), - epoch: next_epoch, + block_height: self + .storage + .get_current_decision_height(), }; let protocol_key = match &self.mode { @@ -210,7 +212,7 @@ mod extend_votes { .and_then(|ext| { // we have a valset update vext when we're expecting one, cool, // let's validate it - self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) + self.validate_valset_upd_vext(ext, self.storage.get_current_decision_height()) .then(|| true) }) .unwrap_or_else(|| { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index d86228b306..00df5f620b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -220,7 +220,6 @@ mod test_vote_extensions { use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::types::key::RefTo; - use namada::types::storage::BlockHeight; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, }; @@ -232,8 +231,6 @@ mod test_vote_extensions { use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; - const FIRST_HEIGHT_WITH_VEXTS: BlockHeight = BlockHeight(1); - /// Test if a [`validator_set_update::Vext`] that incorrectly labels what /// block height it was included on in a vote extension is rejected // TODO: @@ -241,15 +238,14 @@ mod test_vote_extensions { // - add validator voting powers from storage #[test] fn test_reject_incorrect_block_height() { - let (mut shell, _, _) = - test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); + let (shell, _, _) = test_utils::setup(); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let ethereum_events = ethereum_events::Vext::empty( - FIRST_HEIGHT_WITH_VEXTS, + shell.storage.get_current_decision_height(), validator_addr.clone(), ) .sign(protocol_key); @@ -260,8 +256,8 @@ mod test_vote_extensions { // addrs voting_powers: std::collections::HashMap::new(), validator_addr, - // invalid height, should have been: `FIRST_HEIGHT_WITH_VEXTS` - block_height: FIRST_HEIGHT_WITH_VEXTS + 1, + // invalid height + block_height: shell.storage.get_current_decision_height() + 1, } // TODO: sign with secp key .sign(protocol_key), @@ -286,15 +282,14 @@ mod test_vote_extensions { /// a non-validator are rejected #[test] fn test_valset_upd_must_be_signed_by_validator() { - let (mut shell, _, _) = - test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); + let (shell, _, _) = test_utils::setup(); let (protocol_key, validator_addr) = { let bertha_key = wallet::defaults::bertha_keypair(); let bertha_addr = wallet::defaults::bertha_address(); (bertha_key, bertha_addr) }; let ethereum_events = ethereum_events::Vext::empty( - FIRST_HEIGHT_WITH_VEXTS, + shell.storage.get_current_decision_height(), validator_addr.clone(), ) .sign(&protocol_key); @@ -303,7 +298,7 @@ mod test_vote_extensions { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), - block_height: FIRST_HEIGHT_WITH_VEXTS, + block_height: shell.storage.get_current_decision_height(), validator_addr, } .sign(&protocol_key), @@ -329,8 +324,7 @@ mod test_vote_extensions { /// change to the validator set. #[test] fn test_validate_valset_upd_vexts() { - let (mut shell, _, _) = - test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); + let (mut shell, _, _) = test_utils::setup(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let validator_addr = shell @@ -338,11 +332,12 @@ mod test_vote_extensions { .get_validator_address() .expect("Test failed") .clone(); + let signed_height = shell.storage.get_current_decision_height(); let vote_ext = validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), - block_height: FIRST_HEIGHT_WITH_VEXTS, + block_height: signed_height, validator_addr, } .sign(&protocol_key); @@ -364,7 +359,8 @@ mod test_vote_extensions { // we advance forward to the next epoch let mut req = FinalizeBlock::default(); req.header.time = namada::types::time::DateTimeUtc::now(); - shell.storage.last_height = BlockHeight(11); + shell.storage.last_height = + shell.storage.get_current_decision_height() + 11; shell.finalize_block(req).expect("Test failed"); shell.commit(); assert_eq!(shell.storage.get_current_epoch().0.0, 1); @@ -386,7 +382,7 @@ mod test_vote_extensions { .is_ok() ); - assert!(shell.validate_valset_upd_vext(vote_ext, prev_epoch + 1)); + assert!(shell.validate_valset_upd_vext(vote_ext, signed_height)); } /// Test if a [`validator_set_update::Vext`] with an incorrect signature @@ -396,15 +392,14 @@ mod test_vote_extensions { // - add validator voting powers from storage #[test] fn test_reject_bad_signatures() { - let (mut shell, _, _) = - test_utils::setup_at_height(FIRST_HEIGHT_WITH_VEXTS); + let (shell, _, _) = test_utils::setup(); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let ethereum_events = ethereum_events::Vext::empty( - FIRST_HEIGHT_WITH_VEXTS, + shell.storage.get_current_decision_height(), validator_addr.clone(), ) .sign(protocol_key); @@ -414,7 +409,7 @@ mod test_vote_extensions { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), - block_height: FIRST_HEIGHT_WITH_VEXTS, + block_height: shell.storage.get_current_decision_height(), validator_addr, } // TODO: sign with secp key From f48ae69467580d8e352b0ab88bbbea098c6039f1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 13:54:40 +0100 Subject: [PATCH 0479/1995] Remove debug println!() from code --- apps/src/lib/node/ledger/shell/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 439426d6f3..68c9c44d00 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -69,15 +69,10 @@ use crate::{config, wallet}; fn key_to_tendermint( pk: &common::PublicKey, ) -> std::result::Result { - 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::Ed25519(_) => 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())) } From 608ac69d5f5b52bc5bb3c7af980776599b54c106 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 14:19:29 +0100 Subject: [PATCH 0480/1995] Refactor ProcessProposal a bit --- .../lib/node/ledger/shell/process_proposal.rs | 167 +++++++----------- 1 file changed, 63 insertions(+), 104 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 162cfdecf1..de29991335 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use namada::ledger::pos::types::VotingPower; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::voting_power::FractionalVotingPower; use tendermint_proto::abci::response_process_proposal::ProposalStatus; @@ -149,6 +150,59 @@ where .collect() } + /// Validates a list of vote extensions, included in PrepareProposal. + /// + /// If a vote extension is [`Some`], then it was validated properly, + /// and the voting power of the validator who signed it is considered + /// in the sum of the total voting power of all received vote extensions. + fn validate_vexts_in_proposal(&self, mut vote_extensions: I) -> TxResult + where + I: Iterator>, + { + let mut voting_power = FractionalVotingPower::default(); + let total_power = { + let epoch = self.storage.get_epoch(self.storage.last_height); + u64::from(self.storage.get_total_voting_power(epoch)) + }; + + if vote_extensions.all(|maybe_ext| { + maybe_ext + .map(|power| { + voting_power += FractionalVotingPower::new( + u64::from(power), + total_power, + ) + .expect( + "The voting power we obtain from storage should \ + always be valid", + ); + }) + .is_some() + }) { + if voting_power > FractionalVotingPower::TWO_THIRDS { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this transaction".into(), + } + } else { + TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this proposal because \ + the backing stake of the vote extensions published \ + in the proposal was insufficient" + .into(), + } + } + } else { + TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this proposal because at \ + least one of the vote extensions included was invalid." + .into(), + } + } + } + /// 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. /// @@ -222,58 +276,11 @@ where let extensions = digest.decompress(self.storage.last_height); let valid_extensions = - self.validate_eth_events_vext_list(extensions); - - let mut voting_power = FractionalVotingPower::default(); - let total_power = { - let epoch = - self.storage.get_epoch(self.storage.last_height); - u64::from(self.storage.get_total_voting_power(epoch)) - }; - - if valid_extensions.into_iter().all(|maybe_ext| { - maybe_ext - .map(|(power, _)| { - voting_power += FractionalVotingPower::new( - u64::from(power), - total_power, - ) - .expect( - "The voting power we obtain from storage \ - should always be valid", - ); - }) - .is_ok() - }) { - if voting_power > FractionalVotingPower::TWO_THIRDS { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), - info: "Process proposal rejected this \ - proposal because the backing stake of \ - the vote extensions published in the \ - proposal was insufficient" - .into(), - } - } - } else { - // TODO: maybe return a summary of the reasons for - // dropping a vote extension. we have access to the - // motives in `valid_extensions` - TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), - info: "Process proposal rejected this proposal \ - because at least one of the vote \ - extensions included was invalid." - .into(), - } - } + self.validate_eth_events_vext_list(extensions).map( + |maybe_ext| maybe_ext.ok().map(|(power, _)| power), + ); + + self.validate_vexts_in_proposal(valid_extensions) } ProtocolTxType::ValidatorSetUpdate(digest) => { if !self.storage.can_send_validator_set_update( @@ -293,59 +300,11 @@ where let extensions = digest.decompress(self.storage.last_height); let valid_extensions = - self.validate_valset_upd_vext_list(extensions); + self.validate_valset_upd_vext_list(extensions).map( + |maybe_ext| maybe_ext.ok().map(|(power, _)| power), + ); - let mut voting_power = FractionalVotingPower::default(); - let total_power = self - .storage - .get_total_voting_power( - self.storage.get_epoch(self.storage.last_height), - ) - .into(); - - if valid_extensions.into_iter().all(|maybe_ext| { - maybe_ext - .map(|(power, _)| { - voting_power += FractionalVotingPower::new( - u64::from(power), - total_power, - ) - .expect( - "The voting power we obtain from storage \ - should always be valid", - ); - }) - .is_ok() - }) { - if voting_power > FractionalVotingPower::TWO_THIRDS { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), - info: "Process proposal rejected this \ - proposal because the backing stake of \ - the vote extensions published in the \ - proposal was insufficient" - .into(), - } - } - } else { - // TODO: maybe return a summary of the reasons for - // dropping a vote extension. we have access to the - // motives in `filtered_extensions` - TxResult { - code: ErrorCodes::InvalidVoteExtension.into(), - info: "Process proposal rejected this proposal \ - because at least one of the vote \ - extensions included was invalid." - .into(), - } - } + self.validate_vexts_in_proposal(valid_extensions) } _ => TxResult { code: ErrorCodes::InvalidTx.into(), From e9eaa686560f0aa5e8d6789016480366609f03c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Aug 2022 14:45:04 +0100 Subject: [PATCH 0481/1995] Fix docstring in validation code --- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 00df5f620b..46d0f30bf4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -24,10 +24,10 @@ where /// Checks that: /// * The signing validator was active at the preceding epoch /// * The validator correctly signed the extension - /// * The validator signed over the new epoch inside of the extension + /// * The validator signed over the block height inside of the extension /// * The voting powers in the vote extension correspond to the voting /// powers of the validators of the new epoch - /// * The voting powers are normalized to 2^32, and sorted in descending + /// * The voting powers are normalized to `2^32`, and sorted in descending /// order #[inline] pub fn validate_valset_upd_vext( From 9ef64e1d5fb7736247bafe35c36fc9ad7609e5e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 28 Aug 2022 18:05:27 +0100 Subject: [PATCH 0482/1995] Implement SendValsetUpd::AtPrevHeight --- .../lib/node/ledger/shell/process_proposal.rs | 9 +++++---- apps/src/lib/node/ledger/shell/queries.rs | 20 ++++++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index de29991335..9dd3742baa 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -284,7 +284,7 @@ where } ProtocolTxType::ValidatorSetUpdate(digest) => { if !self.storage.can_send_validator_set_update( - SendValsetUpd::AtFixedHeight(self.storage.last_height), + SendValsetUpd::AtPrevHeight, ) { return TxResult { code: ErrorCodes::InvalidVoteExtension.into(), @@ -393,9 +393,10 @@ where /// Checks if we have found the correct number of validator set update /// vote extensions in [`DigestCounters`]. fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { - if self.storage.can_send_validator_set_update( - SendValsetUpd::AtFixedHeight(self.storage.last_height), - ) { + if self + .storage + .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) + { c.valset_upd_digest_num == 1 } else { true diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 609bf42cc7..6708f02b96 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -504,6 +504,7 @@ where fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { let (check_prev_heights, height) = match can_send { SendValsetUpd::Now => (false, self.get_current_decision_height()), + SendValsetUpd::AtPrevHeight => (false, self.last_height), SendValsetUpd::AtFixedHeight(h) => (true, h), }; @@ -549,6 +550,9 @@ pub enum SendValsetUpd { /// vote extension at the current block height. Now, /// Check if it is possible to send a validator set update + /// vote extension at the previous block height. + AtPrevHeight, + /// Check if it is possible to send a validator set update /// vote extension at any given block height. AtFixedHeight(BlockHeight), } @@ -599,7 +603,7 @@ mod test_queries { (2, 28, false), ]; - // test `SendValsetUpd::Now` + // test `SendValsetUpd::Now` and `SendValsetUpd::AtPrevHeight` for (idx, (curr_epoch, curr_block_height, can_send)) in epoch_assertions.iter().copied().enumerate() { @@ -618,6 +622,20 @@ mod test_queries { .can_send_validator_set_update(SendValsetUpd::Now), can_send ); + if let Some((epoch, height, can_send)) = + epoch_assertions.get(idx.wrapping_sub(1)).copied() + { + assert_eq!( + shell.storage.get_epoch(height.into()), + Some(Epoch(epoch)) + ); + assert_eq!( + shell.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight + ), + can_send + ); + } if epoch_assertions .get(idx + 1) .map(|&(_, _, change_epoch)| change_epoch) From fb37a3e4d774f0fc01e60ab0f7ecc2a592f66678 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 28 Aug 2022 18:21:44 +0100 Subject: [PATCH 0483/1995] Use AtPrevHeight instead of AtFixedHeight in PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a650348d74..8ee6550a94 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -105,17 +105,17 @@ where .compress_ethereum_events(eth_events) .expect(NOT_ENOUGH_VOTING_POWER_MSG); - let validator_set_update = - if self.storage.can_send_validator_set_update( - SendValsetUpd::AtFixedHeight(self.storage.last_height), - ) { - Some( - self.compress_valset_updates(valset_upds) - .expect(NOT_ENOUGH_VOTING_POWER_MSG), - ) - } else { - None - }; + let validator_set_update = if self + .storage + .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) + { + Some( + self.compress_valset_updates(valset_upds) + .expect(NOT_ENOUGH_VOTING_POWER_MSG), + ) + } else { + None + }; let protocol_key = self .mode From 50946acba9fa5f42c1dbcb54cb9367f109161132 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 Aug 2022 09:11:30 +0100 Subject: [PATCH 0484/1995] Parameterize can send valset upd vext test --- apps/src/lib/node/ledger/shell/queries.rs | 123 ++++++++++++---------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 36c3e36687..1c80b97f46 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -559,13 +559,73 @@ mod test_queries { use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; - /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as - /// expected. - #[test] - fn test_can_send_validator_set_update() { - let (mut shell, _, _) = test_utils::setup_at_height(0u64); + macro_rules! test_can_send_validator_set_update { + (epoch_assertions: $epoch_assertions:expr $(,)?) => { + /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as + /// expected. + #[test] + fn test_can_send_validator_set_update() { + let (mut shell, _, _) = test_utils::setup_at_height(0u64); + + let epoch_assertions = $epoch_assertions; + + // test `SendValsetUpd::Now` + for (idx, (curr_epoch, curr_block_height, can_send)) in + epoch_assertions.iter().copied().enumerate() + { + shell.storage.last_height = + BlockHeight(curr_block_height - 1); + assert_eq!( + curr_block_height, + shell.storage.get_current_decision_height().0 + ); + assert_eq!( + shell.storage.get_epoch(curr_block_height.into()), + Some(Epoch(curr_epoch)) + ); + assert_eq!( + shell + .storage + .can_send_validator_set_update(SendValsetUpd::Now), + can_send + ); + if epoch_assertions + .get(idx + 1) + .map(|&(_, _, change_epoch)| change_epoch) + .unwrap_or(false) + { + let time = namada::types::time::DateTimeUtc::now(); + let mut req = FinalizeBlock::default(); + req.header.time = time; + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + shell.storage.next_epoch_min_start_time = time; + } + } + + // test `SendValsetUpd::AtPrevHeight` + for (curr_epoch, curr_block_height, can_send) in + epoch_assertions.iter().copied() + { + assert_eq!( + shell.storage.get_epoch(curr_block_height.into()), + Some(Epoch(curr_epoch)) + ); + assert_eq!( + shell.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight( + curr_block_height.into() + ) + ), + can_send + ); + } + } + }; + } - let epoch_assertions = [ + test_can_send_validator_set_update! { + epoch_assertions: [ // (current epoch, current block height, can send valset upd) (0, 1, true), (0, 2, false), @@ -597,55 +657,6 @@ mod test_queries { (2, 26, false), (2, 27, false), (2, 28, false), - ]; - - // test `SendValsetUpd::Now` - for (idx, (curr_epoch, curr_block_height, can_send)) in - epoch_assertions.iter().copied().enumerate() - { - shell.storage.last_height = BlockHeight(curr_block_height - 1); - assert_eq!( - curr_block_height, - shell.storage.get_current_decision_height().0 - ); - assert_eq!( - shell.storage.get_epoch(curr_block_height.into()), - Some(Epoch(curr_epoch)) - ); - assert_eq!( - shell - .storage - .can_send_validator_set_update(SendValsetUpd::Now), - can_send - ); - if epoch_assertions - .get(idx + 1) - .map(|&(_, _, change_epoch)| change_epoch) - .unwrap_or(false) - { - let time = namada::types::time::DateTimeUtc::now(); - let mut req = FinalizeBlock::default(); - req.header.time = time; - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - shell.storage.next_epoch_min_start_time = time; - } - } - - // test `SendValsetUpd::AtPrevHeight` - for (curr_epoch, curr_block_height, can_send) in - epoch_assertions.iter().copied() - { - assert_eq!( - shell.storage.get_epoch(curr_block_height.into()), - Some(Epoch(curr_epoch)) - ); - assert_eq!( - shell.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight(curr_block_height.into()) - ), - can_send - ); - } + ], } } From d1f251747e6b363487dbc708ccd2b626006e3f8c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 Aug 2022 16:32:41 +0100 Subject: [PATCH 0485/1995] Fix logic of vext digests in ProcessProposal --- apps/src/lib/node/ledger/shell/process_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ad3670c077..5324c99dec 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -56,7 +56,8 @@ where // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. - let invalid_num_of_eth_ev_digests = eth_ev_digest_num != 1; + let invalid_num_of_eth_ev_digests = + self.storage.last_height.0 > 0 && eth_ev_digest_num != 1; if invalid_num_of_eth_ev_digests { tracing::warn!( proposer = ?hex::encode(&req.proposer_address), From cb67582120aaf4bb9ed9da34f3b00375d5ddb641 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 10:55:10 +0100 Subject: [PATCH 0486/1995] WIP: Logic changes + facade for vext shimming --- Makefile | 24 +++++ apps/Cargo.toml | 36 +++++-- apps/src/lib/node/ledger/events.rs | 11 ++- apps/src/lib/node/ledger/mod.rs | 6 +- .../lib/node/ledger/shell/finalize_block.rs | 53 +++++++++- apps/src/lib/node/ledger/shell/init_chain.rs | 6 +- shared/Cargo.toml | 31 +++++- shared/src/lib.rs | 7 ++ shared/src/types/transaction/protocol.rs | 10 +- .../types/vote_extensions/ethereum_events.rs | 96 +++++++++++++++---- 10 files changed, 239 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 0aa9843a9d..fa378fcbf8 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,30 @@ clippy: make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true +clippy-abcipp: + ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets \ + --manifest-path ./apps/Cargo.toml \ + --no-default-features \ + --features "std testing abcipp eth-fullnode" && \ + $(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 abcipp ibc-mocks-abcipp" && \ + $(cargo) +$(nightly) clippy --all-targets \ + --manifest-path ./tests/Cargo.toml \ + --no-default-features \ + --features "wasm-runtime abcipp namada_apps/abcipp namada_apps/eth-fullnode" && \ + $(cargo) +$(nightly) clippy \ + --all-targets \ + --manifest-path ./vm_env/Cargo.toml \ + --no-default-features \ + --features "abcipp" && \ + 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 diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ea081a27f6..02c272b189 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -39,18 +39,35 @@ name = "namadaw" path = "src/bin/anoma-wallet/main.rs" [features] -default = ["std"] +default = ["std", "abciplus"] dev = ["namada/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies testing = ["dev"] eth-fullnode = [] +abcipp = [ + "tendermint-abcipp", + "tendermint-config-abcipp", + "tendermint-proto-abcipp", + "tendermint-rpc-abcipp", + "tower-abci-abcipp", + "namada/abcipp" +] + +abciplus = [ + "tendermint", + "tendermint-config", + "tendermint-rpc", + "tendermint-proto", + "tower-abci", + "namada/abciplus" +] [dependencies] 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"]} +async-std = {version = "=1.11.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" bech32 = "0.8.0" @@ -109,10 +126,14 @@ 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", 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"]} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"], optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -120,7 +141,8 @@ 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"} +tower-abci-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200", optional = true} +tower-abci = {git = "https://github.com/heliaxdev/tower-abci", branch = "bat/abciplus", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 493c4855da..486bf485d9 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -7,9 +7,10 @@ use borsh::BorshSerialize; use namada::ledger::governance::utils::ProposalEvent; use namada::types::ibc::IbcEvent; use namada::types::transaction::{hash_tx, TxType}; -use tendermint_proto::abci::EventAttribute; use thiserror::Error; +use crate::facade::tendermint_proto::abci::EventAttribute; + /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block #[derive(Clone, Debug)] @@ -150,7 +151,7 @@ impl From for Event { } /// Convert our custom event into the necessary tendermint proto type -impl From for tendermint_proto::abci::Event { +impl From for crate::facade::tendermint_proto::abci::Event { fn from(event: Event) -> Self { Self { r#type: event.event_type.to_string(), @@ -158,8 +159,14 @@ impl From for tendermint_proto::abci::Event { .attributes .into_iter() .map(|(key, value)| EventAttribute { + #[cfg(feature = "abcipp")] key, + #[cfg(not(feature = "abcipp"))] + key: key.into_bytes(), + #[cfg(feature = "abcipp")] value, + #[cfg(not(feature = "abcipp"))] + value: value.into_bytes(), index: true, }) .collect(), diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b1c0d4f2e6..cd9e4af786 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -20,10 +20,8 @@ use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; -use tendermint_proto::abci::CheckTxType; use tokio::sync::mpsc::unbounded_channel; use tower::ServiceBuilder; -use tower_abci::{response, split, Server}; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; @@ -33,6 +31,8 @@ use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; use crate::{config, wasm_loader}; +use crate::facade::tendermint_proto::abci::CheckTxType; +use crate::facade::tower_abci::{response, split, Server}; /// Env. var to set a number of Tokio RT worker threads const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; @@ -108,9 +108,11 @@ impl Shell { Request::RevertProposal(_req) => { Ok(Response::RevertProposal(self.revert_proposal(_req))) } + #[cfg(feature = "abcipp")] Request::ExtendVote(_req) => { Ok(Response::ExtendVote(self.extend_vote(_req))) } + #[cfg(feature = "abcipp")] Request::VerifyVoteExtension(_req) => { tracing::debug!("Request VerifyVoteExtension"); Ok(Response::VerifyVoteExtension( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index c75bce506e..da3cea9246 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -11,12 +11,12 @@ use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; use namada::types::storage::{BlockHash, Epoch, Header}; use namada::types::transaction::protocol::ProtocolTxType; -use tendermint_proto::abci::Misbehavior as Evidence; -use tendermint_proto::crypto::PublicKey as TendermintPublicKey; use super::queries::QueriesExt; use super::*; use crate::node::ledger::events::EventType; +use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; +use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; impl Shell where @@ -316,7 +316,7 @@ where continue; } TxType::Protocol(protocol_tx) => match protocol_tx.tx { - ProtocolTxType::EthereumEvents(ref digest) => { + ProtocolTxType::EthEventsDigest(ref digest) => { for event in digest.events.iter().map(|signed| &signed.event) { @@ -502,6 +502,7 @@ where let evidence_params = self .storage .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() @@ -831,6 +832,39 @@ mod test_finalize_block { assert_eq!(counter, 2); } + /// Test that if a rejected protocol tx is applied and emits + /// the correct event + #[test] + fn test_rejected_protocol_tx() { + let (mut shell, _, _) = setup(); + let protocol_key = + shell.mode.get_protocol_key().expect("Test failed").clone(); + + let tx = ProtocolTxType::EthEventsDigest(ethereum_events::VextDigest { + signatures: Default::default(), + events: vec![], + }) + .sign(&protocol_key) + .to_bytes(); + + let req = FinalizeBlock { + txs: vec![ProcessedTx { + tx, + result: TxResult { + code: ErrorCodes::InvalidTx.into(), + info: Default::default(), + }, + }], + ..Default::default() + }; + let mut resp = shell.finalize_block(req).expect("Test failed"); + assert_eq!(resp.len(), 1); + let event = resp.remove(0); + assert_eq!(event.event_type.to_string(), String::from("applied")); + let code = event.attributes.get("code").expect("Test failed"); + assert_eq!(code, &String::from(ErrorCodes::InvalidTx)); + } + /// Test that once a validator's vote for an Ethereum event lands /// on-chain, it dequeues from the list of events to vote on. #[test] @@ -864,15 +898,26 @@ mod test_finalize_block { .sig; let signed = MultiSignedEthEvent { event, + #[cfg(feature = "abcipp")] signers: HashSet::from([address.clone()]), + #[cfg(not(feature = "abcipp"))] + signers: HashSet::from([( + address.clone(), + shell.storage.last_height, + )]), }; let digest = ethereum_events::VextDigest { + #[cfg(feature = "abcipp")] signatures: vec![(address, signature)].into_iter().collect(), + #[cfg(not(feature = "abcipp"))] + signatures: vec![((address, shell.storage.last_height), signature)] + .into_iter() + .collect(), events: vec![signed], }; let processed_tx = ProcessedTx { - tx: ProtocolTxType::EthereumEvents(digest) + tx: ProtocolTxType::EthEventsDigest(digest) .sign(&protocol_key) .to_bytes(), result: TxResult { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index c0baab6de0..ef3c44cdf6 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,13 +5,13 @@ use std::hash::Hash; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; -use tendermint_proto::abci; -use tendermint_proto::crypto::PublicKey as TendermintPublicKey; -use tendermint_proto::google::protobuf; use super::queries::QueriesExt; use super::*; use crate::wasm_loader; +use crate::facade::tendermint_proto::abci; +use crate::facade::tendermint_proto::google::protobuf; +use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; impl Shell where diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ff22c0911b..fcdf4e6246 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -9,7 +9,7 @@ version = "0.7.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = [] +default = ["abciplus"] # NOTE "dev" features that shouldn't be used in live networks are enabled by default for now dev = [] ferveo-tpke = [ @@ -22,6 +22,9 @@ ferveo-tpke = [ ibc-mocks = [ "ibc/mocks", ] +ibc-mocks-abcipp = [ + "ibc-abcipp/mocks", +] # for integration tests and test utilies testing = [ "proptest", @@ -47,6 +50,20 @@ secp256k1-sign-verify = [ "libsecp256k1/hmac", ] +abcipp = [ + "ibc-proto-abcipp", + "ibc-abcipp", + "tendermint-abcipp", + "tendermint-proto-abcipp" +] + +abciplus = [ + "ibc", + "ibc-proto", + "tendermint", + "tendermint-proto", +] + [dependencies] namada_proof_of_stake = {path = "../proof_of_stake"} ark-bls12-381 = {version = "0.3"} @@ -67,8 +84,10 @@ num-rational = "0.4.1" 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} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} +ibc = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abciplus", default-features = false, optional = true} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abciplus", default-features = false, optional = true} ics23 = "0.6.7" itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} @@ -90,8 +109,10 @@ 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", features = ["secp256k1"]} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 5c2d2fe4d7..6faaf2ff36 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -6,7 +6,14 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +#[cfg(not(feature = "abcipp"))] pub use {ibc, ibc_proto, tendermint, tendermint_proto}; +#[cfg(feature = "abcipp")] +pub use { + ibc_abcipp as ibc, ibc_proto_abcipp as ibc_proto, + tendermint_abcipp as tendermint, + tendermint_proto_abcipp as tendermint_proto, +}; pub mod bytes; pub mod ledger; diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index a0b8a39bc8..078f5711b9 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -32,6 +32,8 @@ mod protocol_txs { use serde_json; use super::*; + #[cfg(not(feature = "abcipp"))] + use crate::proto::Signed; use crate::proto::Tx; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; @@ -79,8 +81,12 @@ mod protocol_txs { DKG(DkgMessage), /// Tx requesting a new DKG session keypair NewDkgKeypair(Tx), - /// Ethereum events contained in vote extensions - EthereumEvents(ethereum_events::VextDigest), + /// Ethereum events contained in vote extensions that + /// are compressed before being included on chain + EthEventsDigest(ethereum_events::VextDigest), + /// Ethereum events seen be validators + #[cfg(not(feature = "abcipp"))] + EthereumEvents(Signed), /// Validator set updates contained in vote extensions ValidatorSetUpdate(validator_set_update::VextDigest), } diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 9086be0658..bfa1babf8d 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -17,7 +17,9 @@ use crate::types::storage::BlockHeight; /// This struct will be created and signed over by each /// active validator, to be included as a vote extension at the end of a /// Tendermint PreCommit phase. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct Vext { /// The block height for which this [`Vext`] was made. pub block_height: BlockHeight, @@ -48,7 +50,7 @@ impl Vext { } /// Aggregates an Ethereum event with the corresponding -// validators who saw this event. +/// validators who saw this event. #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -56,7 +58,12 @@ pub struct MultiSignedEthEvent { /// The Ethereum event that was signed. pub event: EthereumEvent, /// List of addresses of validators who signed this event + #[cfg(feature = "abcipp")] pub signers: HashSet
, + /// List of addresses of validators who signed this event + /// and block height at which they signed it + #[cfg(not(feature = "abcipp"))] + pub signers: HashSet<(Address, BlockHeight)>, } /// Compresses a set of signed [`Vext`] instances, to save @@ -66,23 +73,34 @@ pub struct MultiSignedEthEvent { )] pub struct VextDigest { /// The signatures and signing address of each [`Vext`] + #[cfg(feature = "abcipp")] pub signatures: HashMap, + /// The signatures, signing address, and signing block height + /// of each [`Vext`] + #[cfg(not(feature = "abcipp"))] + pub signatures: HashMap<(Address, BlockHeight), Signature>, /// The events that were reported pub events: Vec, } impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. - pub fn decompress(self, last_height: BlockHeight) -> Vec> { + pub fn decompress( + self, + #[cfg(feature = "abcipp")] last_height: BlockHeight, + ) -> Vec> { let VextDigest { signatures, events } = self; let mut extensions = vec![]; - for (addr, sig) in signatures.into_iter() { - let mut ext = Vext::empty(last_height, addr.clone()); + for (validator, sig) in signatures.into_iter() { + #[cfg(feature = "abcipp")] + let mut ext = Vext::empty(last_height, validator.clone()); + #[cfg(not(feature = "abcipp"))] + let mut ext = Vext::empty(validator.1, validator.0.clone()); for event in events.iter() { - if event.signers.contains(&addr) { + if event.signers.contains(&validator) { ext.ethereum_events.push(event.event.clone()); } } @@ -111,6 +129,7 @@ mod tests { use crate::types::hash::Hash; use crate::types::key; use crate::types::key::RefTo; + #[cfg(feature = "abcipp")] use crate::types::storage::BlockHeight; /// Test the hashing of an Ethereum event @@ -168,23 +187,62 @@ mod tests { // so each of them signs `ext` with their respective sk let ext_1 = Signed::new(&sk_1, ext(validator_1.clone())); let ext_2 = Signed::new(&sk_2, ext(validator_2.clone())); + #[cfg(not(feature = "abcipp"))] + let ext_3 = Signed::new(&sk_1, { + let mut ext = Vext::empty( + BlockHeight(last_block_height.0 - 1), + validator_1.clone(), + ); + ext.ethereum_events.push(ev_1.clone()); + ext.ethereum_events.push(ev_2.clone()); + ext.ethereum_events.sort(); + ext + }); + #[cfg(feature = "abcipp")] let ext = vec![ext_1, ext_2]; + #[cfg(not(feature = "abcipp"))] + let ext = vec![ext_1, ext_2, ext_3]; // we have the `Signed` instances we need, // let us now compress them into a single `VextDigest` + #[cfg(feature = "abcipp")] let signatures: HashMap<_, _> = [ (validator_1.clone(), ext[0].sig.clone()), (validator_2.clone(), ext[1].sig.clone()), ] .into_iter() .collect(); + #[cfg(not(feature = "abcipp"))] + let signatures: HashMap<_, _> = [ + ((validator_1.clone(), last_block_height), ext[0].sig.clone()), + ((validator_2.clone(), last_block_height), ext[1].sig.clone()), + ( + (validator_1.clone(), BlockHeight(last_block_height.0 - 1)), + ext[2].sig.clone(), + ), + ] + .into_iter() + .collect(); + #[cfg(feature = "abcipp")] let signers = { let mut s = HashSet::new(); - s.insert(validator_1); + s.insert(validator_1.clone()); s.insert(validator_2); s }; + + #[cfg(not(feature = "abcipp"))] + let signers = { + let mut s = HashSet::new(); + s.insert((validator_1.clone(), last_block_height)); + s.insert(( + validator_1.clone(), + BlockHeight(last_block_height.0 - 1), + )); + s.insert((validator_2, last_block_height)); + s + }; let events = vec![ MultiSignedEthEvent { event: ev_1, @@ -200,19 +258,25 @@ mod tests { // finally, decompress the `VextDigest` back into a // `Vec>` - let mut decompressed = digest + #[cfg(feature = "abcipp")] + let decompressed = digest .decompress(last_block_height) .into_iter() .collect::>>(); + #[cfg(not(feature = "abcipp"))] + let decompressed = digest + .decompress() + .into_iter() + .collect::>>(); - // decompressing yields an arbitrary ordering of `Vext` - // instances, which is fine - if decompressed[0].data.validator_addr != ext[0].data.validator_addr { - decompressed.swap(0, 1); + assert_eq!(decompressed.len(), ext.len()); + for vext in decompressed.into_iter() { + assert!(ext.contains(&vext)); + if vext.data.validator_addr == validator_1 { + assert!(vext.verify(&sk_1.ref_to()).is_ok()) + } else { + assert!(vext.verify(&sk_2.ref_to()).is_ok()) + } } - - assert_eq!(ext, decompressed); - assert!(decompressed[0].verify(&sk_1.ref_to()).is_ok()); - assert!(decompressed[1].verify(&sk_2.ref_to()).is_ok()); } } From 1a7d9358a36efe12069e97c23f5c6b7f3c0228c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 11:37:04 +0100 Subject: [PATCH 0487/1995] WIP: Shim vext logic --- apps/src/lib/node/ledger/shell/mod.rs | 56 ++++++++++++++++----- apps/src/lib/node/ledger/tendermint_node.rs | 18 ++++--- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 069a0447f3..a87f47a9e0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -45,16 +45,8 @@ use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; -use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; -use tendermint_proto::abci::{ - Misbehavior as Evidence, MisbehaviorType as EvidenceType, - RequestPrepareProposal, ValidatorUpdate, -}; -use tendermint_proto::crypto::public_key; -use tendermint_proto::types::ConsensusParams; use thiserror::Error; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; -use tower_abci::{request, response}; use super::protocol::ShellParams; use super::rpc; @@ -66,6 +58,13 @@ use crate::node::ledger::{protocol, storage, tendermint_node}; #[allow(unused_imports)] use crate::wallet::{ValidatorData, ValidatorKeys}; use crate::{config, wallet}; +use crate::facade::tendermint_proto::abci::{ + Misbehavior as Evidence, MisbehaviorType as EvidenceType, + RequestPrepareProposal, ValidatorUpdate, +}; +use crate::facade::tendermint_proto::abci::ConsensusParams; +use crate::facade::tendermint_proto::crypto::public_key; +use crate::facade::tower_abci::{request, response}; fn key_to_tendermint( pk: &common::PublicKey, @@ -267,6 +266,19 @@ impl ShellMode { _ => None, } } + + /// If this node is a validator, broadcast a tx + /// to the mempool using the broadcaster subprocess + pub fn broadcast(&self, data: Vec) { + if let Self::Validator { + broadcast_sender, .. + } = self + { + broadcast_sender + .send(data) + .expect("The broadcaster should be running for a validator"); + } + } } #[derive(Clone, Debug)] @@ -624,6 +636,26 @@ where self.storage.last_height, ); response.data = root.0; + + #[cfg(not(feature = "abcipp"))] + { + use namada::types::transaction::protocol::ProtocolTxType; + + if let ShellMode::Validator { .. } = &self.mode { + let ext = self.craft_extension(); + let ext = self + .mode + .get_protocol_key() + .map(|protocol_key| { + ProtocolTxType::EthereumEvents(ext) + .sign(protocol_key) + .to_bytes() + }) + .expect("Validators should have protocol keys"); + self.mode.broadcast(ext); + } + } + response } @@ -736,8 +768,6 @@ mod test_utils { use namada::types::storage::{BlockHash, Epoch, Header}; use namada::types::transaction::Fee; use tempfile::tempdir; - use tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; - use tendermint_proto::google::protobuf::Timestamp; use tokio::sync::mpsc::UnboundedReceiver; use super::*; @@ -745,6 +775,8 @@ mod test_utils { FinalizeBlock, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; + use crate::facade::tendermint_proto::google::protobuf::Timestamp; + use crate::facade::tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; #[derive(Error, Debug)] pub enum TestError { @@ -875,10 +907,10 @@ mod test_utils { }); let results = resp .tx_results - .iter() + .into_iter() .zip(req.txs.into_iter()) .map(|(res, tx_bytes)| ProcessedTx { - result: res.into(), + result: res, tx: tx_bytes, }) .collect(); diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 25dd49ba63..235493c52c 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -9,15 +9,15 @@ use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::time::DateTimeUtc; use serde_json::json; -use tendermint::Genesis; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_config::{Error as TendermintError, TendermintConfig}; use thiserror::Error; use tokio::fs::{self, File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::process::Command; use crate::config; +use crate::facade::tendermint::Genesis; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_config::{Error as TendermintError, TendermintConfig}; /// Env. var to output Tendermint log to stdout pub const ENV_VAR_TM_STDOUT: &str = "ANOMA_TM_STDOUT"; @@ -114,7 +114,10 @@ pub async fn run( .await; } } + #[cfg(feature = "abcipp")] write_tm_genesis(&home_dir, chain_id, genesis_time, &config).await; + #[cfg(not(feature = "abcipp"))] + write_tm_genesis(&home_dir, chain_id, genesis_time).await; update_tendermint_config(&home_dir, config).await?; @@ -377,7 +380,7 @@ async fn write_tm_genesis( home_dir: impl AsRef, chain_id: ChainId, genesis_time: DateTimeUtc, - config: &config::Tendermint, + #[cfg(feature = "abcipp")] config: &config::Tendermint, ) { let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("genesis.json"); @@ -398,8 +401,11 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); - genesis.consensus_params.timeout.commit = - config.consensus_timeout_commit.into(); + #[cfg(feature = "abcipp")] + { + genesis.consensus_params.timeout.commit = + config.consensus_timeout_commit.into(); + } let mut file = OpenOptions::new() .write(true) From fe266bcc7ac5a2794291a84b41634d53d798e261 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 12:40:22 +0100 Subject: [PATCH 0488/1995] WIP: More facade import fixes --- apps/src/lib/node/ledger/shell/queries.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 001bfdbf4e..b2d86e9272 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -4,7 +4,6 @@ use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::parameters::EpochDuration; -#[cfg(not(feature = "ABCI"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; @@ -13,12 +12,12 @@ use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, 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; +use crate::facade::tendermint_proto::google::protobuf; +use crate::facade::tendermint_proto::types::EvidenceParams; +use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; #[derive(Error, Debug)] pub enum Error { From 1ee442103d1527d2e47a51adcb1d985057a70ce8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 12:41:00 +0100 Subject: [PATCH 0489/1995] Add shim modules --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 126 ++++++++++++------ .../node/ledger/shims/abcipp_shim_types.rs | 118 +++++++++++++++- 2 files changed, 198 insertions(+), 46 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 80b2695752..d76f5342c7 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -6,14 +6,25 @@ use std::task::{Context, Poll}; use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; -use tendermint_proto::abci::ResponseFinalizeBlock; +use namada::types::hash::Hash; +#[cfg(not(feature = "abcipp"))] +use namada::types::storage::BlockHash; +#[cfg(not(feature = "abcipp"))] +use namada::types::transaction::hash_tx; +#[cfg(not(feature = "abcipp"))] +use tendermint_proto::abci::RequestBeginBlock; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tower::Service; +#[cfg(not(feature = "abcipp"))] use tower_abci::{BoxError, Request as Req, Response as Resp}; +#[cfg(feature = "abcipp")] +use tower_abci_abcipp::{BoxError, Request as Req, Response as Resp}; use super::super::Shell; +#[cfg(not(feature = "abcipp"))] +use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; +#[cfg(feature = "abcipp")] use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; -use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; @@ -23,6 +34,9 @@ use crate::config; #[derive(Debug)] pub struct AbcippShim { service: Shell, + #[cfg(not(feature = "abcipp"))] + begin_block_request: Option, + processed_txs: Vec, shell_recv: std::sync::mpsc::Receiver<( Req, tokio::sync::oneshot::Sender>, @@ -55,65 +69,93 @@ impl AbcippShim { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), + #[cfg(not(feature = "abcipp"))] + begin_block_request: None, + processed_txs: vec![], shell_recv, }, AbciService { shell_send }, ) } + #[cfg(not(feature = "abcipp"))] + /// 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 { - 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::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) => { + let response = + Ok(Resp::ProcessProposal((&resp).into())); + for (result, tx) in resp + .tx_results + .into_iter() + .zip(txs.into_iter()) + { + self.processed_txs + .push(ProcessedTx { tx, result }); + } + response + } + _ => unreachable!(), + }) + } + #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { - // 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 txs = vec![]; + std::mem::swap(&mut txs, &mut self.processed_txs); 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) => { - let mut resp: ResponseFinalizeBlock = - resp.into(); - - // Add 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)) + Ok(Resp::FinalizeBlock(resp.into())) + } + _ => Err(Error::ConvertResp(res)), + }) + } + #[cfg(not(feature = "abcipp"))] + 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(not(feature = "abcipp"))] + Req::DeliverTx(_) => Ok(Resp::DeliverTx(Default::default())), + #[cfg(not(feature = "abcipp"))] + 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)), }) 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 bea7ba56af..b9b265e559 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -1,9 +1,24 @@ +#[cfg(not(feature = "abcipp"))] use tower_abci::{Request, Response}; +#[cfg(feature = "abcipp")] +use tower_abci_abcipp::{Request, Response}; pub mod shim { use std::convert::TryFrom; + #[cfg(not(feature = "abcipp"))] use tendermint_proto::abci::{ + RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, + RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, + RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestPrepareProposal, + RequestProcessProposal, RequestQuery, ResponseApplySnapshotChunk, + ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseEndBlock, + ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, + ResponseLoadSnapshotChunk, ResponseOfferSnapshot, + ResponsePrepareProposal, ResponseQuery, + }; + #[cfg(feature = "abcipp")] + use tendermint_proto_abcipp::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, RequestExtendVote, RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, @@ -57,7 +72,9 @@ pub mod shim { ProcessProposal(RequestProcessProposal), #[allow(dead_code)] RevertProposal(request::RevertProposal), + #[cfg(feature = "abcipp")] ExtendVote(RequestExtendVote), + #[cfg(feature = "abcipp")] VerifyVoteExtension(RequestVerifyVoteExtension), FinalizeBlock(request::FinalizeBlock), Commit(RequestCommit), @@ -82,7 +99,9 @@ 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(feature = "abcipp")] Req::ExtendVote(inner) => Ok(Request::ExtendVote(inner)), + #[cfg(feature = "abcipp")] Req::VerifyVoteExtension(inner) => { Ok(Request::VerifyVoteExtension(inner)) } @@ -113,11 +132,15 @@ pub mod shim { Query(ResponseQuery), PrepareProposal(ResponsePrepareProposal), VerifyHeader(response::VerifyHeader), - ProcessProposal(ResponseProcessProposal), + ProcessProposal(response::ProcessProposal), RevertProposal(response::RevertProposal), + #[cfg(feature = "abcipp")] ExtendVote(ResponseExtendVote), + #[cfg(feature = "abcipp")] VerifyVoteExtension(ResponseVerifyVoteExtension), FinalizeBlock(response::FinalizeBlock), + #[cfg(not(feature = "abcipp"))] + EndBlock(ResponseEndBlock), Commit(ResponseCommit), Flush(ResponseFlush), Echo(ResponseEcho), @@ -156,7 +179,9 @@ pub mod shim { Response::PrepareProposal(inner) => { Ok(Resp::PrepareProposal(inner)) } + #[cfg(feature = "abcipp")] Response::ExtendVote(inner) => Ok(Resp::ExtendVote(inner)), + #[cfg(feature = "abcipp")] Response::VerifyVoteExtension(inner) => { Ok(Resp::VerifyVoteExtension(inner)) } @@ -169,10 +194,15 @@ pub mod shim { pub mod request { use std::convert::TryFrom; + #[cfg(not(feature = "abcipp"))] + use namada::tendermint_proto::abci::RequestBeginBlock; use namada::types::hash::Hash; use namada::types::storage::{BlockHash, Header}; use namada::types::time::DateTimeUtc; - use tendermint_proto::abci::{ + #[cfg(not(feature = "abcipp"))] + use tendermint_proto::abci::Misbehavior as Evidence; + #[cfg(feature = "abcipp")] + use tendermint_proto_abcipp::abci::{ Misbehavior as Evidence, RequestFinalizeBlock, }; @@ -194,6 +224,7 @@ pub mod shim { pub txs: Vec, } + #[cfg(feature = "abcipp")] impl From for FinalizeBlock { fn from(req: RequestFinalizeBlock) -> FinalizeBlock { FinalizeBlock { @@ -211,17 +242,48 @@ pub mod shim { } } } + + #[cfg(not(feature = "abcipp"))] + 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 = "abcipp"))] use tendermint_proto::abci::{ + ConsensusParams, Event as TmEvent, ResponseProcessProposal, + ValidatorUpdate, + }; + #[cfg(feature = "abcipp")] + use tendermint_proto_abcipp::abci::{ Event as TmEvent, ExecTxResult, ResponseFinalizeBlock, ValidatorUpdate, }; - use tendermint_proto::types::ConsensusParams; + #[cfg(feature = "abcipp")] + use tendermint_proto_abcipp::types::ConsensusParams; - use crate::node::ledger::events::{Event, EventLevel}; + use super::*; + use crate::node::ledger::events::Event; + #[cfg(feature = "abcipp")] + use crate::node::ledger::events::EventLevel; #[derive(Debug, Default)] pub struct VerifyHeader; @@ -232,6 +294,7 @@ pub mod shim { pub info: String, } + #[cfg(feature = "abcipp")] impl From for ExecTxResult { fn from(TxResult { code, info }: TxResult) -> Self { ExecTxResult { @@ -242,6 +305,7 @@ pub mod shim { } } + #[cfg(feature = "abcipp")] impl From<&ExecTxResult> for TxResult { fn from(ExecTxResult { code, info, .. }: &ExecTxResult) -> Self { TxResult { @@ -251,6 +315,36 @@ pub mod shim { } } + #[derive(Debug, Default)] + pub struct ProcessProposal { + pub status: i32, + pub tx_results: Vec, + } + + #[cfg(feature = "abcipp")] + impl From<&ProcessProposal> for ResponseProcessProposal { + fn from(resp: &ProcessProposal) -> Self { + Self { + status: resp.status, + tx_results: resp + .tx_results + .iter() + .map(|res| ExecTxResult::from(res.clone())) + .collect(), + ..Default::default() + } + } + } + + #[cfg(not(feature = "abcipp"))] + impl From<&ProcessProposal> for ResponseProcessProposal { + fn from(resp: &ProcessProposal) -> Self { + Self { + status: resp.status, + } + } + } + #[derive(Debug, Default)] pub struct RevertProposal; @@ -261,6 +355,7 @@ pub mod shim { pub consensus_param_updates: Option, } + #[cfg(feature = "abcipp")] impl From for ResponseFinalizeBlock { fn from(resp: FinalizeBlock) -> Self { ResponseFinalizeBlock { @@ -299,5 +394,20 @@ pub mod shim { } } } + + #[cfg(not(feature = "abcipp"))] + impl From for tendermint_proto::abci::ResponseEndBlock { + 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, + } + } + } } } From c63646fbba425cab20a3dbe55c0968eaf41d983c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 12:49:56 +0100 Subject: [PATCH 0490/1995] Use facade imports --- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 18 ++++-------- .../node/ledger/shims/abcipp_shim_types.rs | 29 +++++-------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index d76f5342c7..6867e6ed6a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -7,26 +7,16 @@ use std::task::{Context, Poll}; use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; use namada::types::hash::Hash; -#[cfg(not(feature = "abcipp"))] -use namada::types::storage::BlockHash; -#[cfg(not(feature = "abcipp"))] -use namada::types::transaction::hash_tx; -#[cfg(not(feature = "abcipp"))] -use tendermint_proto::abci::RequestBeginBlock; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tower::Service; -#[cfg(not(feature = "abcipp"))] -use tower_abci::{BoxError, Request as Req, Response as Resp}; -#[cfg(feature = "abcipp")] -use tower_abci_abcipp::{BoxError, Request as Req, Response as Resp}; use super::super::Shell; -#[cfg(not(feature = "abcipp"))] -use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; -#[cfg(feature = "abcipp")] use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; +#[cfg(not(feature = "abcipp"))] +use crate::facade::tendermint_proto::abci::RequestBeginBlock; +use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used @@ -81,6 +71,7 @@ impl AbcippShim { #[cfg(not(feature = "abcipp"))] /// Get the hash of the txs in the block pub fn get_hash(&self) -> Hash { + use namada::types::transaction::hash_tx; let bytes: Vec = self .processed_txs .iter() @@ -142,6 +133,7 @@ impl AbcippShim { Req::DeliverTx(_) => Ok(Resp::DeliverTx(Default::default())), #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { + use namada::types::storage::BlockHash; let mut txs = vec![]; std::mem::swap(&mut txs, &mut self.processed_txs); let mut end_block_request: FinalizeBlock = 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 b9b265e559..950412485c 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -1,13 +1,13 @@ -#[cfg(not(feature = "abcipp"))] -use tower_abci::{Request, Response}; -#[cfg(feature = "abcipp")] -use tower_abci_abcipp::{Request, Response}; +use crate::facade::tower_abci::{Request, Response}; pub mod shim { use std::convert::TryFrom; - #[cfg(not(feature = "abcipp"))] - use tendermint_proto::abci::{ + use thiserror::Error; + + use super::{Request as Req, Response as Resp}; + use crate::node::ledger::shell; + use crate::facade::tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestPrepareProposal, @@ -18,22 +18,9 @@ pub mod shim { ResponsePrepareProposal, ResponseQuery, }; #[cfg(feature = "abcipp")] - use tendermint_proto_abcipp::abci::{ - RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, - RequestExtendVote, RequestFlush, RequestInfo, RequestInitChain, - RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, - RequestPrepareProposal, RequestProcessProposal, RequestQuery, - RequestVerifyVoteExtension, ResponseApplySnapshotChunk, - ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseExtendVote, - ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, - ResponseLoadSnapshotChunk, ResponseOfferSnapshot, - ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, - ResponseVerifyVoteExtension, + use crate::facade::tendermint_proto::abci::{ + RequestExtendVote, ResponseExtendVote, RequestVerifyVoteExtension, ResponseVerifyVoteExtension, }; - use thiserror::Error; - - use super::{Request as Req, Response as Resp}; - use crate::node::ledger::shell; pub type TxBytes = Vec; From d1a62f2726bd70dedc6e83d6e3385818eaae0a3a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 12:55:23 +0100 Subject: [PATCH 0491/1995] Add facade module --- apps/src/lib/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index eca89896eb..a6e00c2b78 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -18,3 +18,19 @@ pub mod wasm_loader; // Taken from . #[doc(inline)] pub use std; + +pub mod facade { + //! Facade module to reason about `abcipp` feature flag logic. + + #[cfg(feature = "abcipp")] + pub use { + tendermint_abcipp as tendermint, tendermint_config_abcipp as tendermint_config, + tendermint_proto_abcipp as tendermint_proto, tendermint_rpc_abcipp as tendermint_rpc, + tower_abci_abcipp as tower_abci, + }; + #[cfg(not(feature = "abcipp"))] + pub use { + tendermint, tendermint_config, tendermint_proto, + tendermint_rpc, tower_abci, + }; +} From 81e1bf88fe1b08c5d386d25cde24f7f2b9657185 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 30 Aug 2022 14:28:26 +0200 Subject: [PATCH 0492/1995] [feat]: Updated specs to accomodate lack of vexts and details about batching --- .../src/interoperability/ethereum-bridge.md | 336 ++++++++++-------- 1 file changed, 196 insertions(+), 140 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index 5877998b7a..42775f951d 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -12,7 +12,9 @@ The Namada Ethereum bridge system consists of: [ICS20](https://docs.cosmos.network/v0.42/modules/ibc/) fungible token transfers. * A set of Ethereum smart contracts. -* A relayer for submitting transactions to Ethereum +* An automated process to send validator set updates to the Ethereum smart + contracts. +* A relayer binary to aid in submitting transactions to Ethereum This basic bridge architecture should provide for almost-Namada consensus security for the bridge and free Ethereum state reads on Namada, plus @@ -66,7 +68,7 @@ governance. `TransferToNamada` events may include a custom minimum number of confirmations, that must be at least the protocol-specified minimum number of -confirmations. +confirmations but is initially set to __100__. Validators must not vote to include events that have not met the required number of confirmations. Voting on unconfirmed events is considered a @@ -88,112 +90,99 @@ keys involved are: `EthereumEvent`. Changes to this `/eth_msgs` storage subspace are only ever made by -nodes as part of the ledger code based on the aggregate of vote -extensions for the last Tendermint round. That is, changes to `/eth_msgs` happen -in block `n+1` in a deterministic manner based on the vote extensions of the -Tendermint round for block `n`. The `/eth_msgs` storage subspace will belong +nodes as part of the ledger code based on the aggregate of votes +by validators for specific events. That is, changes to +`/eth_msgs` happen +in block `n` in a deterministic manner based on the votes included in the +block proposal for block `n`. Depending on the underlying Tendermint +version, these votes will either be included as vote extensions or as +protocol transactions. + +The `/eth_msgs` storage subspace will belong to the `EthBridge` validity predicate. It should disallow any changes to this storage from wasm transactions. ### Including events into storage -For every Namada block proposal, the vote extension of a validator should include -the events of the Ethereum blocks they have seen via their full node such that: -1. The storage value `/eth_msgs/\$msg_hash/seen_by` does not include their - address. -2. It's correctly formatted. -3. It's reached the required number of confirmations on the Ethereum chain +For every Namada block proposal, block proposer should include the votes for +events from other validators into their proposal. If the underlying Tendermint +version supports vote extensions, consensus invariants guarantee that a +quorum of votes from the previous block height can be included. Otherwise, +validators can only submit votes by broadcasting protocol transactions, +which comes with less guarantees. + +The vote of a validator should include the events of the Ethereum blocks they +have seen via their full node such that: +1. It's correctly formatted. +2. It's reached the required number of confirmations on the Ethereum chain Each event that a validator is voting to include must be individually signed by them. If the validator is not voting to include any events, they must still -provide a signed voted extension indicating this. +provide a signed empty vector of events to indicate this. -The vote extension data field will be a Borsh-serialization of something like the following. +The votes will include be a Borsh-serialization of something like +the following. ```rust -pub struct VoteExtension(Vec); - -/// A struct used by validators to sign that they have seen a particular -/// ethereum event. These are included in vote extensions -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] -pub struct SignedEthEvent { +/// This struct will be created and signed over by each +/// active validator, to be included as a vote extension at the end of a +/// Tendermint PreCommit phase or as Protocol Tx. +pub struct Vext { + /// The block height for which this [`Vext`] was made. + pub block_height: BlockHeight, /// The address of the signing validator - signer: Address, - /// The proportion of the total voting power held by the validator - power: FractionalVotingPower, - /// The event being signed and the block height at which - /// it was seen. We include the height as part of enforcing - /// that a block proposer submits vote extensions from - /// **the previous round only** - event: Signed<(EthereumEvent, BlockHeight)>, + pub validator_addr: Address, + /// The new ethereum events seen. These should be + /// deterministically ordered. + pub ethereum_events: Vec, } ``` -These vote extensions will be given to the next block proposer who will -aggregate those that it can verify and will inject a protocol transaction -(the "vote extensions" transaction). - -```rust -pub struct MultiSigned { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sigs: Vec, -} - -pub struct MultiSignedEthEvent { - /// Address and voting power of the signing validators - pub signers: Vec<(Address, FractionalVotingPower)>, - /// Events as signed by validators - pub event: MultiSigned<(EthereumEvent, BlockHeight)>, -} - -pub enum ProtocolTxType { - EthereumEvents(Vec) -} -``` +These votes will be given to the next block proposer who will +aggregate those that it can verify and will inject a signed protocol +transaction into their proposal. -This vote extensions transaction will be signed by the block proposer. Validators will check this transaction and the validity of the new votes as part of `ProcessProposal`, this includes checking: - signatures - that votes are really from active validators - the calculation of backed voting power -It is also checked that each vote extension came from the previous round, -requiring validators to sign over the Namada block height with their vote -extension. Furthermore, the vote extensions included by the block proposer -should have at least 2 / 3 of the total voting power of the previous round -backing it. Otherwise the block proposer would not have passed the -`FinalizeBlock` phase of the last round. These checks are to prevent censorship -of events from validators by the block proposer. +If vote extensions are supported, it is also checked that each vote extension +came from the previous round, requiring validators to sign over the Namada block +height with their vote extension. + +Furthermore, the vote extensions included by +the block proposer should have at least 2 / 3 of the total voting power of the +previous round backing it. Otherwise the block proposer would not have passed the +`FinalizeBlock` phase of the last round. + +These checks are to prevent censorship +of events from validators by the block proposer. If vote extensions are not +enabled, unfortunately these checks cannot be made. In `FinalizeBlock`, we derive a second transaction (the "state update" -transaction) from the vote extensions transaction that: +transaction) from the vote aggregation that: - calculates the required changes to `/eth_msgs` storage and applies it - acts on any `/eth_msgs/\$msg_hash` where `seen` is going from `false` to `true` (e.g. appropriately minting wrapped Ethereum assets) This state update transaction will not be recorded on chain but will be -deterministically derived from the vote extensions transaction, which is -recorded on chain. All ledger nodes will derive and apply this transaction to -their own local blockchain state, whenever they receive a block with a vote -extensions transaction. This transaction cannot require a protocol signature -as even non-validator full nodes of Namada will be expected to do this. +deterministically derived from the protocol transaction including the +aggregation of votes, which is recorded on chain. All ledger nodes will +derive and apply the appropriate state changes to their own local +blockchain storage. The value of `/eth_msgs/\$msg_hash/seen` will also indicate if the event -has been acted on on the Namada side. The appropriate transfers of tokens to the -given user will be included on chain free of charge and requires no +has been acted upon on the Namada side. The appropriate transfers of tokens +to the given user will be included on chain free of charge and requires no additional actions from the end user. ## Namada Validity Predicates -There will be three internal accounts with associated native validity predicates: +There will be two internal accounts with associated native validity predicates: -- `#EthSentinel` - whose validity predicate will verify the inclusion of events -from Ethereum. This validity predicate will control the `/eth_msgs` storage -subspace. -- `#EthBridge` - the storage of which will contain ledgers of balances for -wrapped Ethereum assets (ERC20 tokens) structured in a +- `#EthBridge` - Controls the `/eth_msgs/` storage and ledgers of balances + for wrapped Ethereum assets (ERC20 tokens) structured in a ["multitoken"](https://github.com/anoma/anoma/issues/1102) hierarchy - `#EthBridgeEscrow` which will hold in escrow wrapped Namada tokens which have been sent to Ethereum. @@ -241,66 +230,30 @@ For 10 DAI i.e. ERC20([0x6b175474e89094c44da98b954eedeac495271d0f](https://ether Any wrapped Namada tokens being redeemed from Ethereum must have an equivalent amount of the native token held in escrow by `#EthBridgeEscrow`. The protocol transaction should simply make a transfer from `#EthBridgeEscrow` to the `receiver` for the appropriate amount and asset. -### Transferring from Namada to Ethereum - -To redeem wrapped Ethereum assets, a user should make a transaction to burn -their wrapped tokens, which the `#EthBridge` validity predicate will accept. - -Once this burn is done, it is incumbent on the end user to -request an appropriate "proof" of the transaction. This proof must be -submitted to the appropriate Ethereum smart contract by the user to -redeem their native Ethereum assets. This also means all Ethereum gas costs -are the responsibility of the end user. - -The proofs to be used will be custom bridge headers that are calculated -deterministically from the block contents, including messages sent by Namada and -possibly validator set updates. They will be designed for maximally -efficient Ethereum decoding and verification. - -For each block on Namada, validators must submit the corresponding bridge -header signed with a special secp256k1 key as part of their vote extension. -Validators must reject votes which do not contain correctly signed bridge -headers. The finalized bridge header with aggregated signatures will appear in the -next block as a protocol transaction. Aggregation of signatures is the -responsibility of the next block proposer. - -The bridge headers need only be produced when the proposed block contains -requests to transfer value over the bridge to Ethereum. The exception is -when validator sets change. Since the Ethereum smart contract should -accept any header signed by bridge header signed by 2 / 3 of the staking -validators, it needs up-to-date knowledge of: -- The current validators' public keys -- The current stake of each validator - -This means the at the end of every Namada epoch, a special transaction must be -sent to the Ethereum contract detailing the new public keys and stake of the -new validator set. This message must also be signed by at least 2 / 3 of the -current validators as a "transfer of power". It is to be included in validators -vote extensions as part of the bridge header. Signing an invalid validator -transition set will be consider a slashable offense. +### Transferring assets from Namada to Ethereum -Due to asynchronicity concerns, this message should be submitted well in -advance of the actual epoch change, perhaps even at the beginning of each -new epoch. Bridge headers to ethereum should include the current Namada epoch -so that the smart contract knows how to verify the headers. In short, there -is a pipelining mechanism in the smart contract. +Moving assets from Namada to Ethereum will not be automatic, as opposed the +movement of value in the opposite direction. Instead, users must send an +appropriate transaction to Namada to initiate a transfer across the bridge +to Ethereum. Once this transaction is approved, a "proof" will be created +and posted on Namada. -Such a message is not prompted by any user transaction and thus will have -to be carried out by a _bridge relayer_. Once the transfer of power -message is on chain, any time afterwards a Namada bridge process may take -it to craft the appropriate message to the Ethereum smart contracts. +It is incumbent on the end user to request an appropriate "proof" of the +transaction. This proof must be submitted to the appropriate Ethereum smart +contract by the user to redeem Ethereum assets / mint wrapped assets. This also +means all Ethereum gas costs are the responsibility of the end user. -The details on bridge relayers are below in the corresponding section. +A relayer binary will be developed to aid users in accessing the proofs +generated by Namada validators as well as posting this proof to Ethereum. It +will also aid in batching transactions. -Signing incorrect headers is considered a slashable offense. Anyone witnessing -an incorrect header that is signed may submit a complaint (a type of transaction) -to initiate slashing of the validator who made the signature. +#### Moving value to Ethereum -#### Namada tokens +To redeem wrapped Ethereum assets, a user should make a transaction to burn +their wrapped tokens, which the `#EthBridge` validity predicate will accept. Mints of a wrapped Namada token on Ethereum (including NAM, Namada's native token) will be represented by a data type like: - ```rust struct MintWrappedNam { /// The Namada address owning the token @@ -314,15 +267,116 @@ struct MintWrappedNam { } ``` -If a user wishes to mint a wrapped Namada token on Ethereum, they must submit a transaction on Namada that: +If a user wishes to mint a wrapped Namada token on Ethereum, they must +submit a transaction on Namada that: - stores `MintWrappedNam` on chain somewhere - TBD - sends the correct amount of Namada token to `#EthBridgeEscrow` -Just as in redeeming Ethereum assets above, it is incumbent on the end user to -request an appropriate proof of the transaction. This proof must be -submitted to the appropriate Ethereum smart contract by the user. -The corresponding amount of wrapped NAM tokens will be transferred to the -`receiver` on Ethereum by the smart contract. +#### Batching + +Ethereum gas fees make it prohibitively expensive in many cases to submit +the proof for a single transaction over the bridge. Instead, it is typically +more economical to submit proofs of many transactions in bulk. This batching +is described in this section. + +A pool of transaction from Namada to Ethereum will be kept by Namada. Every +transaction to Ethereum that Namada validators approve will be added to this +pool. We call this the _Bridge Pool_. + +The Bridge Pool should be thought of as a sort of mempool. When users who +wish to move assets to Ethereum submit their transactions, they will pay some +additional amount of NAM (of their choosing) as a way of covering the gas +costs on Ethereum. Namada validators will hold these fees in a Bridge Pool +Escrow. + +When a batch of transactions from the Bridge Pool is submitted by a user to +Ethereum, Namada validators will receive notifications via their full nodes. +They will then pay out the fees for each submitted transaction to the user who +relayed these transactions (still in NAM). These will be paid out from the +Bridge Pool Escrow. + +The idea is that users will only relay transactions from the Bridge Pool +that make economic sense. This prevents DoS attacks by underpaying fees as +well as obviating the need for Ethereum gas price oracles. It also means +that transfers to Ethereum are not ordered, preventing other attack vectors. + +The Bridge Pool will be organized as a Merkle tree. Every time it is updated, +the root of tree must be signed by a quorum of validators. When a user +wishes to construct a batch of transactions to relay to Ethereum, they +include the signed tree root and inclusion proofs for the subset of the pool +they are relaying. This can be easily verified by the Ethereum smart contracts. + +If vote extensions are available, these are used to collect the signatures +over the Merkle tree root. If they are not, these must be submitted as protocol +transactions, introducing latency to the pool. A user wishing to relay will +need to wait until a Merkle tree root is signed for a tree that +includes all the transactions they wish to relay. + +#### Replay Protection and timeouts + +It is important that nonces are used to prevent copies of the same +transaction being submitted multiple times. Since we do not want to enforce +an order on the transactions, these nonces should land in a range. As a +consequence of this, it is possible that transactions in the Bridge Pool will +time out. Transactions that timed out should revert the state changes on +Namada including refunding the paid in fees. + +#### Proofs +A proof for this bridge is a quorum of signatures by a valid validator set +attached to a message understandable to the Ethereum smart contracts. For +transferring value to Ethereum, a proof is a signed Merkle tree root and +inclusion proofs of assert transfer messages understandable to the Ethereum +smart contractions, as described in the section on batching. + +A message for transferring value to Ethereum should be of the form +```rust +pub struct TransferToEthereum { + /// The type of token + asset: EthereumAddress, + /// The recipient address + recipient: EthereumAddress, + /// The amount to be transferred + amount: Amount, + /// a nonce for replay protection + nonce: Nonce, +} +``` + +Additionally, when the validator set changes, the smart contracts on +Ethereum must be updated so that it can continue to recognize valid proofs. +Since the Ethereum smart contract should accept any header signed by bridge +header signed by 2 / 3 of the staking validators, it needs up-to-date +knowledge of: +- The current validators' public keys +- The current stake of each validator + +This means the at the end of every Namada epoch, a special transaction must be +sent to the Ethereum smart contracts detailing the new public keys and stake +of the new validator set. This message must also be signed by at least 2 / 3 +of the current validators as a "transfer of power". + +If vote extensions are available, this signed data can be constructed +using them. Otherwise, validators must send protocol txs to be included on +the ledger. Once a quorum exist on chain, they can be aggregated into a +single message that can be relayed to Ethereum. Signing an +invalid validator transition set will be considered a slashable offense. + +Due to asynchronicity concerns, this message should be submitted well in +advance of the actual epoch change, perhaps even at the beginning of each +new epoch. Bridge headers to ethereum should include the current Namada epoch +so that the smart contract knows how to verify the headers. In short, there +is a pipelining mechanism in the smart contract. + +Such a message is not prompted by any user transaction and thus will have +to be carried out by a _bridge relayer_. Once the transfer of power +message is on chain, any time afterwards a Namada bridge process may take +it to craft the appropriate message to the Ethereum smart contracts. + +The details on bridge relayers are below in the corresponding section. + +Signing incorrect headers is considered a slashable offense. Anyone witnessing +an incorrect header that is signed may submit a complaint (a type of transaction) +to initiate slashing of the validator who made the signature. ## Namada Bridge Relayers @@ -336,21 +390,23 @@ However, any user may choose to submit this transaction anyway. This necessitates a Namada node whose job it is to submit these transactions on Ethereum at the conclusion of each Namada epoch. This node is called the -__Designated Relayer__. In theory, since this message is publicly available on the blockchain, -anyone can submit this transaction, but only the Designated Relayer will be -directly compensated by Namada. +__Designated Relayer__. In theory, since this message is publicly available +on the blockchain, anyone can submit this transaction, but only the +Designated Relayer will be directly compensated by Namada. All Namada validators will have an option to serve as bridge relayer and the Namada ledger will include a process that does the relaying. Since all Namada validators are running Ethereum full nodes, they can monitor that the message was relayed correctly by the Designated Relayer. -During the `FinalizeBlock` call in the ledger, if the epoch changes, a -flag should be set alerting the next block proposer that they are the -Designated Relayer for this epoch. If their message gets accepted by the -Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward -them. The reward amount shall be a protocol parameter that can be changed -via governance. It should be high enough to cover necessary gas fees. +During the `FinalizeBlock` call in the ledger, if the transfer of power +message is placed on chain, a flag should be set alerting the next block +proposer that they are the Designated Relayer for this epoch. + +If the Ethereum event spawned by relaying their message gets accepted by the +Ethereum state inclusion onto Namada, new NAM tokens will be minted to +reward them. The reward amount shall be a protocol parameter that can be +changed via governance. It should be high enough to cover necessary gas fees. ## Ethereum Smart Contracts The set of Ethereum contracts should perform the following functions: From 42e396909bbda732ebb056d0a4b0854e990f2ecf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 15:02:55 +0100 Subject: [PATCH 0493/1995] Fix unit tests --- apps/src/lib/node/ledger/shell/queries.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index c3c24673f1..f03a37a5b1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -554,6 +554,7 @@ pub enum SendValsetUpd { AtPrevHeight, /// Check if it is possible to send a validator set update /// vote extension at any given block height. + #[allow(dead_code)] AtFixedHeight(BlockHeight), } @@ -573,7 +574,7 @@ mod test_queries { let epoch_assertions = $epoch_assertions; - // test `SendValsetUpd::Now` + // test `SendValsetUpd::Now` and `SendValsetUpd::AtPrevHeight` for (idx, (curr_epoch, curr_block_height, can_send)) in epoch_assertions.iter().copied().enumerate() { @@ -593,6 +594,20 @@ mod test_queries { .can_send_validator_set_update(SendValsetUpd::Now), can_send ); + if let Some((epoch, height, can_send)) = + epoch_assertions.get(idx.wrapping_sub(1)).copied() + { + assert_eq!( + shell.storage.get_epoch(height.into()), + Some(Epoch(epoch)) + ); + assert_eq!( + shell.storage.can_send_validator_set_update( + SendValsetUpd::AtPrevHeight + ), + can_send + ); + } if epoch_assertions .get(idx + 1) .map(|&(_, _, change_epoch)| change_epoch) @@ -607,7 +622,7 @@ mod test_queries { } } - // test `SendValsetUpd::AtPrevHeight` + // test `SendValsetUpd::AtFixedHeight` for (curr_epoch, curr_block_height, can_send) in epoch_assertions.iter().copied() { @@ -617,7 +632,7 @@ mod test_queries { ); assert_eq!( shell.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight( + SendValsetUpd::AtFixedHeight( curr_block_height.into() ) ), From 6a458f11aba9675366fe6dc4bca4d9cc37fd163f Mon Sep 17 00:00:00 2001 From: James Date: Thu, 11 Aug 2022 12:27:36 +0100 Subject: [PATCH 0494/1995] Add storage keys for EthBridge account --- apps/src/lib/wallet/defaults.rs | 2 +- shared/src/ledger/eth_bridge/mod.rs | 11 +- shared/src/ledger/eth_bridge/storage.rs | 12 -- .../src/ledger/eth_bridge/storage/eth_msgs.rs | 199 ++++++++++++++++++ shared/src/ledger/eth_bridge/storage/mod.rs | 11 + .../eth_bridge/storage/wrapped_erc20s.rs | 160 ++++++++++++++ shared/src/ledger/eth_bridge/vp.rs | 13 +- 7 files changed, 384 insertions(+), 24 deletions(-) delete mode 100644 shared/src/ledger/eth_bridge/storage.rs create mode 100644 shared/src/ledger/eth_bridge/storage/eth_msgs.rs create mode 100644 shared/src/ledger/eth_bridge/storage/mod.rs create mode 100644 shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 6a4f9dacc3..eb5a6f3d7a 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -21,7 +21,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::vp::ADDRESS), - ("eth_bridge".into(), eth_bridge::vp::ADDRESS), + ("eth_bridge".into(), eth_bridge::ADDRESS), ]; // Genesis validators let validator_addresses = diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index ff8505b08e..3d9487276d 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,4 +1,11 @@ -//! Bridge from Ethereum - +//! Validity predicate and storage keys for the Ethereum bridge account pub mod storage; pub mod vp; + +use crate::types::address::{Address, InternalAddress}; + +/// The [`InternalAddress`] of the Ethereum bridge account +pub const INTERNAL_ADDRESS: InternalAddress = InternalAddress::EthBridge; + +/// The [`Address`] of the Ethereum bridge account +pub const ADDRESS: Address = Address::Internal(INTERNAL_ADDRESS); diff --git a/shared/src/ledger/eth_bridge/storage.rs b/shared/src/ledger/eth_bridge/storage.rs deleted file mode 100644 index e67abf921c..0000000000 --- a/shared/src/ledger/eth_bridge/storage.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! storage helpers -use super::vp::ADDRESS; -use crate::types::storage::{Key, KeySeg}; - -const QUEUE_STORAGE_KEY: &str = "queue"; - -/// Get the key corresponding to @EthBridge/queue -pub fn queue_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&QUEUE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs new file mode 100644 index 0000000000..9c2260c678 --- /dev/null +++ b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs @@ -0,0 +1,199 @@ +//! Functionality for accessing the `eth_msgs/...` subspace +use crate::types::ethereum_events::EthereumEvent; +use crate::types::hash::Hash; +use crate::types::storage::Key; + +#[allow(missing_docs)] +pub const PREFIX_KEY_SEGMENT: &str = "eth_msgs"; + +/// Get the key prefix corresponding to where details of seen [`EthereumEvent`]s +/// are stored +pub fn prefix() -> Key { + super::prefix() + .push(&PREFIX_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") +} + +#[allow(missing_docs)] +pub const BODY_KEY_SEGMENT: &str = "body"; +#[allow(missing_docs)] +pub const SEEN_KEY_SEGMENT: &str = "seen"; +#[allow(missing_docs)] +pub const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; +#[allow(missing_docs)] +pub const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; + +/// Generator for the keys under which details of an [`EthereumEvent`] is stored +pub struct Keys { + /// The prefix under which the details are an [`EthereumEvent`] is stored + pub prefix: Key, +} + +impl Keys { + /// Get the `body` key- there should be an [`EthereumEvent`] stored here. + pub fn body(&self) -> Key { + self.prefix + .push(&BODY_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + } + + /// Get the `seen` key - there should be a [`bool`] stored here. + pub fn seen(&self) -> Key { + self.prefix + .push(&SEEN_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + } + + /// Get the `seen_by` key - there should be a [`Vec
`] stored here. + pub fn seen_by(&self) -> Key { + self.prefix + .push(&SEEN_BY_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + } + + /// Get the `voting_power` key - there should be a `(u64, u64)` stored + /// here. + pub fn voting_power(&self) -> Key { + self.prefix + .push(&VOTING_POWER_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + } +} + +impl IntoIterator for &Keys { + type IntoIter = std::vec::IntoIter; + type Item = Key; + + fn into_iter(self) -> Self::IntoIter { + vec![ + self.body(), + self.seen(), + self.seen_by(), + self.voting_power(), + ] + .into_iter() + } +} + +impl From<&EthereumEvent> for Keys { + fn from(event: &EthereumEvent) -> Self { + let hash = event + .hash() + .expect("should always be able to hash Ethereum events"); + (&hash).into() + } +} + +impl From<&Hash> for Keys { + fn from(hash: &Hash) -> Self { + let hex = format!("{}", hash); + let prefix = prefix() + .push(&hex) + .expect("should always be able to construct this key"); + Self { prefix } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::eth_bridge::ADDRESS; + use crate::types::storage::DbKeySeg; + + fn arbitrary_event_with_hash() -> (EthereumEvent, String) { + ( + EthereumEvent::TransfersToNamada { + nonce: 1.into(), + transfers: vec![], + }, + "06799912C0FD8785EE29E13DFB84FE2778AF6D9CA026BD5B054F86CE9FE8C017" + .to_owned(), + ) + } + + #[test] + fn test_prefix() { + assert!(matches!(&prefix().segments[..], [ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(s), + ] if s == PREFIX_KEY_SEGMENT)) + } + + #[test] + fn test_keys_all_keys() { + let (event, hash) = arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); + let prefix = vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(hash), + ]; + let body_key = keys.body(); + assert_eq!(body_key.segments[..3], prefix[..]); + assert_eq!( + body_key.segments[3], + DbKeySeg::StringSeg(BODY_KEY_SEGMENT.to_owned()) + ); + + let seen_key = keys.seen(); + assert_eq!(seen_key.segments[..3], prefix[..]); + assert_eq!( + seen_key.segments[3], + DbKeySeg::StringSeg(SEEN_KEY_SEGMENT.to_owned()) + ); + + let seen_by_key = keys.seen_by(); + assert_eq!(seen_by_key.segments[..3], prefix[..]); + assert_eq!( + seen_by_key.segments[3], + DbKeySeg::StringSeg(SEEN_BY_KEY_SEGMENT.to_owned()) + ); + + let voting_power_key = keys.voting_power(); + assert_eq!(voting_power_key.segments[..3], prefix[..]); + assert_eq!( + voting_power_key.segments[3], + DbKeySeg::StringSeg(VOTING_POWER_KEY_SEGMENT.to_owned()) + ); + } + + #[test] + fn test_keys_into_iter() { + let (event, _) = arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); + let as_keys: Vec<_> = keys.into_iter().collect(); + assert_eq!( + as_keys, + vec![ + keys.body(), + keys.seen(), + keys.seen_by(), + keys.voting_power(), + ] + ); + } + + #[test] + fn test_keys_from_ethereum_event() { + let (event, hash) = arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); + let expected = vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(hash), + ]; + assert_eq!(&keys.prefix.segments[..], &expected[..]); + } + + #[test] + fn test_keys_from_hash() { + let (event, hash) = arbitrary_event_with_hash(); + let keys: Keys = (&event.hash().unwrap()).into(); + let expected = vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(hash), + ]; + assert_eq!(&keys.prefix.segments[..], &expected[..]); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs new file mode 100644 index 0000000000..55cbfc01f2 --- /dev/null +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -0,0 +1,11 @@ +//! Functionality for accessing the storage subspace +use super::ADDRESS; +use crate::types::storage::{Key, KeySeg}; + +pub mod eth_msgs; +pub mod wrapped_erc20s; + +/// Key prefix for the storage subspace +pub fn prefix() -> Key { + Key::from(ADDRESS.to_db_key()) +} diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs new file mode 100644 index 0000000000..8c0d567e7d --- /dev/null +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -0,0 +1,160 @@ +//! Functionality for accessing the multitoken subspace +use crate::types::address::Address; +use crate::types::ethereum_events::EthAddress; +use crate::types::storage::Key; + +#[allow(missing_docs)] +pub const PREFIX_KEY_SEGMENT: &str = "ERC20"; + +/// Get the key prefix corresponding to the storage subspace that holds wrapped +/// ERC20 tokens +pub fn prefix() -> Key { + super::prefix() + .push(&PREFIX_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") +} + +#[allow(missing_docs)] +pub const BALANCE_KEY_SEGMENT: &str = "balance"; +#[allow(missing_docs)] +pub const SUPPLY_KEY_SEGMENT: &str = "supply"; + +/// Generator for the keys under which details of an ERC20 token are stored +pub struct Keys { + /// The prefix of keys under which the details for a specific ERC20 token + /// are stored + pub prefix: Key, +} + +impl Keys { + /// Get the `balance` key for a specific owner - there should be a + /// [`crate::types::token::Amount`] stored here + pub fn balance(&self, owner: &Address) -> Key { + self.prefix + .push(&BALANCE_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + .push(&format!("#{}", owner.encode())) + .expect("should always be able to construct this key") + } + + /// Get the `supply` key - there should be a + /// [`crate::types::token::Amount`] stored here + pub fn supply(&self) -> Key { + self.prefix + .push(&SUPPLY_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") + } +} + +impl From<&EthAddress> for Keys { + fn from(address: &EthAddress) -> Self { + Keys { + prefix: prefix() + .push(&address.to_canonical()) + .expect("should always be able to construct this key"), + } + } +} + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use super::*; + use crate::ledger::eth_bridge::ADDRESS; + use crate::types::address::Address; + use crate::types::ethereum_events::testing::{ + DAI_ERC20_ETH_ADDRESS, DAI_ERC20_ETH_ADDRESS_CHECKSUMMED, + }; + use crate::types::storage::DbKeySeg; + + const ARBITRARY_OWNER_ADDRESS: &str = + "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; + + #[test] + fn test_prefix() { + assert!(matches!( + &prefix().segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + ] if multitoken_addr == &ADDRESS && + multitoken_path == PREFIX_KEY_SEGMENT + )) + } + + #[test] + fn test_keys_from_eth_address() { + let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + assert!(matches!( + &keys.prefix.segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + DbKeySeg::StringSeg(token_id), + ] if multitoken_addr == &ADDRESS && + multitoken_path == PREFIX_KEY_SEGMENT && + token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() + )) + } + + #[test] + fn test_keys_balance() { + let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + let key = + keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + assert!(matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + DbKeySeg::StringSeg(token_id), + DbKeySeg::StringSeg(balance_key_seg), + DbKeySeg::AddressSeg(owner_addr), + ] if multitoken_addr == &ADDRESS && + multitoken_path == PREFIX_KEY_SEGMENT && + token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && + balance_key_seg == BALANCE_KEY_SEGMENT && + owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() + )) + } + + #[test] + fn test_keys_balance_to_string() { + let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + let key = + keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + assert_eq!( + "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/balance/#atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4", + key.to_string() + ) + } + + #[test] + fn test_keys_supply() { + let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + let key = keys.supply(); + assert!(matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + DbKeySeg::StringSeg(token_id), + DbKeySeg::StringSeg(supply_key_seg), + ] if multitoken_addr == &ADDRESS && + multitoken_path == PREFIX_KEY_SEGMENT && + token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && + supply_key_seg == SUPPLY_KEY_SEGMENT + )) + } + + #[test] + fn test_keys_supply_to_string() { + let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + let key = keys.supply(); + assert_eq!( + "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/supply", + key.to_string(), + ) + } +} diff --git a/shared/src/ledger/eth_bridge/vp.rs b/shared/src/ledger/eth_bridge/vp.rs index 479541e181..02d21f37b0 100644 --- a/shared/src/ledger/eth_bridge/vp.rs +++ b/shared/src/ledger/eth_bridge/vp.rs @@ -9,9 +9,6 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; -/// Internal address for the Ethereum bridge VP -pub const ADDRESS: Address = Address::Internal(InternalAddress::EthBridge); - /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where @@ -23,12 +20,10 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } -#[allow(missing_docs)] #[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Internal error")] - Internal, -} +#[error(transparent)] +/// Generic error that may be returned by the validity predicate +pub struct Error(#[from] eyre::Error); impl<'a, DB, H, CA> NativeVp for EthBridge<'a, DB, H, CA> where @@ -38,7 +33,7 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::EthBridge; + const ADDR: InternalAddress = super::INTERNAL_ADDRESS; fn validate_tx( &self, From 39528b5eb012423490c3b1bf083444a601981a96 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 15 Aug 2022 09:53:22 +0100 Subject: [PATCH 0495/1995] Replace assert!(matches! with assert_matches! --- shared/src/ledger/eth_bridge/storage/eth_msgs.rs | 4 ++-- .../ledger/eth_bridge/storage/wrapped_erc20s.rs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs index 9c2260c678..e128da3d48 100644 --- a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs +++ b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs @@ -113,10 +113,10 @@ mod test { #[test] fn test_prefix() { - assert!(matches!(&prefix().segments[..], [ + assert_matches!(&prefix().segments[..], [ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(s), - ] if s == PREFIX_KEY_SEGMENT)) + ] if s == PREFIX_KEY_SEGMENT) } #[test] diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 8c0d567e7d..cb4dafe602 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -73,20 +73,20 @@ mod test { #[test] fn test_prefix() { - assert!(matches!( + assert_matches!( &prefix().segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), DbKeySeg::StringSeg(multitoken_path), ] if multitoken_addr == &ADDRESS && multitoken_path == PREFIX_KEY_SEGMENT - )) + ) } #[test] fn test_keys_from_eth_address() { let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - assert!(matches!( + assert_matches!( &keys.prefix.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), @@ -95,7 +95,7 @@ mod test { ] if multitoken_addr == &ADDRESS && multitoken_path == PREFIX_KEY_SEGMENT && token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() - )) + ) } #[test] @@ -103,7 +103,7 @@ mod test { let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); let key = keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); - assert!(matches!( + assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), @@ -116,7 +116,7 @@ mod test { token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && balance_key_seg == BALANCE_KEY_SEGMENT && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() - )) + ) } #[test] @@ -134,7 +134,7 @@ mod test { fn test_keys_supply() { let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); let key = keys.supply(); - assert!(matches!( + assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), @@ -145,7 +145,7 @@ mod test { multitoken_path == PREFIX_KEY_SEGMENT && token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && supply_key_seg == SUPPLY_KEY_SEGMENT - )) + ) } #[test] From b5f39d50a0ec6ef2e46275ec75434bc17cbb3b60 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 30 Aug 2022 14:52:27 +0100 Subject: [PATCH 0496/1995] Change visibility of key segment constants to private --- shared/src/ledger/eth_bridge/storage/eth_msgs.rs | 12 ++++-------- .../src/ledger/eth_bridge/storage/wrapped_erc20s.rs | 6 ++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs index e128da3d48..e133e22918 100644 --- a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs +++ b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs @@ -14,14 +14,10 @@ pub fn prefix() -> Key { .expect("should always be able to construct this key") } -#[allow(missing_docs)] -pub const BODY_KEY_SEGMENT: &str = "body"; -#[allow(missing_docs)] -pub const SEEN_KEY_SEGMENT: &str = "seen"; -#[allow(missing_docs)] -pub const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; -#[allow(missing_docs)] -pub const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; +const BODY_KEY_SEGMENT: &str = "body"; +const SEEN_KEY_SEGMENT: &str = "seen"; +const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; +const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; /// Generator for the keys under which details of an [`EthereumEvent`] is stored pub struct Keys { diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index cb4dafe602..c308395bc7 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -14,10 +14,8 @@ pub fn prefix() -> Key { .expect("should always be able to construct this key") } -#[allow(missing_docs)] -pub const BALANCE_KEY_SEGMENT: &str = "balance"; -#[allow(missing_docs)] -pub const SUPPLY_KEY_SEGMENT: &str = "supply"; +const BALANCE_KEY_SEGMENT: &str = "balance"; +const SUPPLY_KEY_SEGMENT: &str = "supply"; /// Generator for the keys under which details of an ERC20 token are stored pub struct Keys { From eca4c3ff7a012795d7c0c29d0ceb990c1d351e2b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Aug 2022 15:24:54 +0100 Subject: [PATCH 0497/1995] Run make fmt --- apps/src/lib/mod.rs | 16 +++++++++------- apps/src/lib/node/ledger/mod.rs | 4 ++-- .../lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/init_chain.rs | 4 ++-- apps/src/lib/node/ledger/shell/mod.rs | 19 ++++++++++--------- apps/src/lib/node/ledger/shell/queries.rs | 4 ++-- .../node/ledger/shims/abcipp_shim_types.rs | 5 +++-- apps/src/lib/node/ledger/tendermint_node.rs | 4 +++- 8 files changed, 32 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index a6e00c2b78..8ba2e59855 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -22,15 +22,17 @@ pub use std; pub mod facade { //! Facade module to reason about `abcipp` feature flag logic. - #[cfg(feature = "abcipp")] + #[cfg(not(feature = "abcipp"))] pub use { - tendermint_abcipp as tendermint, tendermint_config_abcipp as tendermint_config, - tendermint_proto_abcipp as tendermint_proto, tendermint_rpc_abcipp as tendermint_rpc, - tower_abci_abcipp as tower_abci, + tendermint, tendermint_config, tendermint_proto, tendermint_rpc, + tower_abci, }; - #[cfg(not(feature = "abcipp"))] + #[cfg(feature = "abcipp")] pub use { - tendermint, tendermint_config, tendermint_proto, - tendermint_rpc, tower_abci, + tendermint_abcipp as tendermint, + tendermint_config_abcipp as tendermint_config, + tendermint_proto_abcipp as tendermint_proto, + tendermint_rpc_abcipp as tendermint_rpc, + tower_abci_abcipp as tower_abci, }; } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index cd9e4af786..f8337fac60 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -26,13 +26,13 @@ use tower::ServiceBuilder; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::TendermintMode; +use crate::facade::tendermint_proto::abci::CheckTxType; +use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; use crate::{config, wasm_loader}; -use crate::facade::tendermint_proto::abci::CheckTxType; -use crate::facade::tower_abci::{response, split, Server}; /// Env. var to set a number of Tokio RT worker threads const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index da3cea9246..8c8cd23b16 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -14,9 +14,9 @@ use namada::types::transaction::protocol::ProtocolTxType; use super::queries::QueriesExt; use super::*; -use crate::node::ledger::events::EventType; use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use crate::node::ledger::events::EventType; impl Shell where diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index ef3c44cdf6..77a23003da 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -8,10 +8,10 @@ use sha2::{Digest, Sha256}; use super::queries::QueriesExt; use super::*; -use crate::wasm_loader; use crate::facade::tendermint_proto::abci; -use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use crate::facade::tendermint_proto::google::protobuf; +use crate::wasm_loader; impl Shell where diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cba20c2af0..3f3ff0edc9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -51,6 +51,12 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use super::protocol::ShellParams; use super::rpc; use crate::config::{genesis, TendermintMode}; +use crate::facade::tendermint_proto::abci::{ + ConsensusParams, Misbehavior as Evidence, MisbehaviorType as EvidenceType, + RequestPrepareProposal, ValidatorUpdate, +}; +use crate::facade::tendermint_proto::crypto::public_key; +use crate::facade::tower_abci::{request, response}; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -58,13 +64,6 @@ use crate::node::ledger::{protocol, storage, tendermint_node}; #[allow(unused_imports)] use crate::wallet::{ValidatorData, ValidatorKeys}; use crate::{config, wallet}; -use crate::facade::tendermint_proto::abci::{ - Misbehavior as Evidence, MisbehaviorType as EvidenceType, - RequestPrepareProposal, ValidatorUpdate, -}; -use crate::facade::tendermint_proto::abci::ConsensusParams; -use crate::facade::tendermint_proto::crypto::public_key; -use crate::facade::tower_abci::{request, response}; fn key_to_tendermint( pk: &common::PublicKey, @@ -771,12 +770,14 @@ mod test_utils { use tokio::sync::mpsc::UnboundedReceiver; use super::*; + use crate::facade::tendermint_proto::abci::{ + RequestInitChain, RequestProcessProposal, + }; + use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; - use crate::facade::tendermint_proto::google::protobuf::Timestamp; - use crate::facade::tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; #[derive(Error, Debug)] pub enum TestError { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9d9be58a83..cf768c6d98 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -14,10 +14,10 @@ use namada::types::storage::{Epoch, Key, PrefixValue}; use namada::types::token::{self, Amount}; use super::*; -use crate::node::ledger::response; +use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; -use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; +use crate::node::ledger::response; #[derive(Error, Debug)] pub enum Error { 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 950412485c..d628374cf1 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -6,7 +6,6 @@ pub mod shim { use thiserror::Error; use super::{Request as Req, Response as Resp}; - use crate::node::ledger::shell; use crate::facade::tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, @@ -19,8 +18,10 @@ pub mod shim { }; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ - RequestExtendVote, ResponseExtendVote, RequestVerifyVoteExtension, ResponseVerifyVoteExtension, + RequestExtendVote, RequestVerifyVoteExtension, ResponseExtendVote, + ResponseVerifyVoteExtension, }; + use crate::node::ledger::shell; pub type TxBytes = Vec; diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 235493c52c..5918ab814f 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -17,7 +17,9 @@ use tokio::process::Command; use crate::config; use crate::facade::tendermint::Genesis; use crate::facade::tendermint_config::net::Address as TendermintAddress; -use crate::facade::tendermint_config::{Error as TendermintError, TendermintConfig}; +use crate::facade::tendermint_config::{ + Error as TendermintError, TendermintConfig, +}; /// Env. var to output Tendermint log to stdout pub const ENV_VAR_TM_STDOUT: &str = "ANOMA_TM_STDOUT"; From 40c965bf9109c16cf769ecc41ed4bffdd0ecc12a Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 30 Aug 2022 18:19:54 +0200 Subject: [PATCH 0498/1995] [feat]: Added more info about the the bridge pool vp --- .../ethereum-bridge/proofs.md | 16 ++----- .../ethereum-bridge/transfers_to_ethereum.md | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index f8c36f8492..f94c7e7212 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -7,19 +7,9 @@ inclusion proofs of assert transfer messages understandable to the Ethereum smart contractions, as described in the section on [batching](transfers_to_ethereum.md/#batching) -A message for transferring value to Ethereum should be of the form -```rust -pub struct TransferToEthereum { - /// The type of token - asset: EthereumAddress, - /// The recipient address - recipient: EthereumAddress, - /// The amount to be transferred - amount: Amount, - /// a nonce for replay protection - nonce: Nonce, -} -``` +A message for transferring value to Ethereum is a `TransferToNamada` +instance as described +[here](./transfers_to_ethereum.md/#bridge-pool-validity-predicate). Additionally, when the validator set changes, the smart contracts on Ethereum must be updated so that it can continue to recognize valid proofs. diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md index 56db41ff28..2177da9481 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md @@ -80,6 +80,51 @@ transactions, introducing latency to the pool. A user wishing to relay will need to wait until a Merkle tree root is signed for a tree that includes all the transactions they wish to relay. +### Bridge Pool validity predicate + +The Bridge Pool will have associated storage under the control of a native +validity predicate. The storage layout looks as follows. + +``` +# all values are Borsh-serialized +/pending_transfers: Vec +/signed_root: Signed +``` + +The pending transfers are instances of the following type: +```rust +pub struct TransferToEthereum { + /// The type of token + asset: EthereumAddress, + /// The recipient address + recipient: EthereumAddress, + /// The amount to be transferred + amount: Amount, + /// a nonce for replay protection + nonce: Nonce, +} + +pub struct PendingTransfer { + /// The message to send to Ethereum to + /// complete the transfer + transfer: TransferToEthereum, + /// The amount of gas fees (in NAM) + /// paid by the user sending this transfer + gas_fee: Amount +} +``` +When a user submits initiates a transfer, their transaction should include wasm +to append craft a `PendingTransfer` and append it to the pool in storage. +This will be validated by the Bridge Pool vp. + +The signed Merkle root is only modifiable by validators. The Merkle tree +only consists of the `TransferToEthereum` messages as Ethereum does not need +information about the gas fees paid on Namada. + +If vote extensions are not available, this signed root may lag behind the +list of pending transactions. However, it should be the eventually every +pending transaction is covered by the root or it times out. + ## Replay Protection and timeouts It is important that nonces are used to prevent copies of the same From f4a0759ac69950d8d08f2da4c179de1dbd32798a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 09:28:46 +0100 Subject: [PATCH 0499/1995] Add ProtocolTxType::VoteExtension --- apps/src/lib/node/ledger/shell/mod.rs | 2 +- shared/src/types/transaction/protocol.rs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3f3ff0edc9..31a7f50815 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -646,7 +646,7 @@ where .mode .get_protocol_key() .map(|protocol_key| { - ProtocolTxType::EthereumEvents(ext) + ProtocolTxType::VoteExtension(ext) .sign(protocol_key) .to_bytes() }) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index 078f5711b9..ddd490b898 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -32,11 +32,11 @@ mod protocol_txs { use serde_json; use super::*; - #[cfg(not(feature = "abcipp"))] - use crate::proto::Signed; use crate::proto::Tx; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; + #[cfg(not(feature = "abcipp"))] + use crate::types::vote_extensions::VoteExtension; use crate::types::vote_extensions::{ ethereum_events, validator_set_update, }; @@ -83,12 +83,14 @@ mod protocol_txs { NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions that /// are compressed before being included on chain - EthEventsDigest(ethereum_events::VextDigest), - /// Ethereum events seen be validators - #[cfg(not(feature = "abcipp"))] - EthereumEvents(Signed), + EthereumEvents(ethereum_events::VextDigest), /// Validator set updates contained in vote extensions ValidatorSetUpdate(validator_set_update::VextDigest), + /// Protocol transaction type including Ethereum events + /// seen by validators and validator set updates signed + /// at the beginning of a new epoch + #[cfg(not(feature = "abcipp"))] + VoteExtension(VoteExtension), } impl ProtocolTxType { From 035aac1e58e1710b7abbad5842d857f41c81de8d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Aug 2022 08:36:46 +0000 Subject: [PATCH 0500/1995] [ci skip] 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 c21393e8a0..a337a7b1a8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.f5c6d09ea3edec9f1a2054433a204c3074be74bdf84cc31d85cdbb57826b66e9.wasm", - "tx_from_intent.wasm": "tx_from_intent.314765bf6d98c3ed8921ec690088db228593e94520131f4210357dcf15297080.wasm", - "tx_ibc.wasm": "tx_ibc.49284526958de6c419783cc77b53869054e0942a633b1ad9f50ee86ea0b38b30.wasm", - "tx_init_account.wasm": "tx_init_account.2ee9452093351afc9c4465d87c174750ba8299d323cbd6a6572bb0e86e04de5f.wasm", - "tx_init_nft.wasm": "tx_init_nft.115aa449551d6246cd4183f9781b383a06bec921917a715e5e5e5348b64b8419.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.9a29feb5c7d292b2c36e129e2f863bfb24ae846fa9d47b1907314c2ee8bd6382.wasm", - "tx_init_validator.wasm": "tx_init_validator.80cbab1b8469db413777eb09d64ff1e25c6f2e8448dc11681e3e5ebb53970ff5.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9d3a9440df51586f8a76c080b1de6ebca116e484f177eccb1b666bdcf81c9a29.wasm", - "tx_transfer.wasm": "tx_transfer.f39dcb8338e21018e04c4834aa9c587581dfd9b83d07aff2f1c8be179ff0e86f.wasm", - "tx_unbond.wasm": "tx_unbond.32e671deae591d83f20b6a034c9a923498df7f51743817b534029fe5a76c4ffd.wasm", + "tx_bond.wasm": "tx_bond.e0cf6b4c7cae7606f597f66a2b467ed44ab817744471554650e59c1504e1cfd8.wasm", + "tx_from_intent.wasm": "tx_from_intent.2190adc094be799ff1c8383e4b57c7d2ab5da328e845f8f3095cce4ed8b0041b.wasm", + "tx_ibc.wasm": "tx_ibc.014a3a93980f04147826f1e568551b1127de763e6c07480990db1c240877141b.wasm", + "tx_init_account.wasm": "tx_init_account.f4eceee710c20f402a9984338294a87ca5b33b45ac3e4c2229550c2a00597230.wasm", + "tx_init_nft.wasm": "tx_init_nft.809d0b46379ea9ea4cd7cd026e52ac7ad25094b090976a748a349691026db95b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b6b1014750b667bca57d82979c2295e99d0d3ab3723301029dcaa4b125ed401a.wasm", + "tx_init_validator.wasm": "tx_init_validator.da09c65fad79490ededfbf9ebc75df6626031f36f62791dc4e092743c91914aa.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.07d3118e626ca59c95571ccf00e24265570388b66f8e67228b109b7c095653a7.wasm", + "tx_transfer.wasm": "tx_transfer.e84e5b4d77c81d2d6fe248d2c3b5ae0bb6d5fb7ef13da5e62e4b7ac7764fd80a.wasm", + "tx_unbond.wasm": "tx_unbond.35bb3196fd77c138541507d139cb68ca0f752df6cad0fb22db4a00bc64471771.wasm", "tx_update_vp.wasm": "tx_update_vp.f73a121138f8b9d2fa0a50ae0b1dfb0b69ca4fd6559f5600aec61cd2aeece960.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1f3876e600041d28138c510dbd47531172fb9774062ded936e48773a0b82df2a.wasm", - "tx_withdraw.wasm": "tx_withdraw.e3cef11e09eddf21bc2c433f76b36eda01c89bf0a1e9d87397e21120bc035faf.wasm", - "vp_nft.wasm": "vp_nft.e28c3cf27dc98d6db226cebc8c64eb759aa5d8b4c32210f9f66a12feb851f23f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c5fc2b058dc6db59b852d065e9fa0efd9ecbb5391c66fb501d1cf09b5c381fc.wasm", - "vp_token.wasm": "vp_token.96de2be005cdd359061106e4b278e3fda4a4456ac84123baa528be6aecc1bbf1.wasm", - "vp_user.wasm": "vp_user.3d8a41b4f48b6793bc49cd70c25c75a4183b0c63251e052ed19828de655c2efd.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.66f3c60eb109f2c631c2ee5f5fb47d73a086fa2a786b2ff63907e4e95bd79fcf.wasm", + "tx_withdraw.wasm": "tx_withdraw.a630427bcb03ec5cbd96b85ef86f79f85a5d1cff12f6b10308a9cb006ac95533.wasm", + "vp_nft.wasm": "vp_nft.bd7840f82f1ad27546266c01884ef69e81bd9e365cee53c8b013339e395ce392.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.feb9eec34cc11bd2233281a199e78d1bc284ce9eb4465198f483c5eb8075d8ab.wasm", + "vp_token.wasm": "vp_token.07f6e0b8a161125c7c1ed3356694a28e340332be1674e1d561802ba213736417.wasm", + "vp_user.wasm": "vp_user.b856d1a609021e0a4728ecd961268840eddff938fd67d09b648f31fc6422bf5a.wasm" } \ No newline at end of file From 4bc83b46e1be91ccb8227373cafaac5027fe6e38 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 10:38:51 +0100 Subject: [PATCH 0501/1995] WIP: Shimming prepare proposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 103 +++- .../lib/node/ledger/shell/vote_extensions.rs | 531 ++++++++++-------- 2 files changed, 373 insertions(+), 261 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 530e04d05b..5890a0b8be 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -45,7 +45,13 @@ where // TODO: add some info logging? // add ethereum events and validator set updates as protocol txs - let mut txs = self.build_vote_extensions_txs(req.local_last_commit); + #[cfg(feature = "abcipp")] + let txs = self.build_vote_extensions_txs(req.local_last_commit); + #[cfg(not(feature = "abcipp"))] + let mut txs = self.build_vote_extensions_txs(&req.txs); + #[cfg(feature = "abcipp")] + let mut txs: Vec = + txs.into_iter().map(record::add).collect(); // add mempool txs let mut mempool_txs = self.build_mempool_txs(req.txs); @@ -53,6 +59,9 @@ where // decrypt the wrapper txs included in the previous block let mut decrypted_txs = self.build_decrypted_txs(); + #[cfg(feature = "abcipp")] + let mut decrypted_txs: Vec = + decrypted_txs.into_iter().map(record::add).collect(); txs.append(&mut decrypted_txs); txs @@ -65,9 +74,17 @@ where tx_records = txs.len(), "Proposing block" ); - response::PrepareProposal { - tx_records: txs, - ..Default::default() + + #[cfg(feature = "abcipp")] + { + response::PrepareProposal { + tx_records: txs, + ..Default::default() + } + } + #[cfg(not(feature = "abcipp"))] + { + response::PrepareProposal { txs } } } @@ -75,13 +92,15 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - local_last_commit: Option, - ) -> Vec { + #[cfg(feature = "abcipp")] local_last_commit: Option, + #[cfg(not(feature = "abcipp"))] txs: &[TxBytes], + ) -> Vec { // genesis block should not contain vote extensions if self.storage.last_height == BlockHeight(0) { return vec![]; } + #[cfg(feature = "abcipp")] let (eth_events, valset_upds) = split_vote_extensions( local_last_commit .expect( @@ -95,6 +114,8 @@ where ) .votes, ); + #[cfg(not(feature = "abcipp"))] + let (eth_events, valset_upds) = split_vote_extensions(txs); const NOT_ENOUGH_VOTING_POWER_MSG: &str = "A Tendermint quorum should never decide on a block including \ @@ -105,6 +126,10 @@ where .compress_ethereum_events(eth_events) .expect(NOT_ENOUGH_VOTING_POWER_MSG); + // TODO: check that we can only send one validator set + // update vote extension per epoch + // + // add feature flag gating at this level let validator_set_update = if self.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight(self.storage.last_height), @@ -126,11 +151,12 @@ where ethereum_events, validator_set_update, }) - .map(|tx| record::add(tx.sign(protocol_key).to_bytes())) + .map(|tx| tx.sign(protocol_key).to_bytes()) .collect() } /// Builds a batch of mempool transactions + #[cfg(feature = "abcipp")] fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { // filter in half of the new txs from Tendermint, only keeping // wrappers @@ -149,6 +175,26 @@ where .collect() } + /// Builds a batch of mempool transactions + #[cfg(not(feature = "abcipp"))] + fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { + // filter in half of the new txs from Tendermint, only keeping + // wrappers + let number_of_new_txs = 1 + txs.len() / 2; + txs.into_iter() + .take(number_of_new_txs) + .filter_map(|tx_bytes| { + if let Ok(Ok(TxType::Wrapper(_))) = + Tx::try_from(tx_bytes.as_slice()).map(process_tx) + { + Some(tx_bytes) + } else { + None + } + }) + .collect() + } + /// Builds a batch of DKG decrypted transactions // TODO: we won't have frontrunning protection until V2 of the Anoma // protocol; Namada runs V1, therefore this method is @@ -157,7 +203,7 @@ where // sources: // - https://specs.anoma.net/main/releases/v2.html // - https://github.com/anoma/ferveo - fn build_decrypted_txs(&mut self) -> Vec { + fn build_decrypted_txs(&mut self) -> Vec { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); @@ -171,17 +217,16 @@ where }) .to_bytes() }) - .map(record::add) .collect() } } /// Functions for creating the appropriate TxRecord given the /// numeric code +#[cfg(feature = "abcipp")] pub(super) mod record { - use tendermint_proto::abci::tx_record::TxAction; - use super::*; + use crate::facade::tendermint_proto::abci::tx_record::TxAction; /// Keep this transaction in the proposal pub fn keep(tx: TxBytes) -> TxRecord { @@ -232,19 +277,21 @@ mod test_prepare_proposal { self, MultiSignedEthEvent, }; use namada::types::vote_extensions::VoteExtension; - use tendermint_proto::abci::tx_record::TxAction; - use tendermint_proto::abci::{ - ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, - }; use super::*; + use crate::wallet; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; - use crate::wallet; + #[cfg(feature = "abicpp")] + use crate::facade::tendermint_proto::abci::{ + tx_record::TxAction, + ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, + }; + #[cfg(feature = "abicpp")] fn get_local_last_commit(shell: &TestShell) -> Option { let evts = { let validator_addr = shell @@ -296,16 +343,20 @@ mod test_prepare_proposal { Some("transaction_data".as_bytes().to_owned()), ); let req = RequestPrepareProposal { + #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), txs: vec![tx.to_bytes()], max_tx_bytes: 0, ..Default::default() }; + #[cfg(feature = "abcipp")] assert_eq!( // NOTE: we process mempool txs after protocol txs shell.prepare_proposal(req).tx_records.remove(1), record::remove(tx.to_bytes()) ); + #[cfg(not(feature = "abcipp"))] + assert!(shell.prepare_proposal(req).txs.is_empty()); } /// Check if we are filtering out an invalid vote extension `vext` @@ -318,6 +369,26 @@ mod test_prepare_proposal { assert_eq!(filtered_votes, vec![]); } +/* + /// Check if we are filtering out an invalid vote extension `vext` + fn check_eth_events_filtering( + shell: &mut TestShell, + vext: Signed, + ) { + #[cfg(feature = "abcipp")] + let vexts = vec![vote_extension_serialize(vext)]; + #[cfg(not(feature = "abcipp"))] + let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + #[cfg(not(feature = "abcipp"))] + let vexts = vec![vote_extension_serialize(vext, protocol_key)]; + + let votes = deserialize_vote_extensions(vexts); + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + + assert_eq!(filtered_votes, vec![]); + } +*/ /// Test if we are filtering out Ethereum events with bad /// signatures in a prepare proposal. diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 35116a6e7d..306b2ba63b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,293 +1,334 @@ //! Extend Tendermint votes with Ethereum bridge logic. -#[cfg(not(feature = "ABCI"))] pub mod ethereum_events; - -#[cfg(not(feature = "ABCI"))] pub mod validator_set_update; -#[cfg(not(feature = "ABCI"))] -mod extend_votes { - use borsh::BorshDeserialize; - use namada::proto::Signed; - use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::vote_extensions::{ - ethereum_events, validator_set_update, VoteExtension, - VoteExtensionDigest, - }; - use tendermint_proto::abci::ExtendedVoteInfo; - - use super::super::*; - use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; +use borsh::BorshDeserialize; +use namada::proto::Signed; +use namada::types::transaction::protocol::ProtocolTxType; +use namada::types::vote_extensions::{ + ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, +}; +use tendermint_proto::abci::ExtendedVoteInfo; - /// Message to be passed to `.expect()` calls in this module. - const VALIDATOR_EXPECT_MSG: &str = - "Only validators receive this method call."; +use super::*; +use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; +use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - /// The error yielded from validating faulty vote extensions in the shell - #[derive(Error, Debug)] - pub enum VoteExtensionError { - #[error("The vote extension was issued at block height 0.")] - IssuedAtGenesis, - #[error( - "The vote extension has an unexpected sequence number (e.g. block \ - height)." - )] - UnexpectedSequenceNumber, - #[error( - "The vote extension contains duplicate or non-sorted Ethereum \ - events." - )] - HaveDupesOrNonSorted, - #[error( - "The public key of the vote extension's associated validator \ - could not be found in storage." - )] - PubKeyNotInStorage, - #[error("The vote extension's signature is invalid.")] - VerifySigFailed, - } +/// Message to be passed to `.expect()` calls in this module. +const VALIDATOR_EXPECT_MSG: &str = "Only validators receive this method call."; - impl Shell - where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, - { - /// INVARIANT: This method must be stateless. - pub fn extend_vote( - &mut self, - _req: request::ExtendVote, - ) -> response::ExtendVote { - let vote_extension = VoteExtension { - ethereum_events: self.extend_vote_with_ethereum_events(), - validator_set_update: self.extend_vote_with_valset_update(), - } - .try_to_vec() - .unwrap(); +/// The error yielded from validating faulty vote extensions in the shell +#[derive(Error, Debug)] +pub enum VoteExtensionError { + #[error("The vote extension was issued at block height 0.")] + IssuedAtGenesis, + #[error( + "The vote extension has an unexpected sequence number (e.g. block \ + height)." + )] + UnexpectedSequenceNumber, + #[error( + "The vote extension contains duplicate or non-sorted Ethereum events." + )] + HaveDupesOrNonSorted, + #[error( + "The public key of the vote extension's associated validator could \ + not be found in storage." + )] + PubKeyNotInStorage, + #[error("The vote extension's signature is invalid.")] + VerifySigFailed, +} - response::ExtendVote { vote_extension } +impl Shell +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + /// INVARIANT: This method must be stateless. + pub fn extend_vote( + &mut self, + _req: request::ExtendVote, + ) -> response::ExtendVote { + let vote_extension = VoteExtension { + ethereum_events: self.extend_vote_with_ethereum_events(), + validator_set_update: self.extend_vote_with_valset_update(), } + .try_to_vec() + .unwrap(); - /// Extend PreCommit votes with [`ethereum_events::Vext`] instances. - pub fn extend_vote_with_ethereum_events( - &mut self, - ) -> Signed { - let validator_addr = self - .mode - .get_validator_address() - .expect(VALIDATOR_EXPECT_MSG) - .to_owned(); + response::ExtendVote { vote_extension } + } - let ext = ethereum_events::Vext { - block_height: self.storage.get_current_decision_height(), - ethereum_events: self.new_ethereum_events(), - validator_addr, - }; + /// Extend PreCommit votes with [`ethereum_events::Vext`] instances. + pub fn extend_vote_with_ethereum_events( + &mut self, + ) -> Signed { + let validator_addr = self + .mode + .get_validator_address() + .expect(VALIDATOR_EXPECT_MSG) + .to_owned(); - let protocol_key = match &self.mode { - ShellMode::Validator { data, .. } => { - &data.keys.protocol_keypair - } - _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), - }; + let ext = ethereum_events::Vext { + block_height: self.storage.get_current_decision_height(), + ethereum_events: self.new_ethereum_events(), + validator_addr, + }; - ext.sign(protocol_key) - } + let protocol_key = match &self.mode { + ShellMode::Validator { data, .. } => &data.keys.protocol_keypair, + _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), + }; - /// Extend PreCommit votes with [`validator_set_update::Vext`] - /// instances. - pub fn extend_vote_with_valset_update( - &mut self, - ) -> Option { - let validator_addr = self - .mode - .get_validator_address() - .expect(VALIDATOR_EXPECT_MSG) - .to_owned(); - - self.storage - .can_send_validator_set_update(SendValsetUpd::Now) - .then(|| { - let next_epoch = self.storage.get_current_epoch().0.next(); - let _validator_set = - self.storage.get_active_validators(Some(next_epoch)); + ext.sign(protocol_key) + } - let ext = validator_set_update::Vext { - validator_addr, - // TODO: we need a way to map ethereum addresses to - // namada validator addresses - voting_powers: std::collections::HashMap::new(), - epoch: next_epoch, - }; + /// Extend PreCommit votes with [`validator_set_update::Vext`] + /// instances. + pub fn extend_vote_with_valset_update( + &mut self, + ) -> Option { + let validator_addr = self + .mode + .get_validator_address() + .expect(VALIDATOR_EXPECT_MSG) + .to_owned(); - let protocol_key = match &self.mode { - ShellMode::Validator { data, .. } => { - &data.keys.protocol_keypair - } - _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), - }; + self.storage + .can_send_validator_set_update(SendValsetUpd::Now) + .then(|| { + let next_epoch = self.storage.get_current_epoch().0.next(); + let _validator_set = + self.storage.get_active_validators(Some(next_epoch)); - // TODO: sign validator set update with secp key instead - ext.sign(protocol_key) - }) - } + let ext = validator_set_update::Vext { + validator_addr, + // TODO: we need a way to map ethereum addresses to + // namada validator addresses + voting_powers: std::collections::HashMap::new(), + epoch: next_epoch, + }; - /// This checks that the vote extension: - /// * Correctly deserializes. - /// * The Ethereum events vote extension within was correctly signed by - /// an active validator. - /// * The validator set update vote extension within was correctly - /// signed by an active validator, in case it could have been sent at - /// the current block height. - /// * The Ethereum events vote extension block height signed over is - /// correct (for replay protection). - /// * The validator set update vote extension epoch signed over is - /// correct (for replay protection). - /// - /// INVARIANT: This method must be stateless. - pub fn verify_vote_extension( - &self, - req: request::VerifyVoteExtension, - ) -> response::VerifyVoteExtension { - let ext = - match VoteExtension::try_from_slice(&req.vote_extension[..]) { - Ok(ext) => ext, - Err(err) => { - tracing::warn!( - ?err, - ?req.validator_address, - ?req.hash, - req.height, - "Received undeserializable vote extension" - ); - return response::VerifyVoteExtension { - status: VerifyStatus::Reject.into(), - }; + let protocol_key = match &self.mode { + ShellMode::Validator { data, .. } => { + &data.keys.protocol_keypair } + _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), }; - let validated_eth_events = - self.verify_ethereum_events(&req, ext.ethereum_events); - let validated_valset_upd = - self.verify_valset_update(&req, ext.validator_set_update); + // TODO: sign validator set update with secp key instead + ext.sign(protocol_key) + }) + } - response::VerifyVoteExtension { - status: if validated_eth_events && validated_valset_upd { - VerifyStatus::Accept.into() - } else { - VerifyStatus::Reject.into() - }, + /// This checks that the vote extension: + /// * Correctly deserializes. + /// * The Ethereum events vote extension within was correctly signed by an + /// active validator. + /// * The validator set update vote extension within was correctly signed by + /// an active validator, in case it could have been sent at the current + /// block height. + /// * The Ethereum events vote extension block height signed over is correct + /// (for replay protection). + /// * The validator set update vote extension epoch signed over is correct + /// (for replay protection). + /// + /// INVARIANT: This method must be stateless. + pub fn verify_vote_extension( + &self, + req: request::VerifyVoteExtension, + ) -> response::VerifyVoteExtension { + let ext = match VoteExtension::try_from_slice(&req.vote_extension[..]) { + Ok(ext) => ext, + Err(err) => { + tracing::warn!( + ?err, + ?req.validator_address, + ?req.hash, + req.height, + "Received undeserializable vote extension" + ); + return response::VerifyVoteExtension { + status: VerifyStatus::Reject.into(), + }; } + }; + + let validated_eth_events = + self.verify_ethereum_events(&req, ext.ethereum_events); + let validated_valset_upd = + self.verify_valset_update(&req, ext.validator_set_update); + + response::VerifyVoteExtension { + status: if validated_eth_events && validated_valset_upd { + VerifyStatus::Accept.into() + } else { + VerifyStatus::Reject.into() + }, } + } - /// Check if [`ethereum_events::Vext`] instances are valid. - pub fn verify_ethereum_events( - &self, - req: &request::VerifyVoteExtension, - ext: Signed, - ) -> bool { - self.validate_eth_events_vext(ext, self.storage.get_current_decision_height()) - .then(|| true) + /// Check if [`ethereum_events::Vext`] instances are valid. + pub fn verify_ethereum_events( + &self, + req: &request::VerifyVoteExtension, + ext: Signed, + ) -> bool { + self.validate_eth_events_vext( + ext, + self.storage.get_current_decision_height(), + ) + .then(|| true) + .unwrap_or_else(|| { + tracing::warn!( + ?req.validator_address, + ?req.hash, + req.height, + "Received Ethereum events vote extension that didn't validate" + ); + false + }) + } + + /// Check if [`validator_set_update::Vext`] instances are valid. + pub fn verify_valset_update( + &self, + req: &request::VerifyVoteExtension, + ext: Option, + ) -> bool { + self.storage + .can_send_validator_set_update(SendValsetUpd::Now) + .then(|| { + ext.and_then(|ext| { + // we have a valset update vext when we're expecting one, + // cool, let's validate it + self.validate_valset_upd_vext( + ext, + self.storage.get_current_epoch().0.next(), + ) + .then(|| true) + }) .unwrap_or_else(|| { + // either validation failed, or we were expecting a valset + // update vext and got none tracing::warn!( ?req.validator_address, ?req.hash, req.height, - "Received Ethereum events vote extension that didn't validate" + "Missing or invalid validator set update vote extension" ); false }) - } - - /// Check if [`validator_set_update::Vext`] instances are valid. - pub fn verify_valset_update( - &self, - req: &request::VerifyVoteExtension, - ext: Option, - ) -> bool { - self.storage.can_send_validator_set_update(SendValsetUpd::Now).then(|| { - ext - .and_then(|ext| { - // we have a valset update vext when we're expecting one, cool, - // let's validate it - self.validate_valset_upd_vext(ext, self.storage.get_current_epoch().0.next()) - .then(|| true) - }) - .unwrap_or_else(|| { - // either validation failed, or we were expecting a valset update - // vext and got none - tracing::warn!( - ?req.validator_address, - ?req.hash, - req.height, - "Missing or invalid validator set update vote extension" - ); - false - }) - }).unwrap_or({ + }) + .unwrap_or({ // NOTE: if we're not supposed to send a validator set update // vote extension at a particular block height, we will // just return true as the validation result true }) - } - } - - /// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the - /// ones we could deserialize to [`VoteExtension`] - /// instances. - pub fn deserialize_vote_extensions( - vote_extensions: Vec, - ) -> impl Iterator + 'static { - vote_extensions.into_iter().filter_map(|vote| { - VoteExtension::try_from_slice(&vote.vote_extension[..]) - .map_err(|err| { - tracing::error!( - ?err, - "Failed to deserialize data as a VoteExtension", - ); - }) - .ok() - }) - } - - /// Yields an iterator over the [`ProtocolTxType`] transactions - /// in a [`VoteExtensionDigest`]. - pub fn iter_protocol_txs( - digest: VoteExtensionDigest, - ) -> impl Iterator { - [ - Some(ProtocolTxType::EthereumEvents(digest.ethereum_events)), - digest - .validator_set_update - .map(ProtocolTxType::ValidatorSetUpdate), - ] - .into_iter() - .flatten() } +} - /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering - /// out invalid data, and splits these into [`ethereum_events::Vext`] - /// and [`validator_set_update::Vext`] instances. - pub fn split_vote_extensions( - vote_extensions: Vec, - ) -> ( - Vec>, - Vec, - ) { - let mut eth_evs = vec![]; - let mut valset_upds = vec![]; +/// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the +/// ones we could deserialize to [`VoteExtension`] +/// instances. +#[cfg(feature = "abcipp")] +pub fn deserialize_vote_extensions( + vote_extensions: Vec, +) -> impl Iterator + 'static { + vote_extensions.into_iter().filter_map(|vote| { + VoteExtension::try_from_slice(&vote.vote_extension[..]) + .map_err(|err| { + tracing::error!( + ?err, + "Failed to deserialize data as a VoteExtension", + ); + }) + .ok() + }) +} - for ext in deserialize_vote_extensions(vote_extensions) { - if let Some(validator_set_update) = ext.validator_set_update { - valset_upds.push(validator_set_update); +/// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the +/// ones we could deserialize to [`VoteExtension`] +/// instances. +#[cfg(not(feature = "abcipp"))] +pub fn deserialize_vote_extensions( + txs: &[TxBytes], +) -> impl Iterator + '_ { + txs.iter().filter_map(|tx| { + if let Ok(tx) = Tx::try_from(tx.as_slice()) { + match process_tx(tx).ok()? { + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::VoteExtension(ext), + .. + }) => Some(ext), + _ => None, } - eth_evs.push(ext.ethereum_events); + } else { + None } + }) +} + +/// Yields an iterator over the [`ProtocolTxType`] transactions +/// in a [`VoteExtensionDigest`]. +pub fn iter_protocol_txs( + digest: VoteExtensionDigest, +) -> impl Iterator { + [ + Some(ProtocolTxType::EthereumEvents(digest.ethereum_events)), + digest + .validator_set_update + .map(ProtocolTxType::ValidatorSetUpdate), + ] + .into_iter() + .flatten() +} + +/// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering +/// out invalid data, and splits these into [`ethereum_events::Vext`] +/// and [`validator_set_update::Vext`] instances. +#[cfg(feature = "abcipp")] +pub fn split_vote_extensions( + vote_extensions: Vec, +) -> ( + Vec>, + Vec, +) { + let mut eth_evs = vec![]; + let mut valset_upds = vec![]; - (eth_evs, valset_upds) + for ext in deserialize_vote_extensions(vote_extensions) { + if let Some(validator_set_update) = ext.validator_set_update { + valset_upds.push(validator_set_update); + } + eth_evs.push(ext.ethereum_events); } + + (eth_evs, valset_upds) } -#[cfg(not(feature = "ABCI"))] -pub use extend_votes::*; +/// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering +/// out invalid data, and splits these into [`ethereum_events::Vext`] +/// and [`validator_set_update::Vext`] instances. +#[cfg(not(feature = "abcipp"))] +pub fn split_vote_extensions( + transactions: &[TxBytes], +) -> ( + Vec>, + Vec, +) { + let mut eth_evs = vec![]; + let mut valset_upds = vec![]; + + for ext in deserialize_vote_extensions(transactions) { + if let Some(validator_set_update) = ext.validator_set_update { + valset_upds.push(validator_set_update); + } + eth_evs.push(ext.ethereum_events); + } + + (eth_evs, valset_upds) +} From 21e071f2b6d7835cf8e7636df9bc4723d87479f2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 12:32:46 +0100 Subject: [PATCH 0502/1995] Remove commented check_eth_events_filtering() --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 43172a8529..09dffd9985 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -369,26 +369,6 @@ mod test_prepare_proposal { assert_eq!(filtered_votes, vec![]); } -/* - /// Check if we are filtering out an invalid vote extension `vext` - fn check_eth_events_filtering( - shell: &mut TestShell, - vext: Signed, - ) { - #[cfg(feature = "abcipp")] - let vexts = vec![vote_extension_serialize(vext)]; - #[cfg(not(feature = "abcipp"))] - let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); - #[cfg(not(feature = "abcipp"))] - let vexts = vec![vote_extension_serialize(vext, protocol_key)]; - - let votes = deserialize_vote_extensions(vexts); - let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); - - assert_eq!(filtered_votes, vec![]); - } -*/ /// Test if we are filtering out Ethereum events with bad /// signatures in a prepare proposal. From 40caf782506569c9d38a51d8eb2d62a0e6050796 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 31 Aug 2022 13:38:00 +0200 Subject: [PATCH 0503/1995] [fix]: Cleanups and clarifications --- .../src/interoperability/ethereum-bridge.md | 3 +- .../ethereum_events_attestation.md | 13 ++++--- .../ethereum-bridge/proofs.md | 37 ++++++++++++++----- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index b78e157a67..4d626eb3f3 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -2,8 +2,7 @@ The Namada - Ethereum bridge exists to mint ERC20 tokens on Namada which naturally can be redeemed on Ethereum at a later time. Furthermore, it -allows the minting of wrapped tokens on Ethereum backed by escrowed assets on -Namada. +allows the minting of wrapped NAM (wNAM) tokens on Ethereum. The Namada Ethereum bridge system consists of: diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md index 00d454f75c..d5a3e0e4ea 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md @@ -77,7 +77,7 @@ events from other validators into their proposal. If the underlying Tendermint version supports vote extensions, consensus invariants guarantee that a quorum of votes from the previous block height can be included. Otherwise, validators can only submit votes by broadcasting protocol transactions, -which comes with less guarantees. +which comes with less guarantees (i.e. no consensus finality). The vote of a validator should include the events of the Ethereum blocks they have seen via their full node such that: @@ -117,12 +117,13 @@ part of `ProcessProposal`, this includes checking: If vote extensions are supported, it is also checked that each vote extension came from the previous round, requiring validators to sign over the Namada block -height with their vote extension. +height with their vote extension. Signing over the block height also acts as +a replay protection mechanism. -Furthermore, the vote extensions included by -the block proposer should have at least 2 / 3 of the total voting power of the -previous round backing it. Otherwise the block proposer would not have passed the -`FinalizeBlock` phase of the last round. +Furthermore, the vote extensions included by the block proposer should have +a quorum of the total voting power of the epoch of the block height behind +it. Otherwise the block proposer would not have passed the `FinalizeBlock` +phase of the last round of the last block. These checks are to prevent censorship of events from validators by the block proposer. If vote extensions are not diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index f94c7e7212..e15dfb9b33 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -1,10 +1,10 @@ # Proofs -A proof for the bridge is a quorum of signatures by a valid validator set -attached to a message understandable to the Ethereum smart contracts. For -transferring value to Ethereum, a proof is a signed Merkle tree root and -inclusion proofs of assert transfer messages understandable to the Ethereum -smart contractions, as described in the section on +A proof for the bridge is a quorum of signatures by a valid validator set. A +bridge header is a proof attached to a message understandable to the +Ethereum smart contracts. For transferring value to Ethereum, a proof is a +signed Merkle tree root and inclusion proofs of assert transfer messages +understandable to the Ethereum smart contracts, as described in the section on [batching](transfers_to_ethereum.md/#batching) A message for transferring value to Ethereum is a `TransferToNamada` @@ -13,13 +13,13 @@ instance as described Additionally, when the validator set changes, the smart contracts on Ethereum must be updated so that it can continue to recognize valid proofs. -Since the Ethereum smart contract should accept any header signed by bridge +Since the Ethereum smart contract should accept any bridge header signed by 2 / 3 of the staking validators, it needs up-to-date knowledge of: - The current validators' public keys - The current stake of each validator -This means the at the end of every Namada epoch, a special transaction must be +This means that by the end of every Namada epoch, a special transaction must be sent to the Ethereum smart contracts detailing the new public keys and stake of the new validator set. This message must also be signed by at least 2 / 3 of the current validators as a "transfer of power". @@ -31,7 +31,7 @@ single message that can be relayed to Ethereum. Signing an invalid validator transition set will be considered a slashable offense. Due to asynchronicity concerns, this message should be submitted well in -advance of the actual epoch change, perhaps even at the beginning of each +advance of the actual epoch change. It should happen at the beginning of each new epoch. Bridge headers to ethereum should include the current Namada epoch so that the smart contract knows how to verify the headers. In short, there is a pipelining mechanism in the smart contract. @@ -58,7 +58,7 @@ should not be the responsibility of any user to submit such a transaction. However, any user may choose to submit this transaction anyway. This necessitates a Namada node whose job it is to submit these transactions on -Ethereum at the conclusion of each Namada epoch. This node is called the +Ethereum by the conclusion of each Namada epoch. This node is called the __bridge relayer__. In theory, since this message is publicly available on the blockchain, anyone can submit this transaction, but only the bridge relayer will be directly compensated by Namada. @@ -72,7 +72,24 @@ During the `FinalizeBlock` call in the ledger, if the transfer of power message is placed on chain, a flag should be set alerting the next block proposer that they are the bridge relayer for this epoch. -If the Ethereum event spawned by relaying their message gets accepted by the +If the Ethereum event spawned by relaying their message gets accepted by the Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward them. The reward amount shall be a protocol parameter that can be changed via governance. It should be high enough to cover necessary gas fees. + +### Recovering from an update failure + +If vote extensions are not available, we cannot guarantee that a quorum of +validator signatures can be gathered for the message that updates the +validator set before the epoch ends. + +If a significant number of validators become inactive in the next epoch, we +need a means to complete validator set update. Until this is done, the +bridge will halt. + +In this case, the validators from that epoch will need to craft a +transaction with a quorum of signatures offline and submit it on-chain. This +transaction should include the validator set update. + +The only way this is impossible is if more than 1/3 of the validators by +stake from that epoch delete their ethereum keys, which is extremely unlikely. \ No newline at end of file From 9a0d69a09ddeba3aed8874fc72d91967497cc76f Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 31 Aug 2022 13:48:18 +0200 Subject: [PATCH 0504/1995] [fix]: Clarified the transfer of power when vexts aren't available. Apparently was still causing confusion --- .../interoperability/ethereum-bridge/proofs.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index e15dfb9b33..5be3ec9bb8 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -37,9 +37,10 @@ so that the smart contract knows how to verify the headers. In short, there is a pipelining mechanism in the smart contract. Such a message is not prompted by any user transaction and thus will have -to be carried out by a _bridge relayer_. Once the transfer of power -message is on chain, any time afterwards a Namada bridge process may take -it to craft the appropriate message to the Ethereum smart contracts. +to be carried out by a _bridge relayer_. Once the necessary data to +construct the transfer of power message is on chain, any time afterwards a +Namada bridge process may take it to craft the appropriate header to the +Ethereum smart contracts. The details on bridge relayers are below in the corresponding section. @@ -63,15 +64,15 @@ __bridge relayer__. In theory, since this message is publicly available on the blockchain, anyone can submit this transaction, but only the bridge relayer will be directly compensated by Namada. +The bridge relayer will be chosen to be the proposer of the first block of the +new epoch. Anyone else may relay this message, but must pay for the fees out of +their own pocket. + All Namada validators will have an option to serve as bridge relayer and the Namada ledger will include a process that does the relaying. Since all Namada validators are running Ethereum full nodes, they can monitor that the message was relayed correctly by the bridge relayer. -During the `FinalizeBlock` call in the ledger, if the transfer of power -message is placed on chain, a flag should be set alerting the next block -proposer that they are the bridge relayer for this epoch. - If the Ethereum event spawned by relaying their message gets accepted by the Ethereum state inclusion onto Namada, new NAM tokens will be minted to reward them. The reward amount shall be a protocol parameter that can be From 37927d950a892f013b5a65d4755bb128a2ff137e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 14:03:12 +0100 Subject: [PATCH 0505/1995] WIP: Shimming --- apps/src/lib/node/ledger/shell/mod.rs | 7 + .../lib/node/ledger/shell/prepare_proposal.rs | 331 +++++++++++++----- .../shell/vote_extensions/ethereum_events.rs | 39 ++- 3 files changed, 285 insertions(+), 92 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9b6e6b4830..27f6f3f1e2 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -753,6 +753,7 @@ mod test_utils { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; + use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; use namada::types::address::{xan, EstablishedAddressGen}; @@ -946,6 +947,12 @@ mod test_utils { } } + /// Get the only validator's voting power. + #[inline] + pub fn get_validator_voting_power() -> VotingPower { + 200.into() + } + /// Start a new test shell and initialize it. Returns the shell paired with /// a broadcast receiver, which will receives any protocol txs sent by the /// shell. diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 09dffd9985..50a425f2c8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -92,7 +92,9 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - #[cfg(feature = "abcipp")] local_last_commit: Option, + #[cfg(feature = "abcipp")] local_last_commit: Option< + ExtendedCommitInfo, + >, #[cfg(not(feature = "abcipp"))] txs: &[TxBytes], ) -> Vec { // genesis block should not contain vote extensions @@ -122,25 +124,36 @@ where vote extensions reflecting less than or equal to 2/3 of the \ total stake."; - let ethereum_events = self - .compress_ethereum_events(eth_events) - .expect(NOT_ENOUGH_VOTING_POWER_MSG); - - // TODO: check that we can only send one validator set - // update vote extension per epoch - // - // add feature flag gating at this level - let validator_set_update = - if self.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight, - ) { - Some( - self.compress_valset_updates(valset_upds) - .expect(NOT_ENOUGH_VOTING_POWER_MSG), - ) - } else { - None - }; + let ethereum_events = + self.compress_ethereum_events(eth_events).expect({ + #[cfg(feature = "abcipp")] + { + NOT_ENOUGH_VOTING_POWER_MSG + } + + #[cfg(not(feature = "abcipp"))] + { + "CONSENSUS FAILURE!!!!!" + } + }); + + #[cfg(feature = "abcipp")] + let validator_set_update = if self + .storage + .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) + { + Some( + self.compress_valset_updates(valset_upds) + .expect(NOT_ENOUGH_VOTING_POWER_MSG), + ) + } else { + None + }; + #[cfg(not(feature = "abcipp"))] + let validator_set_update = Some( + self.compress_valset_updates(valset_upds) + .expect("CONSENSUS FAILURE!!!!!"), + ); let protocol_key = self .mode @@ -279,17 +292,16 @@ mod test_prepare_proposal { use namada::types::vote_extensions::VoteExtension; use super::*; - use crate::wallet; + #[cfg(feature = "abicpp")] + use crate::facade::tendermint_proto::abci::{ + tx_record::TxAction, ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, + }; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::{ - self, gen_keypair, TestShell, + self, gen_keypair, get_validator_voting_power, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; - #[cfg(feature = "abicpp")] - use crate::facade::tendermint_proto::abci::{ - tx_record::TxAction, - ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, - }; + use crate::wallet; #[cfg(feature = "abicpp")] fn get_local_last_commit(shell: &TestShell) -> Option { @@ -428,7 +440,18 @@ mod test_prepare_proposal { ext }; + #[cfg(feature = "abcipp")] check_eth_events_filtering(&mut shell, signed_vote_extension); + + #[cfg(not(feature = "abcipp"))] + { + let filtered_votes: Vec<_> = + shell.filter_invalid_vote_extensions(votes).collect(); + assert_eq!( + filtered_votes, + vec![(get_validator_voting_power(), signed_vote_extension)] + ) + } } /// Test if we are filtering out Ethereum events seen by @@ -513,23 +536,35 @@ mod test_prepare_proposal { event: ext.data.ethereum_events[0].clone(), signers: { let mut s = HashSet::new(); + #[cfg(feature = "abcipp")] s.insert(ext.data.validator_addr.clone()); + #[cfg(not(feature = "abcipp"))] + s.insert((ext.data.validator_addr.clone(), last_height)); s }, }]; let signatures = { - let mut s = std::collections::HashMap::new(); + let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] s.insert(ext.data.validator_addr.clone(), ext.sig.clone()); + #[cfg(not(feature = "abcipp"))] + s.insert( + (ext.data.validator_addr.clone(), last_height), + ext.sig.clone(), + ); s }; let vote_extension_digest = ethereum_events::VextDigest { events, signatures }; + #[cfg(feature = "abcipp")] assert_eq!( vec![ext], vote_extension_digest.clone().decompress(last_height) ); + #[cfg(not(feature = "abcipp"))] + assert_eq!(vec![ext], vote_extension_digest.clone().decompress()); vote_extension_digest @@ -541,6 +576,7 @@ mod test_prepare_proposal { /// Test if Ethereum events validation and inclusion in a block /// behaves as expected, considering honest validators. + #[cfg(feature = "abcipp")] #[test] fn test_prepare_proposal_vext_normal_op() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); @@ -619,10 +655,87 @@ mod test_prepare_proposal { // assert_eq!(rsp.tx_records, vec![digest]); } + /// Test if Ethereum events validation and inclusion in a block + /// behaves as expected, considering honest validators. + #[cfg(not(feature = "abcipp"))] + #[test] + fn test_prepare_proposal_vext_normal_op() { + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + + let (mut shell, _, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let signed_vote_extension = { + let ext = ethereum_events::Vext { + validator_addr, + block_height: LAST_HEIGHT, + ethereum_events: vec![ethereum_event], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + + let rsp_digest = { + let vote_extension = VoteExtension { + ethereum_events: signed_vote_extension.clone(), + validator_set_update: None, + }; + let tx = ProtocolTxType::VoteExtension(vote_extension) + .sign(&protocol_key) + .to_bytes(); + let mut rsp = shell.prepare_proposal(RequestPrepareProposal { + txs: vec![tx], + ..Default::default() + }); + assert_eq!(rsp.txs.len(), 1); + + let tx_bytes = rsp.txs.pop().unwrap(); + let got = Tx::try_from(&tx_bytes[..]).unwrap(); + let got_signed_tx = + SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); + let protocol_tx = + TxType::try_from_slice(&got_signed_tx.data.unwrap()[..]) + .unwrap(); + let protocol_tx = match protocol_tx { + TxType::Protocol(protocol_tx) => protocol_tx.tx, + _ => panic!("Test failed"), + }; + + match protocol_tx { + ProtocolTxType::EthereumEvents(digest) => digest, + _ => panic!("Test failed"), + } + }; + + let digest = manually_assemble_digest( + &protocol_key, + signed_vote_extension, + LAST_HEIGHT, + ); + + assert_eq!(rsp_digest, digest); + + // NOTE: this comparison will not work because of timestamps + // assert_eq!(rsp.tx_records, vec![digest]); + } + /// Test if Ethereum events validation and inclusion in a block /// behaves as expected, considering <= 2/3 voting power. #[test] - #[should_panic(expected = "A Tendermint quorum should never")] + #[cfg_attr( + feature = "abcipp", + should_panic(expected = "A Tendermint quorum should never") + )] fn test_prepare_proposal_vext_insufficient_voting_power() { const FIRST_HEIGHT: BlockHeight = BlockHeight(0); const LAST_HEIGHT: BlockHeight = BlockHeight(FIRST_HEIGHT.0 + 11); @@ -678,7 +791,7 @@ mod test_prepare_proposal { nonce: 1u64.into(), transfers: vec![], }; - let ethereum_events = { + let signed_eth_ev_vote_extension = { let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, @@ -688,24 +801,61 @@ mod test_prepare_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - let vote = ExtendedVoteInfo { - vote_extension: VoteExtension { - ethereum_events, - validator_set_update: None, - } - .try_to_vec() - .unwrap(), - ..Default::default() + let vote_extension = VoteExtension { + ethereum_events: signed_eth_ev_vote_extension.clone(), + validator_set_update: None, }; - - // this should panic - shell.prepare_proposal(RequestPrepareProposal { - local_last_commit: Some(ExtendedCommitInfo { - votes: vec![vote], + #[cfg(feature = "abcipp")] + { + let vote = ExtendedVoteInfo { + vote_extension: vote_extension.try_to_vec().unwrap(), ..Default::default() - }), - ..Default::default() - }); + }; + // this should panic + shell.prepare_proposal(RequestPrepareProposal { + local_last_commit: Some(ExtendedCommitInfo { + votes: vec![vote], + ..Default::default() + }), + ..Default::default() + }); + } + #[cfg(not(feature = "abcipp"))] + { + let vote = ProtocolTxType::VoteExtension(vote_extension) + .sign(&protocol_key) + .to_bytes(); + let mut rsp = shell.prepare_proposal(RequestPrepareProposal { + txs: vec![vote], + ..Default::default() + }); + assert_eq!(rsp.txs.len(), 1); + + let tx_bytes = rsp.txs.pop().unwrap(); + let got = Tx::try_from(&tx_bytes[..]).unwrap(); + let got_signed_tx = + SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); + let protocol_tx = + TxType::try_from_slice(&got_signed_tx.data.unwrap()[..]) + .unwrap(); + let protocol_tx = match protocol_tx { + TxType::Protocol(protocol_tx) => protocol_tx.tx, + _ => panic!("Test failed"), + }; + + let digest = match protocol_tx { + ProtocolTxType::EthereumEvents(digest) => digest, + _ => panic!("Test failed"), + }; + + let expected = manually_assemble_digest( + &protocol_key, + signed_eth_ev_vote_extension, + LAST_HEIGHT, + ); + + assert_eq!(expected, digest); + } } /// Test that if an error is encountered while @@ -713,7 +863,7 @@ mod test_prepare_proposal { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -739,17 +889,19 @@ mod test_prepare_proposal { ), ) .to_bytes(); + #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { - local_last_commit: get_local_last_commit(&shell), txs: vec![wrapper.clone()], max_tx_bytes: 0, ..Default::default() }; + #[cfg(feature = "abcipp")] assert_eq!( - // NOTE: we process mempool txs after protocol txs - shell.prepare_proposal(req).tx_records.remove(1), - record::remove(wrapper) + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper)] ); + #[cfg(not(feature = "abcipp"))] + assert!(shell.prepare_proposal(req).txs.is_empty()); } /// Test that the decrypted txs are included @@ -757,7 +909,7 @@ mod test_prepare_proposal { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _, _) = setup(); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; @@ -765,7 +917,6 @@ mod test_prepare_proposal { let mut req = RequestPrepareProposal { txs: vec![], max_tx_bytes: 0, - local_last_commit: get_local_last_commit(&shell), ..Default::default() }; // create a request with two new wrappers from mempool and @@ -801,34 +952,50 @@ mod test_prepare_proposal { .iter() .map(|tx| tx.data.clone().expect("Test failed")) .collect(); - - let received: Vec> = shell - .prepare_proposal(req) - .tx_records - .iter() - // NOTE: skip Ethereum events protocol tx - .skip(1) - .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); + #[cfg(feature = "abcipp")] + { + 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); + } + #[cfg(not(feature = "abcipp"))] + { + let received: Vec> = shell + .prepare_proposal(req) + .txs + .into_iter() + .map(|tx_bytes| { + Tx::try_from(tx_bytes.as_slice()) + .expect("Test failed") + .data + .expect("Test failed") + }) + .collect(); + // check that the order of the txs is correct + assert_eq!(received, expected_txs); + } } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 857cf327e8..e9bc4ddb34 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -126,16 +126,16 @@ where /// valid Ethereum events vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. #[inline] - pub fn validate_eth_events_vext_list( - &self, + pub fn validate_eth_events_vext_list<'iter, 'this: 'iter>( + &'this self, vote_extensions: impl IntoIterator> - + 'static, + + 'iter, ) -> impl Iterator< Item = std::result::Result< (VotingPower, Signed), VoteExtensionError, >, - > + '_ { + > + 'this { vote_extensions.into_iter().map(|vote_extension| { self.validate_eth_events_vext_and_get_it_back( vote_extension, @@ -147,11 +147,11 @@ where /// Takes a list of signed Ethereum events vote extensions, /// and filters out invalid instances. #[inline] - pub fn filter_invalid_eth_events_vexts( + pub fn filter_invalid_eth_events_vexts<'iter, 'this: 'iter>( &self, vote_extensions: impl IntoIterator> - + 'static, - ) -> impl Iterator)> + '_ + + 'iter, + ) -> impl Iterator)> + 'this { self.validate_eth_events_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) @@ -160,7 +160,7 @@ where /// Compresses a set of signed Ethereum events into a single /// [`ethereum_events::VextDigest`], whilst filtering invalid /// [`Signed`] instances in the process. - pub fn compress_ethereum_events( + fn compress_ethereum_events( &self, vote_extensions: Vec>, ) -> Option { @@ -168,6 +168,10 @@ where self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", ); + #[cfg(not(feature = "abcipp"))] + if self.storage.last_height == BlockHeight(0) { + return None; + } let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); @@ -180,6 +184,7 @@ where self.filter_invalid_eth_events_vexts(vote_extensions) { let validator_addr = vote_extension.data.validator_addr; + let block_height = vote_extension.data.block_height; // update voting power let validator_voting_power = u64::from(validator_voting_power); @@ -196,14 +201,17 @@ where for ev in vote_extension.data.ethereum_events { let signers = event_observers.entry(ev).or_insert_with(HashSet::new); - + #[cfg(feature = "abcipp")] signers.insert(validator_addr.clone()); + #[cfg(not(feature = "abcipp"))] + signers.insert((validator_addr.clone(), block_height)); } // register the signature of `validator_addr` let addr = validator_addr.clone(); let sig = vote_extension.sig; + #[cfg(feature = "abcipp")] if let Some(sig) = signatures.insert(addr, sig) { tracing::warn!( ?sig, @@ -212,8 +220,19 @@ where constructing ethereum_events::VextDigest" ); } + + #[cfg(not(feature = "abcipp"))] + if let Some(sig) = signatures.insert((addr, block_height), sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing ethereum_events::VextDigest" + ); + } } + #[cfg(feature = "abcipp")] if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( "Tendermint has decided on a block including Ethereum events \ @@ -222,7 +241,7 @@ where return None; } - let events = event_observers + let events: Vec = event_observers .into_iter() .map(|(event, signers)| MultiSignedEthEvent { event, signers }) .collect(); From cc51bf4f00fb1e284f865e6b97bd466578b0aed7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 14:21:24 +0100 Subject: [PATCH 0506/1995] Shim out ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 81 ++++++++++++------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 32659cdee0..debe50c7b2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,13 +4,14 @@ use namada::ledger::pos::types::VotingPower; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::voting_power::FractionalVotingPower; -use tendermint_proto::abci::response_process_proposal::ProposalStatus; -use tendermint_proto::abci::{ - ExecTxResult, RequestProcessProposal, ResponseProcessProposal, -}; use super::queries::{QueriesExt, SendValsetUpd}; use super::*; +use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; +use crate::facade::tendermint_proto::abci::RequestProcessProposal; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto_abcipp::abci::ExecTxResult; +use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; /// Contains stateful data about the number of vote extension /// digests found as protocol transactions in a proposed block. @@ -42,7 +43,7 @@ where pub fn process_proposal( &self, req: RequestProcessProposal, - ) -> ResponseProcessProposal { + ) -> ProcessProposal { let mut tx_queue_iter = self.storage.tx_queue.iter(); tracing::info!( proposer = ?hex::encode(&req.proposer_address), @@ -122,34 +123,13 @@ where } else { ProposalStatus::Accept }; - tracing::info!( - proposer = ?hex::encode(&req.proposer_address), - height = req.height, - hash = ?hex::encode(&req.hash), - ?status, - "Processed block proposal", - ); - ResponseProcessProposal { + + ProcessProposal { status: status as i32, tx_results, - ..Default::default() } } - /// Check all the given txs. - 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, - &mut tx_queue_iter, - &mut Default::default(), - )) - }) - .collect() - } - /// Validates a list of vote extensions, included in PrepareProposal. /// /// If a vote extension is [`Some`], then it was validated properly, @@ -272,9 +252,11 @@ where TxType::Protocol(protocol_tx) => match protocol_tx.tx { ProtocolTxType::EthereumEvents(digest) => { counters.eth_ev_digest_num += 1; - + #[cfg(feature = "abcipp")] let extensions = digest.decompress(self.storage.last_height); + #[cfg(not(feature = "abcipp"))] + let extensions = digest.decompress(); let valid_extensions = self.validate_eth_events_vext_list(extensions).map( |maybe_ext| maybe_ext.ok().map(|(power, _)| power), @@ -427,6 +409,8 @@ mod test_process_proposal { }; use super::*; + use crate::facade::tendermint_proto::abci::RequestInitChain; + use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; @@ -480,7 +464,13 @@ mod test_process_proposal { ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] s.insert(validator_addr, signed_vote_extension.sig); + #[cfg(not(feature = "abcipp"))] + s.insert( + (validator_addr, LAST_HEIGHT), + signed_vote_extension.sig, + ); s }, events: vec![], @@ -556,14 +546,20 @@ mod test_process_proposal { ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] s.insert(addr.clone(), ext.sig); + #[cfg(not(feature = "abcipp"))] + s.insert((addr.clone(), LAST_HEIGHT), ext.sig); s }, events: vec![MultiSignedEthEvent { event, signers: { let mut s = HashSet::new(); + #[cfg(feature = "abcipp")] s.insert(addr); + #[cfg(not(feature = "abcipp"))] + s.insert((addr, LAST_HEIGHT)); s }, }], @@ -600,20 +596,41 @@ mod test_process_proposal { ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] s.insert(addr.clone(), ext.sig); + #[cfg(not(feature = "abcipp"))] + s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); s }, events: vec![MultiSignedEthEvent { event, signers: { let mut s = HashSet::new(); + #[cfg(feature = "abcipp")] s.insert(addr); + #[cfg(not(feature = "abcipp"))] + s.insert((addr, PRED_LAST_HEIGHT)); s }, }], } }; + #[cfg(feature = "abcipp")] check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); + #[cfg(not(feature = "abcipp"))] + { + let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + if let Ok(mut resp) = shell.process_proposal(request) { + assert_eq!(resp.len(), 1); + let processed = resp.remove(0); + assert_eq!(processed.result.code, ErrorCodes::Ok as u32); + } else { + panic!("Test failed"); + } + } } /// Test that if a proposal contains Ethereum events with @@ -646,14 +663,20 @@ mod test_process_proposal { ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] s.insert(addr.clone(), ext.sig); + #[cfg(not(feature = "abcipp"))] + s.insert((addr.clone(), LAST_HEIGHT), ext.sig); s }, events: vec![MultiSignedEthEvent { event, signers: { let mut s = HashSet::new(); + #[cfg(feature = "abcipp")] s.insert(addr); + #[cfg(not(feature = "abcipp"))] + s.insert((addr, LAST_HEIGHT)); s }, }], From cfd3677ec8403896f4d7f18626f1a00b4ab9dd22 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 31 Aug 2022 14:38:43 +0100 Subject: [PATCH 0507/1995] Add remaining logic changes from bat/ethbridge/shim-vext --- .../lib/node/ledger/shell/vote_extensions.rs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 306b2ba63b..ec8cc3cb0c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -9,9 +9,10 @@ use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, }; -use tendermint_proto::abci::ExtendedVoteInfo; use super::*; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::ExtendedVoteInfo; use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -47,18 +48,26 @@ where H: StorageHasher + Sync + 'static, { /// INVARIANT: This method must be stateless. + #[cfg(feature = "abcipp")] + #[inline] pub fn extend_vote( &mut self, _req: request::ExtendVote, ) -> response::ExtendVote { - let vote_extension = VoteExtension { + response::ExtendVote { + vote_extension: self.craft_extension().try_to_vec().unwrap(), + } + } + + /// Creates the data to be added to a vote extension. + /// + /// INVARIANT: This method must be stateless. + #[inline] + pub fn craft_extension(&mut self) -> VoteExtension { + VoteExtension { ethereum_events: self.extend_vote_with_ethereum_events(), validator_set_update: self.extend_vote_with_valset_update(), } - .try_to_vec() - .unwrap(); - - response::ExtendVote { vote_extension } } /// Extend PreCommit votes with [`ethereum_events::Vext`] instances. @@ -136,6 +145,7 @@ where /// (for replay protection). /// /// INVARIANT: This method must be stateless. + #[cfg(feature = "abcipp")] pub fn verify_vote_extension( &self, req: request::VerifyVoteExtension, @@ -171,6 +181,7 @@ where } /// Check if [`ethereum_events::Vext`] instances are valid. + #[cfg(feature = "abcipp")] pub fn verify_ethereum_events( &self, req: &request::VerifyVoteExtension, @@ -193,6 +204,7 @@ where } /// Check if [`validator_set_update::Vext`] instances are valid. + #[cfg(feature = "abcipp")] pub fn verify_valset_update( &self, req: &request::VerifyVoteExtension, From 380cae10b1a634596b20e4992cebc59c5b1aa42c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 31 Aug 2022 17:16:02 +0200 Subject: [PATCH 0508/1995] [feat]: Merged in ProcessProposal with val set updates PR --- Cargo.lock | 380 ++++++++++-------- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 45 ++- .../lib/node/ledger/shell/process_proposal.rs | 19 +- .../lib/node/ledger/shell/vote_extensions.rs | 9 +- .../{ethereum_events.rs => eth_events.rs} | 125 +++--- ...idator_set_update.rs => val_set_update.rs} | 163 +++++--- .../node/ledger/shims/abcipp_shim_types.rs | 1 + shared/src/types/vote_extensions.rs | 4 +- 10 files changed, 440 insertions(+), 314 deletions(-) rename apps/src/lib/node/ledger/shell/vote_extensions/{ethereum_events.rs => eth_events.rs} (87%) rename apps/src/lib/node/ledger/shell/vote_extensions/{validator_set_update.rs => val_set_update.rs} (80%) diff --git a/Cargo.lock b/Cargo.lock index 0eba89dc81..ae1007b406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,12 +656,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.9.3" @@ -1289,12 +1283,6 @@ 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" @@ -1472,18 +1460,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 0.14.5", - "rand_core 0.6.3", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -1514,16 +1490,6 @@ 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" @@ -1706,15 +1672,6 @@ 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" @@ -1879,18 +1836,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.5.2" @@ -1938,24 +1883,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 0.14.5", - "group", - "rand_core 0.6.3", - "sec1", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "embed-resource" version = "1.7.2" @@ -2205,16 +2132,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 2.4.1", -] - [[package]] name = "file-lock" version = "2.1.4" @@ -2621,17 +2538,6 @@ 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" @@ -2808,16 +2714,6 @@ 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" @@ -2990,6 +2886,33 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ibc" +version = "0.12.0" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +dependencies = [ + "bytes 1.1.0", + "derive_more", + "flex-error", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "time 0.3.9", + "tracing 0.1.35", +] + [[package]] name = "ibc" version = "0.12.0" @@ -2998,7 +2921,7 @@ dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -3009,14 +2932,27 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "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)", "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=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +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=bat/abciplus)", + "tonic", +] + [[package]] name = "ibc-proto" version = "0.16.0" @@ -3026,7 +2962,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tonic", ] @@ -3293,19 +3229,6 @@ 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" @@ -4366,8 +4289,10 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc", - "ibc-proto", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", "itertools 0.10.3", "libsecp256k1 0.7.0", @@ -4388,8 +4313,10 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", "tiny-keccak", @@ -4475,10 +4402,14 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "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=bat/abciplus)", + "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=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", "tokio", @@ -4487,7 +4418,8 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus)", + "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", @@ -6007,17 +5939,6 @@ 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" @@ -6333,18 +6254,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 0.14.5", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "secp256k1" version = "0.21.3" @@ -6689,10 +6598,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" @@ -6973,6 +6878,34 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tendermint" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +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=bat/abciplus)", + "time 0.3.9", + "zeroize", +] + [[package]] name = "tendermint" version = "0.23.5" @@ -6984,12 +6917,10 @@ 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", @@ -6998,11 +6929,24 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "time 0.3.9", "zeroize", ] +[[package]] +name = "tendermint-config" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +dependencies = [ + "flex-error", + "serde 1.0.137", + "serde_json", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "toml", + "url 2.2.2", +] + [[package]] name = "tendermint-config" version = "0.23.5" @@ -7011,11 +6955,24 @@ dependencies = [ "flex-error", "serde 1.0.137", "serde_json", - "tendermint", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", "url 2.2.2", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +dependencies = [ + "derive_more", + "flex-error", + "serde 1.0.137", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "time 0.3.9", +] + [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" @@ -7024,8 +6981,25 @@ dependencies = [ "derive_more", "flex-error", "serde 1.0.137", - "tendermint", - "tendermint-rpc", + "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=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +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", ] @@ -7046,6 +7020,39 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +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=bat/abciplus)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "thiserror", + "time 0.3.9", + "tokio", + "tracing 0.1.35", + "url 2.2.2", + "uuid", + "walkdir", +] + [[package]] name = "tendermint-rpc" version = "0.23.5" @@ -7067,9 +7074,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "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)", "thiserror", "time 0.3.9", "tokio", @@ -7079,6 +7086,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tendermint-testgen" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +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=bat/abciplus)", + "time 0.3.9", +] + [[package]] name = "tendermint-testgen" version = "0.23.5" @@ -7090,7 +7112,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "time 0.3.9", ] @@ -7551,6 +7573,24 @@ dependencies = [ "tracing 0.1.35", ] +[[package]] +name = "tower-abci" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus#21623a99bdca5b006d53752a1967849bef3b89ea" +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=bat/abciplus)", + "tokio", + "tokio-stream", + "tokio-util 0.6.10", + "tower", + "tracing 0.1.30", + "tracing-tower", +] + [[package]] name = "tower-abci" version = "0.1.0" @@ -7560,7 +7600,7 @@ dependencies = [ "futures 0.3.21", "pin-project 1.0.10", "prost 0.9.0", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8c8cd23b16..4433878e93 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -316,7 +316,7 @@ where continue; } TxType::Protocol(protocol_tx) => match protocol_tx.tx { - ProtocolTxType::EthEventsDigest(ref digest) => { + ProtocolTxType::EthereumEvents(ref digest) => { for event in digest.events.iter().map(|signed| &signed.event) { @@ -840,7 +840,7 @@ mod test_finalize_block { let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); - let tx = ProtocolTxType::EthEventsDigest(ethereum_events::VextDigest { + let tx = ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { signatures: Default::default(), events: vec![], }) @@ -917,7 +917,7 @@ mod test_finalize_block { events: vec![signed], }; let processed_tx = ProcessedTx { - tx: ProtocolTxType::EthEventsDigest(digest) + tx: ProtocolTxType::EthereumEvents(digest) .sign(&protocol_key) .to_bytes(), result: TxResult { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 27f6f3f1e2..7f0793c1b1 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -53,7 +53,7 @@ use super::rpc; use crate::config::{genesis, TendermintMode}; use crate::facade::tendermint_proto::abci::{ ConsensusParams, Misbehavior as Evidence, MisbehaviorType as EvidenceType, - RequestPrepareProposal, ValidatorUpdate, + ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; use crate::facade::tower_abci::{request, response}; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 50a425f2c8..067ad0b885 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -7,11 +7,12 @@ use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use tendermint_proto::abci::{ - ExtendedCommitInfo, RequestPrepareProposal, TxRecord, -}; use super::super::*; +use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::{ExtendedCommitInfo, TxRecord}; +#[cfg(feature = "abcipp")] use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shell::vote_extensions::{ iter_protocol_txs, split_vote_extensions, @@ -118,23 +119,26 @@ where ); #[cfg(not(feature = "abcipp"))] let (eth_events, valset_upds) = split_vote_extensions(txs); - + #[cfg(feature = "abcipp")] const NOT_ENOUGH_VOTING_POWER_MSG: &str = "A Tendermint quorum should never decide on a block including \ vote extensions reflecting less than or equal to 2/3 of the \ total stake."; - let ethereum_events = - self.compress_ethereum_events(eth_events).expect({ - #[cfg(feature = "abcipp")] - { - NOT_ENOUGH_VOTING_POWER_MSG - } - - #[cfg(not(feature = "abcipp"))] - { - "CONSENSUS FAILURE!!!!!" - } + let ethereum_events = self + .compress_ethereum_events(eth_events) + .unwrap_or_else(|| { + panic!("{}", { + #[cfg(feature = "abcipp")] + { + NOT_ENOUGH_VOTING_POWER_MSG + } + + #[cfg(not(feature = "abcipp"))] + { + "CONSENSUS FAILURE!!!!!" + } + }) }); #[cfg(feature = "abcipp")] @@ -272,7 +276,7 @@ pub(super) mod record { // TODO: write tests for validator set update vote extensions in // prepare proposals mod test_prepare_proposal { - use std::collections::HashSet; + use std::collections::{HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos::namada_proof_of_stake::types::{ @@ -445,8 +449,11 @@ mod test_prepare_proposal { #[cfg(not(feature = "abcipp"))] { - let filtered_votes: Vec<_> = - shell.filter_invalid_vote_extensions(votes).collect(); + let filtered_votes: Vec<_> = shell + .filter_invalid_eth_events_vexts(vec![ + signed_vote_extension.clone(), + ]) + .collect(); assert_eq!( filtered_votes, vec![(get_validator_voting_power(), signed_vote_extension)] @@ -909,7 +916,7 @@ mod test_prepare_proposal { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _, _) = setup(); + let (mut shell, _, _) = test_utils::setup(); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index debe50c7b2..343923c46a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -54,7 +54,7 @@ where ); // the number of vote extension digests included in the block proposal let mut counters = DigestCounters::default(); - let tx_results: Vec = req + let tx_results: Vec<_> = req .txs .iter() .map(|tx_bytes| { @@ -63,7 +63,6 @@ where &mut tx_queue_iter, &mut counters, ) - .into() }) .collect(); @@ -374,16 +373,19 @@ where /// Checks if we have found the correct number of validator set update /// vote extensions in [`DigestCounters`]. - fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { + fn has_proper_valset_upd_num(&self, _c: &DigestCounters) -> bool { + #[cfg(feature = "abcipp")] if self .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) { // TODO: confirm if we need a height check here or not - self.storage.last_height.0 > 0 && c.valset_upd_digest_num == 1 + self.storage.last_height.0 > 0 && _c.valset_upd_digest_num == 1 } else { true } + #[cfg(not(feature = "abcipp"))] + true } } @@ -409,7 +411,9 @@ mod test_process_proposal { }; use super::*; + #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::RequestInitChain; + #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, @@ -430,11 +434,18 @@ mod test_process_proposal { ) .sign(protocol_key); ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { + #[cfg(feature = "abcipp")] signatures: { let mut s = HashMap::new(); s.insert(addr, ext.sig); s }, + #[cfg(not(feature = "abcipp"))] + signatures: { + let mut s = HashMap::new(); + s.insert((addr, shell.storage.last_height), ext.sig); + s + }, events: vec![], }) .sign(protocol_key) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ec8cc3cb0c..8c765881af 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -1,11 +1,12 @@ //! Extend Tendermint votes with Ethereum bridge logic. -pub mod ethereum_events; -pub mod validator_set_update; +pub mod eth_events; +pub mod val_set_update; +#[cfg(feature = "abcipp")] use borsh::BorshDeserialize; use namada::proto::Signed; -use namada::types::transaction::protocol::ProtocolTxType; +use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, }; @@ -117,7 +118,7 @@ where // TODO: we need a way to map ethereum addresses to // namada validator addresses voting_powers: std::collections::HashMap::new(), - epoch: next_epoch, + block_height: self.storage.get_current_decision_height(), }; let protocol_key = match &self.mode { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs similarity index 87% rename from apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs rename to apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index e9bc4ddb34..e7f518cb9e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -30,6 +30,7 @@ where /// * The validator signed over the correct height inside of the extension /// * There are no duplicate Ethereum events in this vote extension, and /// the events are sorted in ascending order + #[allow(dead_code)] #[inline] pub fn validate_eth_events_vext( &self, @@ -51,6 +52,7 @@ where (VotingPower, Signed), VoteExtensionError, > { + #[cfg(feature = "abcipp")] if ext.data.block_height != last_height { let ext_height = ext.data.block_height; tracing::error!( @@ -126,8 +128,8 @@ where /// valid Ethereum events vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. #[inline] - pub fn validate_eth_events_vext_list<'iter, 'this: 'iter>( - &'this self, + pub fn validate_eth_events_vext_list<'iter>( + &'iter self, vote_extensions: impl IntoIterator> + 'iter, ) -> impl Iterator< @@ -135,7 +137,7 @@ where (VotingPower, Signed), VoteExtensionError, >, - > + 'this { + > + 'iter { vote_extensions.into_iter().map(|vote_extension| { self.validate_eth_events_vext_and_get_it_back( vote_extension, @@ -147,11 +149,11 @@ where /// Takes a list of signed Ethereum events vote extensions, /// and filters out invalid instances. #[inline] - pub fn filter_invalid_eth_events_vexts<'iter, 'this: 'iter>( - &self, + pub fn filter_invalid_eth_events_vexts<'iter>( + &'iter self, vote_extensions: impl IntoIterator> + 'iter, - ) -> impl Iterator)> + 'this + ) -> impl Iterator)> + 'iter { self.validate_eth_events_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) @@ -160,7 +162,7 @@ where /// Compresses a set of signed Ethereum events into a single /// [`ethereum_events::VextDigest`], whilst filtering invalid /// [`Signed`] instances in the process. - fn compress_ethereum_events( + pub fn compress_ethereum_events( &self, vote_extensions: Vec>, ) -> Option { @@ -254,7 +256,7 @@ where mod test_vote_extensions { use std::convert::TryInto; - use assert_matches::assert_matches; + #[cfg(feature = "abcipp")] use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; @@ -263,12 +265,17 @@ mod test_vote_extensions { }; use namada::types::key::*; use namada::types::storage::{BlockHeight, Epoch}; - use namada::types::vote_extensions::{ethereum_events, VoteExtension}; - use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; - use tower_abci::request; - + use namada::types::vote_extensions::ethereum_events; + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::VoteExtension; + + #[cfg(feature = "abcipp")] + use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + #[cfg(feature = "abcipp")] + use crate::facade::tower_abci::request; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; + #[cfg(feature = "abcipp")] use crate::node::ledger::shell::vote_extensions::VoteExtensionError; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; @@ -322,6 +329,7 @@ mod test_vote_extensions { /// Test that ethereum events are added to vote extensions. /// Check that vote extensions pass verification. + #[cfg(feature = "abcipp")] #[test] fn test_eth_events_vote_extension() { let (mut shell, _, oracle) = setup(); @@ -384,6 +392,7 @@ mod test_vote_extensions { .get_validator_address() .expect("Test failed") .clone(); + #[allow(clippy::redundant_clone)] let ethereum_events = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), @@ -397,6 +406,7 @@ mod test_vote_extensions { validator_addr: address.clone(), } .sign(&signing_key); + #[cfg(feature = "abcipp")] let req = request::VerifyVoteExtension { hash: vec![], validator_address: address @@ -406,16 +416,21 @@ mod test_vote_extensions { .to_vec(), height: 0, vote_extension: VoteExtension { - ethereum_events, + ethereum_events: ethereum_events.clone(), validator_set_update: None, } .try_to_vec() .expect("Test failed"), }; + #[cfg(feature = "abcipp")] assert_eq!( shell.verify_vote_extension(req).status, i32::from(VerifyStatus::Reject) ); + assert!(!shell.validate_eth_events_vext( + ethereum_events, + shell.storage.get_current_decision_height(), + )) } /// Test that validation of Ethereum events cast during the @@ -424,7 +439,7 @@ mod test_vote_extensions { /// change to the validator set. #[test] fn test_validate_eth_events_vexts() { - let (mut shell, _, _) = setup_at_height(3u64); + let (mut shell, _recv, _) = setup_at_height(3u64); let signing_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let address = shell @@ -493,6 +508,7 @@ mod test_vote_extensions { fn reject_incorrect_block_number() { let (shell, _, _) = setup_at_height(3u64); let address = shell.mode.get_validator_address().unwrap().clone(); + #[allow(clippy::redundant_clone)] let ethereum_events = ethereum_events::Vext { ethereum_events: vec![EthereumEvent::TransfersToEthereum { nonce: 1.into(), @@ -507,21 +523,29 @@ mod test_vote_extensions { } .sign(shell.mode.get_protocol_key().expect("Test failed")); - let req = request::VerifyVoteExtension { - hash: vec![], - validator_address: address.try_to_vec().expect("Test failed"), - height: 0, - vote_extension: VoteExtension { - ethereum_events, - validator_set_update: None, - } - .try_to_vec() - .expect("Test failed"), - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); + #[cfg(feature = "abcipp")] + { + let req = request::VerifyVoteExtension { + hash: vec![], + validator_address: address.try_to_vec().expect("Test failed"), + height: 0, + vote_extension: VoteExtension { + ethereum_events, + validator_set_update: None, + } + .try_to_vec() + .expect("Test failed"), + }; + + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } + assert!(shell.validate_eth_events_vext( + ethereum_events, + shell.storage.last_height + )) } /// Test if we reject Ethereum events vote extensions @@ -529,28 +553,37 @@ mod test_vote_extensions { #[test] fn test_reject_genesis_vexts() { let (shell, _, _) = setup(); - let address = shell - .mode - .get_validator_address() - .expect("Test failed") - .to_owned(); - let eth_evts = ethereum_events::Vext::empty(0u64.into(), address) - .sign(shell.mode.get_protocol_key().expect("Test failed")); + let address = shell.mode.get_validator_address().unwrap().clone(); + #[allow(clippy::redundant_clone)] + let vote_ext = ethereum_events::Vext { + ethereum_events: vec![EthereumEvent::TransfersToEthereum { + nonce: 1.into(), + transfers: vec![TransferToEthereum { + amount: 100.into(), + asset: EthAddress([1; 20]), + receiver: EthAddress([2; 20]), + }], + }], + block_height: shell.storage.last_height, + validator_addr: address.clone(), + } + .sign(shell.mode.get_protocol_key().expect("Test failed")); + + #[cfg(feature = "abcipp")] let req = request::VerifyVoteExtension { - vote_extension: VoteExtension { - validator_set_update: None, - ethereum_events: eth_evts.clone(), - } - .try_to_vec() - .expect("Test failed"), - ..Default::default() + hash: vec![], + validator_address: address.try_to_vec().expect("Test failed"), + height: 0, + vote_extension: vote_ext.try_to_vec().expect("Test failed"), }; + #[cfg(feature = "abcipp")] assert_eq!( shell.verify_vote_extension(req).status, i32::from(VerifyStatus::Reject) ); - let result = shell - .validate_eth_events_vext_and_get_it_back(eth_evts, 0u64.into()); - assert_matches!(result, Err(VoteExtensionError::IssuedAtGenesis)); + assert!( + !shell + .validate_eth_events_vext(vote_ext, shell.storage.last_height) + ) } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs similarity index 80% rename from apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs rename to apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 46d0f30bf4..1e85c887a4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -30,6 +30,7 @@ where /// * The voting powers are normalized to `2^32`, and sorted in descending /// order #[inline] + #[allow(dead_code)] pub fn validate_valset_upd_vext( &self, ext: validator_set_update::SignedVext, @@ -216,23 +217,30 @@ where mod test_vote_extensions { use std::default::Default; + #[cfg(feature = "abcipp")] + #[cfg(feature = "abcipp")] use borsh::BorshSerialize; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::types::key::RefTo; - use namada::types::vote_extensions::{ - ethereum_events, validator_set_update, VoteExtension, - }; - use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; - use tower_abci::request; - + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::VoteExtension; + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::ethereum_events; + use namada::types::vote_extensions::validator_set_update; + + #[cfg(feature = "abcipp")] + use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + #[cfg(feature = "abcipp")] + use crate::facade::tower_abci::request; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; /// Test if a [`validator_set_update::Vext`] that incorrectly labels what - /// block height it was included on in a vote extension is rejected + /// block height it was included on in a vote extension is rejected if + /// vote extensions are enabled. Else, it accepts. // TODO: // - sign with secp key // - add validator voting powers from storage @@ -244,12 +252,6 @@ mod test_vote_extensions { let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); - let ethereum_events = ethereum_events::Vext::empty( - shell.storage.get_current_decision_height(), - validator_addr.clone(), - ) - .sign(protocol_key); - let validator_set_update = Some( validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth @@ -262,20 +264,35 @@ mod test_vote_extensions { // TODO: sign with secp key .sign(protocol_key), ); - - let req = request::VerifyVoteExtension { - vote_extension: VoteExtension { - ethereum_events, - validator_set_update, - } - .try_to_vec() - .expect("Test failed"), - ..Default::default() - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); + #[cfg(feature = "abcipp")] + { + let ethereum_events = ethereum_events::Vext::empty( + shell.storage.get_current_decision_height(), + validator_addr.clone(), + ) + .sign(protocol_key); + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } + #[cfg(not(feature = "abcipp"))] + { + assert!(shell.validate_valset_upd_vext( + validator_set_update.unwrap(), + shell.storage.get_current_decision_height() + )) + } } /// Test that validator set update vote extensions signed by @@ -288,11 +305,7 @@ mod test_vote_extensions { let bertha_addr = wallet::defaults::bertha_address(); (bertha_key, bertha_addr) }; - let ethereum_events = ethereum_events::Vext::empty( - shell.storage.get_current_decision_height(), - validator_addr.clone(), - ) - .sign(&protocol_key); + let validator_set_update = Some( validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth @@ -303,19 +316,32 @@ mod test_vote_extensions { } .sign(&protocol_key), ); - let req = request::VerifyVoteExtension { - vote_extension: VoteExtension { - ethereum_events, - validator_set_update, - } - .try_to_vec() - .expect("Test failed"), - ..Default::default() - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); + #[cfg(feature = "abcipp")] + { + let ethereum_events = ethereum_events::Vext::empty( + shell.storage.get_current_decision_height(), + validator_addr.clone(), + ) + .sign(&protocol_key); + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } + #[cfg(not(feature = "abcipp"))] + assert!(!shell.validate_valset_upd_vext( + validator_set_update.unwrap(), + shell.storage.get_current_decision_height() + )); } /// Test the validation of a validator set update emitted for @@ -324,7 +350,7 @@ mod test_vote_extensions { /// change to the validator set. #[test] fn test_validate_valset_upd_vexts() { - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); let validator_addr = shell @@ -398,12 +424,6 @@ mod test_vote_extensions { let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); - let ethereum_events = ethereum_events::Vext::empty( - shell.storage.get_current_decision_height(), - validator_addr.clone(), - ) - .sign(protocol_key); - let validator_set_update = { let mut ext = validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth @@ -417,20 +437,31 @@ mod test_vote_extensions { ext.sig = test_utils::invalidate_signature(ext.sig); Some(ext) }; - - let req = request::VerifyVoteExtension { - vote_extension: VoteExtension { - ethereum_events, - validator_set_update, - } - .try_to_vec() - .expect("Test failed"), - ..Default::default() - }; - assert_eq!( - shell.verify_vote_extension(req).status, - i32::from(VerifyStatus::Reject) - ); + #[cfg(feature = "abcipp")] + { + let ethereum_events = ethereum_events::Vext::empty( + shell.storage.get_current_decision_height(), + validator_addr.clone(), + ) + .sign(protocol_key); + let req = request::VerifyVoteExtension { + vote_extension: VoteExtension { + ethereum_events, + validator_set_update, + } + .try_to_vec() + .expect("Test failed"), + ..Default::default() + }; + assert_eq!( + shell.verify_vote_extension(req).status, + i32::from(VerifyStatus::Reject) + ); + } + assert!(shell.validate_valset_upd_vext( + validator_set_update.unwrap(), + shell.storage.get_current_decision_height() + )); } /// Test if a [`validator_set_update::Vext`] is signed with a secp key 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 d628374cf1..803092dd3d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -268,6 +268,7 @@ pub mod shim { #[cfg(feature = "abcipp")] use tendermint_proto_abcipp::types::ConsensusParams; + #[cfg(feature = "abcipp")] use super::*; use crate::node::ledger::events::Event; #[cfg(feature = "abcipp")] diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index 1e0def9001..dace23a8ab 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -9,7 +9,9 @@ use crate::proto::Signed; /// This type represents the data we pass to the extension of /// a vote at the PreCommit phase of Tendermint. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct VoteExtension { /// Vote extension data related with Ethereum events. pub ethereum_events: Signed, From 55e588893b9d0cb99c2a1652afb60559d4171ad6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 31 Aug 2022 17:05:31 +0100 Subject: [PATCH 0509/1995] compress_valset_updates: use current epoch's voting powers --- .../vote_extensions/validator_set_update.rs | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index d97182598a..97b8da29a7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -151,25 +151,17 @@ where /// single [`validator_set_update::VextDigest`], whilst filtering /// invalid [`validator_set_update::SignedVext`] instances in the /// process. - #[allow(dead_code)] pub fn compress_valset_updates( &self, vote_extensions: Vec, ) -> Option { - let total_voting_power = { - let current_valset_epoch = self.storage.get_current_epoch().0; - if current_valset_epoch == Epoch(0) { - tracing::error!( - epoch = ?current_valset_epoch, - "Cannot compress validator set update vote extensions at the given epoch" - ); - return None; - } - let prev_valset_epoch = current_valset_epoch - 1; - u64::from( - self.storage.get_total_voting_power(Some(prev_valset_epoch)), - ) - }; + let events_epoch = + self.storage.get_epoch(self.storage.last_height).expect( + "The epoch of the last block height should always be known", + ); + + let total_voting_power = + u64::from(self.storage.get_total_voting_power(Some(events_epoch))); let mut voting_power = FractionalVotingPower::default(); let mut voting_powers = None; From 942b0916c5b5d69320c902e3ece9cb582b0eeb24 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 31 Aug 2022 17:06:04 +0100 Subject: [PATCH 0510/1995] compress_ethereum_events: reorder vars to be same as compress_valset_updates --- .../node/ledger/shell/vote_extensions/ethereum_events.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 857cf327e8..01d69a84ca 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -169,13 +169,13 @@ where "The epoch of the last block height should always be known", ); - let mut event_observers = BTreeMap::new(); - let mut signatures = HashMap::new(); - let total_voting_power = u64::from(self.storage.get_total_voting_power(Some(events_epoch))); let mut voting_power = FractionalVotingPower::default(); + let mut event_observers = BTreeMap::new(); + let mut signatures = HashMap::new(); + for (validator_voting_power, vote_extension) in self.filter_invalid_eth_events_vexts(vote_extensions) { From 595d14702670500e3231daa55c6e1fdbe13a34cc Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 31 Aug 2022 17:09:32 +0100 Subject: [PATCH 0511/1995] Rename events_epoch -> vexts_epoch --- .../lib/node/ledger/shell/vote_extensions/ethereum_events.rs | 4 ++-- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs index 01d69a84ca..e97eb2bda4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/ethereum_events.rs @@ -164,13 +164,13 @@ where &self, vote_extensions: Vec>, ) -> Option { - let events_epoch = + let vexts_epoch = self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", ); let total_voting_power = - u64::from(self.storage.get_total_voting_power(Some(events_epoch))); + u64::from(self.storage.get_total_voting_power(Some(vexts_epoch))); let mut voting_power = FractionalVotingPower::default(); let mut event_observers = BTreeMap::new(); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 97b8da29a7..642e952b41 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -155,13 +155,13 @@ where &self, vote_extensions: Vec, ) -> Option { - let events_epoch = + let vexts_epoch = self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", ); let total_voting_power = - u64::from(self.storage.get_total_voting_power(Some(events_epoch))); + u64::from(self.storage.get_total_voting_power(Some(vexts_epoch))); let mut voting_power = FractionalVotingPower::default(); let mut voting_powers = None; From 0023b8584b47545dbb79fc423f0e62de447a0d95 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 09:24:28 +0100 Subject: [PATCH 0512/1995] WIP: Fix abcipp feature flag --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/gossip.rs | 2 +- apps/src/lib/client/rpc.rs | 14 +++++++------- apps/src/lib/client/signing.rs | 2 +- apps/src/lib/client/tendermint_rpc_types.rs | 5 ++--- .../src/lib/client/tendermint_websocket_client.rs | 15 ++++++++------- apps/src/lib/client/tm_jsonrpc_client.rs | 4 ++-- apps/src/lib/client/tx.rs | 8 ++++---- apps/src/lib/client/utils.rs | 4 ++-- apps/src/lib/config/mod.rs | 4 ++-- apps/src/lib/node/ledger/broadcaster.rs | 3 ++- apps/src/lib/node/ledger/rpc.rs | 3 ++- apps/src/lib/node/ledger/shell/mod.rs | 7 +++++-- .../src/lib/node/ledger/shell/process_proposal.rs | 4 ++-- .../shell/vote_extensions/val_set_update.rs | 4 ++-- apps/src/lib/node/matchmaker.rs | 4 ++-- shared/src/types/hash.rs | 5 +++-- 17 files changed, 49 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..d5626a21d0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1372,14 +1372,14 @@ pub mod args { use namada::types::token; use namada::types::transaction::GasLimit; use serde::Deserialize; - use tendermint::Timeout; - use tendermint_config::net::Address as TendermintAddress; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; use super::ArgMatches; use crate::config; use crate::config::TendermintMode; + use crate::facade::tendermint::Timeout; + use crate::facade::tendermint_config::net::Address as TendermintAddress; const ADDRESS: Arg = arg("address"); const ALIAS_OPT: ArgOpt = ALIAS.opt(); diff --git a/apps/src/lib/client/gossip.rs b/apps/src/lib/client/gossip.rs index 2225898ff4..80444d942c 100644 --- a/apps/src/lib/client/gossip.rs +++ b/apps/src/lib/client/gossip.rs @@ -4,10 +4,10 @@ use std::io::Write; use borsh::BorshSerialize; use namada::proto::Signed; use namada::types::intent::{Exchange, FungibleTokenIntent}; -use tendermint_config::net::Address as TendermintAddress; use super::signing; use crate::cli::{self, args, Context}; +use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::proto::services::rpc_service_client::RpcServiceClient; use crate::proto::{services, RpcMessage}; use crate::wallet::Wallet; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4209772be2..8f462a0dd8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,16 +30,16 @@ use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use tendermint::abci::Code; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_rpc::error::Error as TError; -use tendermint_rpc::query::Query; -use tendermint_rpc::{ - Client, HttpClient, Order, SubscriptionClient, WebSocketClient, -}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; +use crate::facade::tendermint::abci::Code; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_rpc::error::Error as TError; +use crate::facade::tendermint_rpc::query::Query; +use crate::facade::tendermint_rpc::{ + Client, HttpClient, Order, SubscriptionClient, WebSocketClient, +}; use crate::node::ledger::rpc::Path; /// Query the epoch of the last committed block diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index dd86470403..fb87151c82 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -9,12 +9,12 @@ use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -use tendermint_config::net::Address as TendermintAddress; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxBroadcastData; +use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::wallet::Wallet; /// Find the public key for the given address and try to load the keypair diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 6575c74082..0a7406aed0 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -135,9 +135,9 @@ mod params { use serde::ser::SerializeTuple; use serde::{Deserialize, Serializer}; - use tendermint_rpc::query::Query; use super::*; + use crate::facade::tendermint_rpc::query::Query; /// Opaque type for ordering events. Set by Tendermint #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] @@ -310,9 +310,8 @@ mod params { #[cfg(test)] mod test_rpc_types { - use tendermint_rpc::query::EventType; - use super::*; + use crate::facade::tendermint_rpc::query::EventType; /// Test that [`EventParams`] is serialized correctly #[test] diff --git a/apps/src/lib/client/tendermint_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs index d5af3bcee1..6f70464e58 100644 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ b/apps/src/lib/client/tendermint_websocket_client.rs @@ -6,15 +6,16 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use async_trait::async_trait; -use tendermint_config::net::Address; -use tendermint_rpc::{ - Client, Error as RpcError, Request, Response, SimpleRequest, -}; use thiserror::Error; use tokio::time::Instant; use websocket::result::WebSocketError; use websocket::{ClientBuilder, Message, OwnedMessage}; +use crate::facade::tendermint_config::net::Address; +use crate::facade::tendermint_rpc::{ + Client, Error as RpcError, Request, Response, SimpleRequest, +}; + #[derive(Error, Debug)] pub enum Error { #[error("Could not convert into websocket address: {0:?}")] @@ -49,11 +50,11 @@ mod rpc_types { use std::str::FromStr; use serde::{de, Deserialize, Serialize, Serializer}; - use tendermint_rpc::method::Method; - use tendermint_rpc::query::{EventType, Query}; - use tendermint_rpc::{request, response}; use super::Json; + use crate::facade::tendermint_rpc::method::Method; + use crate::facade::tendermint_rpc::query::{EventType, Query}; + use crate::facade::tendermint_rpc::{request, response}; #[derive(Debug, Deserialize, Serialize)] pub struct RpcRequest { diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs index 7372012ff5..88ec006be2 100644 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -4,12 +4,12 @@ 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 crate::client::tendermint_rpc_types::{ parse, Error, EventParams, EventReply, TxResponse, }; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_rpc::query::Query; /// Maximum number of times we try to send a curl request const MAX_SEND_ATTEMPTS: u8 = 10; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8daf318435..79d3e464a0 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -25,10 +25,6 @@ use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, token}; use namada::{ledger, vm}; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::query::{EventType, Query}; -use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; @@ -39,6 +35,10 @@ use crate::client::tendermint_websocket_client::{ Error as WsError, TendermintWebsocketClient, WebSocketAddress, }; use crate::client::tm_jsonrpc_client::{fetch_event, JsonRpcAddress}; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use crate::facade::tendermint_rpc::query::{EventType, Query}; +use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::tendermint_node; const ACCEPTED_QUERY_KEY: &str = "accepted.hash"; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 9043a14db7..8f56b1b87c 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -18,8 +18,6 @@ use rand::prelude::ThreadRng; use rand::thread_rng; use serde_json::json; use sha2::{Digest, Sha256}; -use tendermint::node::Id as TendermintNodeId; -use tendermint_config::net::Address as TendermintAddress; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; @@ -30,6 +28,8 @@ use crate::config::global::GlobalConfig; use crate::config::{ self, Config, IntentGossiper, PeerAddress, TendermintMode, }; +use crate::facade::tendermint::node::Id as TendermintNodeId; +use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::gossip; use crate::node::ledger::tendermint_node; use crate::wallet::{pre_genesis, Wallet}; diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index edf13cbeff..5699f02a32 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -19,11 +19,11 @@ use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; use regex::Regex; use serde::{de, Deserialize, Serialize}; -use tendermint::Timeout; -use tendermint_config::net::Address as TendermintAddress; use thiserror::Error; use crate::cli; +use crate::facade::tendermint::Timeout; +use crate::facade::tendermint_config::net::Address as TendermintAddress; /// Base directory contains global config and chain directories. pub const DEFAULT_BASE_DIR: &str = ".anoma"; diff --git a/apps/src/lib/node/ledger/broadcaster.rs b/apps/src/lib/node/ledger/broadcaster.rs index 94aec1a00c..199ab953c1 100644 --- a/apps/src/lib/node/ledger/broadcaster.rs +++ b/apps/src/lib/node/ledger/broadcaster.rs @@ -1,6 +1,7 @@ -use tendermint_rpc::{Client, HttpClient}; use tokio::sync::mpsc::UnboundedReceiver; +use crate::facade::tendermint_rpc::{Client, HttpClient}; + /// A service for broadcasting txs via an HTTP client. /// The receiver is for receiving message payloads for other services /// to be broadcast. diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index ce3cbd592d..ad3d2f5fcb 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -5,9 +5,10 @@ use std::str::FromStr; use namada::types::address::Address; use namada::types::storage; -use tendermint::abci::Path as AbciPath; use thiserror::Error; +use crate::facade::tendermint::abci::Path as AbciPath; + /// RPC query path #[derive(Debug, Clone)] pub enum Path { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7f0793c1b1..77805aa6c6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -51,11 +51,14 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use super::protocol::ShellParams; use super::rpc; use crate::config::{genesis, TendermintMode}; +#[cfg(not(feature = "abcipp"))] +use crate::facade::tendermint_proto::abci::ConsensusParams; use crate::facade::tendermint_proto::abci::{ - ConsensusParams, Misbehavior as Evidence, MisbehaviorType as EvidenceType, - ValidatorUpdate, + Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tower_abci::{request, response}; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 343923c46a..c52fd577ad 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -8,9 +8,9 @@ use namada::types::voting_power::FractionalVotingPower; use super::queries::{QueriesExt, SendValsetUpd}; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; -use crate::facade::tendermint_proto::abci::RequestProcessProposal; #[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto_abcipp::abci::ExecTxResult; +use crate::facade::tendermint_proto::abci::ExecTxResult; +use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; /// Contains stateful data about the number of vote extension diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 1e85c887a4..be2d8163a8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -224,10 +224,10 @@ mod test_vote_extensions { use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::types::key::RefTo; #[cfg(feature = "abcipp")] - use namada::types::vote_extensions::VoteExtension; - #[cfg(feature = "abcipp")] use namada::types::vote_extensions::ethereum_events; use namada::types::vote_extensions::validator_set_update; + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::VoteExtension; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 0a01528b00..edd7a1ac32 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -14,8 +14,6 @@ use namada::types::intent::{IntentTransfers, MatchedExchanges}; use namada::types::key::*; use namada::types::matchmaker::AddIntentResult; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -use tendermint_config::net; -use tendermint_config::net::Address as TendermintAddress; use super::gossip::rpc::matchmakers::{ ClientDialer, ClientListener, MsgFromClient, MsgFromServer, @@ -24,6 +22,8 @@ use crate::cli::args; use crate::client::rpc; use crate::client::tendermint_rpc_types::TxBroadcastData; use crate::client::tx::broadcast_tx; +use crate::facade::tendermint_config::net; +use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::{cli, config, wasm_loader}; /// Run a matchmaker diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 5455c7a823..5d33a762a7 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,10 +6,11 @@ use std::ops::Deref; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use tendermint::abci::transaction; -use tendermint::Hash as TmHash; use thiserror::Error; +use crate::tendermint::abci::transaction; +use crate::tendermint::Hash as TmHash; + /// The length of the transaction hash string pub const HASH_LENGTH: usize = 32; From e72720f3f5ea25ab019d0dc1f3a9d4e3837b6efa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 09:24:40 +0100 Subject: [PATCH 0513/1995] Add Cargo.lock files --- wasm/tx_template/Cargo.lock | 162 ++------------------------ wasm/vp_template/Cargo.lock | 162 ++------------------------ wasm/wasm_source/Cargo.lock | 162 ++------------------------ wasm_for_tests/wasm_source/Cargo.lock | 162 ++------------------------ 4 files changed, 32 insertions(+), 616 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 0ba212fdb4..e77113a834 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/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" @@ -427,12 +421,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" @@ -567,18 +555,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" @@ -589,16 +565,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" @@ -660,15 +626,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" @@ -737,18 +694,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" @@ -791,24 +736,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" @@ -932,16 +859,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 = "fixed-hash" version = "0.7.0" @@ -1091,17 +1008,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" @@ -1174,16 +1080,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" @@ -1257,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "derive_more", @@ -1284,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "prost", @@ -1415,19 +1311,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" @@ -2322,17 +2205,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" @@ -2509,18 +2381,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" @@ -2650,10 +2510,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" @@ -2791,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "async-trait", "bytes", @@ -2799,12 +2655,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2821,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "flex-error", "serde", @@ -2834,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "derive_more", "flex-error", @@ -2847,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2864,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2888,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index e6242cce48..3bb42f693f 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/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" @@ -427,12 +421,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" @@ -567,18 +555,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" @@ -589,16 +565,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" @@ -660,15 +626,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" @@ -737,18 +694,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" @@ -791,24 +736,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" @@ -932,16 +859,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 = "fixed-hash" version = "0.7.0" @@ -1091,17 +1008,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" @@ -1174,16 +1080,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" @@ -1257,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "derive_more", @@ -1284,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "prost", @@ -1415,19 +1311,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" @@ -2322,17 +2205,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" @@ -2509,18 +2381,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" @@ -2650,10 +2510,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" @@ -2791,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "async-trait", "bytes", @@ -2799,12 +2655,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2821,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "flex-error", "serde", @@ -2834,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "derive_more", "flex-error", @@ -2847,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2864,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2888,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 7238da664b..f1ad3c4e5f 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" @@ -427,12 +421,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" @@ -567,18 +555,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" @@ -589,16 +565,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" @@ -660,15 +626,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" @@ -737,18 +694,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" @@ -791,24 +736,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" @@ -932,16 +859,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 = "fixed-hash" version = "0.7.0" @@ -1091,17 +1008,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" @@ -1174,16 +1080,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" @@ -1257,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "derive_more", @@ -1284,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "prost", @@ -1415,19 +1311,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" @@ -2348,17 +2231,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" @@ -2535,18 +2407,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" @@ -2676,10 +2536,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" @@ -2817,7 +2673,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "async-trait", "bytes", @@ -2825,12 +2681,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2847,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "flex-error", "serde", @@ -2860,7 +2714,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "derive_more", "flex-error", @@ -2873,7 +2727,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2890,7 +2744,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2914,7 +2768,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index ef833d5532..1aeb3185e4 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/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" @@ -427,12 +421,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" @@ -568,18 +556,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" @@ -590,16 +566,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" @@ -661,15 +627,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" @@ -738,18 +695,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.1" @@ -792,24 +737,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" @@ -933,16 +860,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 = "fixed-hash" version = "0.7.0" @@ -1092,17 +1009,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.1" @@ -1184,16 +1090,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" @@ -1267,7 +1163,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "derive_more", @@ -1294,7 +1190,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" dependencies = [ "bytes", "prost", @@ -1425,19 +1321,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" @@ -2354,17 +2237,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" @@ -2541,18 +2413,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" @@ -2682,10 +2542,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" @@ -2823,7 +2679,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "async-trait", "bytes", @@ -2831,12 +2687,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2853,7 +2707,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "flex-error", "serde", @@ -2866,7 +2720,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "derive_more", "flex-error", @@ -2879,7 +2733,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2896,7 +2750,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "bytes", "flex-error", @@ -2920,7 +2774,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" dependencies = [ "ed25519-dalek", "gumdrop", From cfef1fc8822bb7274ecc2e2b805ec39ec279c86e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 10:01:43 +0100 Subject: [PATCH 0514/1995] WIP: More abcipp feature flag fixes --- apps/src/lib/client/tendermint_rpc_types.rs | 2 +- apps/src/lib/client/tm_jsonrpc_client.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 16 ++++++++++------ .../lib/node/ledger/shell/vote_extensions.rs | 2 ++ .../node/ledger/shims/abcipp_shim_types.rs | 19 ++++++++----------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 0a7406aed0..ff185d2718 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -15,7 +15,7 @@ pub enum Error { #[error("Error in sending JSON RPC request to Tendermint")] Send, #[error("Received an error response from Tendermint: {0:?}")] - Rpc(tendermint_rpc::response_error::ResponseError), + Rpc(crate::facade::tendermint_rpc::response_error::ResponseError), #[error("Received malformed JSON response from Tendermint")] MalformedJson, #[error("Received an empty response from Tendermint")] diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs index 88ec006be2..ccdf402577 100644 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -72,7 +72,7 @@ pub struct Response { /// Results of request (if successful) result: Option, /// Error message if unsuccessful - error: Option, + error: Option, } impl Response { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 067ad0b885..2d099870a1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -11,7 +11,9 @@ use namada::types::vote_extensions::VoteExtensionDigest; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto::abci::{ExtendedCommitInfo, TxRecord}; +use crate::facade::tendermint_proto::abci::{ + tx_record::TxAction, ExtendedCommitInfo, TxRecord, +}; #[cfg(feature = "abcipp")] use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shell::vote_extensions::{ @@ -243,7 +245,6 @@ where #[cfg(feature = "abcipp")] pub(super) mod record { use super::*; - use crate::facade::tendermint_proto::abci::tx_record::TxAction; /// Keep this transaction in the proposal pub fn keep(tx: TxBytes) -> TxRecord { @@ -296,18 +297,18 @@ mod test_prepare_proposal { use namada::types::vote_extensions::VoteExtension; use super::*; - #[cfg(feature = "abicpp")] + #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::{ - self, gen_keypair, get_validator_voting_power, TestShell, + self, gen_keypair, TestShell, }; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; - #[cfg(feature = "abicpp")] + #[cfg(feature = "abcipp")] fn get_local_last_commit(shell: &TestShell) -> Option { let evts = { let validator_addr = shell @@ -456,7 +457,10 @@ mod test_prepare_proposal { .collect(); assert_eq!( filtered_votes, - vec![(get_validator_voting_power(), signed_vote_extension)] + vec![( + test_utils::get_validator_voting_power(), + signed_vote_extension + )] ) } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 8c765881af..eafd4092fd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -151,6 +151,8 @@ where &self, req: request::VerifyVoteExtension, ) -> response::VerifyVoteExtension { + use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; + let ext = match VoteExtension::try_from_slice(&req.vote_extension[..]) { Ok(ext) => ext, Err(err) => { 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 803092dd3d..03adab2e3a 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -255,24 +255,21 @@ pub mod shim { /// Custom types for response payloads pub mod response { - #[cfg(not(feature = "abcipp"))] - use tendermint_proto::abci::{ - ConsensusParams, Event as TmEvent, ResponseProcessProposal, - ValidatorUpdate, - }; #[cfg(feature = "abcipp")] - use tendermint_proto_abcipp::abci::{ - Event as TmEvent, ExecTxResult, ResponseFinalizeBlock, + use super::*; + use crate::facade::tendermint_proto::abci::{ + Event as TmEvent, ExecTxResult, ResponseProcessProposal, ValidatorUpdate, }; #[cfg(feature = "abcipp")] - use tendermint_proto_abcipp::types::ConsensusParams; - - #[cfg(feature = "abcipp")] - use super::*; + use crate::facade::tendermint_proto::{ + abci::ResponseFinalizeBlock, types::ConsensusParams, + }; use crate::node::ledger::events::Event; #[cfg(feature = "abcipp")] use crate::node::ledger::events::EventLevel; + #[cfg(not(feature = "abcipp"))] + use crate::tendermint_proto::abci::ConsensusParams; #[derive(Debug, Default)] pub struct VerifyHeader; From f8f68c83fe45b1fd208995881fcb5f0f8ca452e8 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 1 Sep 2022 11:06:58 +0200 Subject: [PATCH 0515/1995] [fix]: Tiny typo --- .../specs/src/interoperability/ethereum-bridge/proofs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index 5be3ec9bb8..834f50bba8 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -3,7 +3,7 @@ A proof for the bridge is a quorum of signatures by a valid validator set. A bridge header is a proof attached to a message understandable to the Ethereum smart contracts. For transferring value to Ethereum, a proof is a -signed Merkle tree root and inclusion proofs of assert transfer messages +signed Merkle tree root and inclusion proofs of asset transfer messages understandable to the Ethereum smart contracts, as described in the section on [batching](transfers_to_ethereum.md/#batching) From 303fc17f8cefdda7c4e5979ac4531676ad6d5abf Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 1 Sep 2022 11:07:16 +0200 Subject: [PATCH 0516/1995] Update documentation/specs/src/interoperability/ethereum-bridge/proofs.md Co-authored-by: James --- .../specs/src/interoperability/ethereum-bridge/proofs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index 834f50bba8..8b105102a4 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -34,7 +34,7 @@ Due to asynchronicity concerns, this message should be submitted well in advance of the actual epoch change. It should happen at the beginning of each new epoch. Bridge headers to ethereum should include the current Namada epoch so that the smart contract knows how to verify the headers. In short, there -is a pipelining mechanism in the smart contract. +is a pipelining mechanism in the smart contract - the active validators for epoch `n` submit details of the active validator set for epoch `n+1`. Such a message is not prompted by any user transaction and thus will have to be carried out by a _bridge relayer_. Once the necessary data to From ddb8043102b171c985f488a4c5be829b1c0acca2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 10:30:40 +0100 Subject: [PATCH 0517/1995] More fixes --- apps/src/lib/node/ledger/shell/queries.rs | 55 ++++++++++++++++++- .../lib/node/ledger/shell/vote_extensions.rs | 2 +- shared/src/types/time.rs | 2 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 35bc500d67..e98d08ca5e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -159,7 +159,20 @@ where let mut cur_ops: Vec = p .ops .into_iter() - .map(|op| op.into()) + .map(|op| { + #[cfg(feature = "abcipp")] + { + ProofOp { + r#type: op.field_type, + key: op.key, + data: op.data, + } + } + #[cfg(not(feature = "abcipp"))] + { + op.into() + } + }) .collect(); ops.append(&mut cur_ops); } @@ -211,7 +224,25 @@ where value.clone(), height, ) { - Ok(proof) => Some(proof.into()), + Ok(proof) => Some({ + #[cfg(feature = "abcipp")] + { + let ops = proof + .ops + .into_iter() + .map(|op| ProofOp { + r#type: op.field_type, + key: op.key, + data: op.data, + }) + .collect(); + ProofOps { ops } + } + #[cfg(not(feature = "abcipp"))] + { + proof.into() + } + }), Err(err) => { return response::Query { code: 2, @@ -232,7 +263,25 @@ where Ok((None, _gas)) => { let proof_ops = if is_proven { match self.storage.get_non_existence_proof(key, height) { - Ok(proof) => Some(proof.into()), + Ok(proof) => Some({ + #[cfg(feature = "abcipp")] + { + let ops = proof + .ops + .into_iter() + .map(|op| ProofOp { + r#type: op.field_type, + key: op.key, + data: op.data, + }) + .collect(); + ProofOps { ops } + } + #[cfg(not(feature = "abcipp"))] + { + proof.into() + } + }), Err(err) => { return response::Query { code: 2, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index eafd4092fd..ed449e08ad 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -221,7 +221,7 @@ where // cool, let's validate it self.validate_valset_upd_vext( ext, - self.storage.get_current_epoch().0.next(), + self.storage.get_current_decision_height(), ) .then(|| true) }) diff --git a/shared/src/types/time.rs b/shared/src/types/time.rs index ec91ff5b14..dfca614c82 100644 --- a/shared/src/types/time.rs +++ b/shared/src/types/time.rs @@ -6,10 +6,10 @@ use std::ops::{Add, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use chrono::{DateTime, Duration, TimeZone, Utc}; -use tendermint_proto::google::protobuf; use crate::tendermint::time::Time; use crate::tendermint::Error as TendermintError; +use crate::tendermint_proto::google::protobuf; /// Check if the given `duration` has passed since the given `start. pub fn duration_passed( From bb966fe2b7269664ad48e15402e1bb5694f42914 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 10:57:36 +0100 Subject: [PATCH 0518/1995] Fixes --- .../ledger/shell/vote_extensions/eth_events.rs | 2 +- .../shell/vote_extensions/val_set_update.rs | 17 ++++++++++------- .../lib/node/ledger/shims/abcipp_shim_types.rs | 10 +++++----- shared/src/ledger/storage/merkle_tree.rs | 2 +- shared/src/ledger/storage/mod.rs | 2 +- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index e7f518cb9e..3dc0db702e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -530,7 +530,7 @@ mod test_vote_extensions { validator_address: address.try_to_vec().expect("Test failed"), height: 0, vote_extension: VoteExtension { - ethereum_events, + ethereum_events: ethereum_events.clone(), validator_set_update: None, } .try_to_vec() diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index be2d8163a8..057d27e5c3 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -252,12 +252,13 @@ mod test_vote_extensions { let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + #[allow(clippy::redundant_clone)] let validator_set_update = Some( validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), - validator_addr, + validator_addr: validator_addr.clone(), // invalid height block_height: shell.storage.get_current_decision_height() + 1, } @@ -268,7 +269,7 @@ mod test_vote_extensions { { let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), - validator_addr.clone(), + validator_addr, ) .sign(protocol_key); let req = request::VerifyVoteExtension { @@ -306,13 +307,14 @@ mod test_vote_extensions { (bertha_key, bertha_addr) }; + #[allow(clippy::redundant_clone)] let validator_set_update = Some( validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), block_height: shell.storage.get_current_decision_height(), - validator_addr, + validator_addr: validator_addr.clone(), } .sign(&protocol_key), ); @@ -320,7 +322,7 @@ mod test_vote_extensions { { let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), - validator_addr.clone(), + validator_addr, ) .sign(&protocol_key); let req = request::VerifyVoteExtension { @@ -424,13 +426,14 @@ mod test_vote_extensions { let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + #[allow(clippy::redundant_clone)] let validator_set_update = { let mut ext = validator_set_update::Vext { // TODO: get voting powers from storage, associated with eth // addrs voting_powers: std::collections::HashMap::new(), block_height: shell.storage.get_current_decision_height(), - validator_addr, + validator_addr: validator_addr.clone(), } // TODO: sign with secp key .sign(protocol_key); @@ -441,13 +444,13 @@ mod test_vote_extensions { { let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), - validator_addr.clone(), + validator_addr, ) .sign(protocol_key); let req = request::VerifyVoteExtension { vote_extension: VoteExtension { ethereum_events, - validator_set_update, + validator_set_update: validator_set_update.clone(), } .try_to_vec() .expect("Test failed"), 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 03adab2e3a..a8b7b5ed8e 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -257,19 +257,19 @@ pub mod shim { pub mod response { #[cfg(feature = "abcipp")] use super::*; + #[cfg(not(feature = "abcipp"))] + use crate::facade::tendermint_proto::abci::ConsensusParams; use crate::facade::tendermint_proto::abci::{ - Event as TmEvent, ExecTxResult, ResponseProcessProposal, - ValidatorUpdate, + Event as TmEvent, ResponseProcessProposal, ValidatorUpdate, }; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::{ - abci::ResponseFinalizeBlock, types::ConsensusParams, + abci::{ExecTxResult, ResponseFinalizeBlock}, + types::ConsensusParams, }; use crate::node::ledger::events::Event; #[cfg(feature = "abcipp")] use crate::node::ledger::events::EventLevel; - #[cfg(not(feature = "abcipp"))] - use crate::tendermint_proto::abci::ConsensusParams; #[derive(Debug, Default)] pub struct VerifyHeader; diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index fa0f4a011f..ea0710dd4f 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -16,10 +16,10 @@ 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::tendermint::merkle::proof::{Proof, ProofOp}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{DbKeySeg, Error as StorageError, Key}; diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index f3e561616c..2407ac5e9f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -8,7 +8,6 @@ pub mod write_log; use core::fmt::Debug; -use tendermint::merkle::proof::Proof; use thiserror::Error; use super::parameters; @@ -22,6 +21,7 @@ pub use crate::ledger::storage::merkle_tree::{ MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, Sha256Hasher, StorageHasher, StoreType, }; +use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; #[cfg(feature = "ferveo-tpke")] From 7f210665041e0018c1b9c87d8b41ded7a9ecd183 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 11:13:44 +0100 Subject: [PATCH 0519/1995] Fix IBC mock tests --- shared/src/ledger/ibc/handler.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index bf45759535..1d72b99b7b 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -66,7 +66,7 @@ use crate::ibc::core::ics24_host::identifier::{ }; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::events::IbcEvent; -#[cfg(any(feature = "ibc-mocks-abci", feature = "ibc-mocks"))] +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; use crate::ibc::timestamp::Timestamp; use crate::ledger::ibc::storage; @@ -997,12 +997,12 @@ pub fn update_client( let new_consensus_state = TmConsensusState::from(h).wrap_any(); Ok((new_client_state, new_consensus_state)) } - #[cfg(any(feature = "ibc-mocks-abci", feature = "ibc-mocks"))] + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] _ => Err(Error::ClientUpdate( "The header type is mismatched".to_owned(), )), }, - #[cfg(any(feature = "ibc-mocks-abci", feature = "ibc-mocks"))] + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] AnyClientState::Mock(_) => match header { AnyHeader::Mock(h) => Ok(( MockClientState::new(h).wrap_any(), From 51a466a78d29030d65bef846497caf316c951f57 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 12:45:21 +0100 Subject: [PATCH 0520/1995] Cont. fixing abcipp --- Makefile | 4 ---- vm_env/Cargo.toml | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fa378fcbf8..a53d1effa6 100644 --- a/Makefile +++ b/Makefile @@ -67,10 +67,6 @@ clippy-abcipp: --manifest-path ./shared/Cargo.toml \ --no-default-features \ --features "testing wasm-runtime abcipp ibc-mocks-abcipp" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./tests/Cargo.toml \ - --no-default-features \ - --features "wasm-runtime abcipp namada_apps/abcipp namada_apps/eth-fullnode" && \ $(cargo) +$(nightly) clippy \ --all-targets \ --manifest-path ./vm_env/Cargo.toml \ diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index cebcd4c0ca..672f013be6 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -7,10 +7,18 @@ resolver = "2" version = "0.7.1" [features] -default = [] +default = ["abciplus"] + +abciplus = [ + "namada/abciplus", +] + +abcipp = [ + "namada/abcipp", +] [dependencies] -namada = {path = "../shared"} +namada = {path = "../shared", default-features = false} namada_macros = {path = "../macros"} borsh = "0.9.0" hex = "0.4.3" From c4d2d93b3e94cf4724452463d72442ecee2ad856 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 1 Sep 2022 13:52:24 +0200 Subject: [PATCH 0521/1995] Update documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md Co-authored-by: James --- .../interoperability/ethereum-bridge/transfers_to_namada.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md index 4a05320aba..3f1972237f 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md @@ -14,8 +14,8 @@ In order to facilitate transferring assets from Ethereum to Namada, There If an ERC20 token is transferred to Namada, once the associated `TransferToNamada` Ethereum event is included into Namada, validators mint -the appropriate amount to the corresponding multitoken balance key for -the receiver. +the appropriate amount to the corresponding multitoken balance key for +the receiver, or release the escrowed native Namada token. ```rust pub struct EthAddress(pub [u8; 20]); From ed362d75c2a7ca03cad8106210c69b315987c8b6 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 1 Sep 2022 13:52:40 +0200 Subject: [PATCH 0522/1995] Update documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md Co-authored-by: James --- .../interoperability/ethereum-bridge/transfers_to_ethereum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md index 2177da9481..707ddc69af 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md @@ -47,7 +47,7 @@ the proof for a single transaction over the bridge. Instead, it is typically more economical to submit proofs of many transactions in bulk. This batching is described in this section. -A pool of transaction from Namada to Ethereum will be kept by Namada. Every +A pool of transfers from Namada to Ethereum will be kept by Namada. Every transaction to Ethereum that Namada validators approve will be added to this pool. We call this the _Bridge Pool_. From f521368ead95f974ef33b6833057aaf3c3ba04a5 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 1 Sep 2022 13:54:09 +0200 Subject: [PATCH 0523/1995] Update documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md Co-authored-by: James --- .../interoperability/ethereum-bridge/transfers_to_namada.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md index 3f1972237f..b3c67c87f9 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md @@ -42,9 +42,9 @@ pub struct TransferToNamada { For 10 DAI i.e. ERC20([0x6b175474e89094c44da98b954eedeac495271d0f](https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f)) to `atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt` ``` #EthBridge - /erc20 + /ERC20 /0x6b175474e89094c44da98b954eedeac495271d0f - /balances + /balance /atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt += 10 ``` From 514d0bfc829059c8f407a4f495d42d0160b67b95 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 1 Sep 2022 13:57:37 +0200 Subject: [PATCH 0524/1995] Update documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md Co-authored-by: James --- .../ethereum-bridge/transfers_to_ethereum.md | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md index 707ddc69af..404fc1652e 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md @@ -19,26 +19,9 @@ will also aid in batching transactions. To redeem wrapped Ethereum assets, a user should make a transaction to burn their wrapped tokens, which the `#EthBridge` validity predicate will accept. - -Mints of a wrapped Namada token on Ethereum (including NAM, Namada's native token) -will be represented by a data type like: -```rust -struct MintWrappedNam { - /// The Namada address owning the token - owner: NamadaAddress, - /// The address on Ethereum receiving the wrapped tokens - receiver: EthereumAddress, - /// The address of the token to be wrapped - token: NamadaAddress, - /// The number of wrapped Namada tokens to mint on Ethereum - amount: Amount, -} -``` - -If a user wishes to mint a wrapped Namada token on Ethereum, they must -submit a transaction on Namada that: -- stores `MintWrappedNam` on chain somewhere - TBD -- sends the correct amount of Namada token to `#EthBridgeEscrow` +For sending NAM over the bridge, a user should send their NAM to +`#EthBridgeEscrow`. In both cases, it's important that the user also adds a +`PendingTransfer` to the [Bridge Pool](#bridge-pool-validity-predicate). ## Batching From 6a8b1b36c8b5a4ad33228ebfb973d0c878f9895a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 13:01:01 +0100 Subject: [PATCH 0525/1995] Removed remaining warnings from the code --- apps/src/lib/node/ledger/shell/mod.rs | 2 ++ apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 ++++-- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 ------ apps/src/lib/node/ledger/shell/vote_extensions.rs | 5 ++++- .../lib/node/ledger/shell/vote_extensions/eth_events.rs | 3 +-- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 1 + apps/src/lib/node/ledger/shims/abcipp_shim_types.rs | 8 ++++---- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 77805aa6c6..f41e611180 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -756,6 +756,7 @@ mod test_utils { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; + #[cfg(not(feature = "abcipp"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; @@ -952,6 +953,7 @@ mod test_utils { /// Get the only validator's voting power. #[inline] + #[cfg(not(feature = "abcipp"))] pub fn get_validator_voting_power() -> VotingPower { 200.into() } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 2d099870a1..c0445d31c1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -61,10 +61,11 @@ where txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block - let mut decrypted_txs = self.build_decrypted_txs(); + let decrypted_txs = self.build_decrypted_txs(); #[cfg(feature = "abcipp")] - let mut decrypted_txs: Vec = + let decrypted_txs: Vec = decrypted_txs.into_iter().map(record::add).collect(); + let mut decrypted_txs = decrypted_txs; txs.append(&mut decrypted_txs); txs @@ -812,6 +813,7 @@ mod test_prepare_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; + #[allow(clippy::redundant_clone)] let vote_extension = VoteExtension { ethereum_events: signed_eth_ev_vote_extension.clone(), validator_set_update: None, diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c52fd577ad..ecaa5c9bb7 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -8,8 +8,6 @@ use namada::types::voting_power::FractionalVotingPower; use super::queries::{QueriesExt, SendValsetUpd}; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; -#[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto::abci::ExecTxResult; use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; @@ -411,10 +409,6 @@ mod test_process_proposal { }; use super::*; - #[cfg(feature = "abcipp")] - use crate::facade::tendermint_proto::abci::RequestInitChain; - #[cfg(feature = "abcipp")] - use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ed449e08ad..b070cf255a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -6,7 +6,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; use namada::proto::Signed; -use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; +use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, }; @@ -15,6 +15,7 @@ use super::*; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedVoteInfo; use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; +#[cfg(not(feature = "abcipp"))] use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; /// Message to be passed to `.expect()` calls in this module. @@ -272,6 +273,8 @@ pub fn deserialize_vote_extensions( pub fn deserialize_vote_extensions( txs: &[TxBytes], ) -> impl Iterator + '_ { + use namada::types::transaction::protocol::ProtocolTx; + txs.iter().filter_map(|tx| { if let Ok(tx) = Tx::try_from(tx.as_slice()) { match process_tx(tx).ok()? { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 3dc0db702e..240ce0a97b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -186,6 +186,7 @@ where self.filter_invalid_eth_events_vexts(vote_extensions) { let validator_addr = vote_extension.data.validator_addr; + #[cfg(not(feature = "abcipp"))] let block_height = vote_extension.data.block_height; // update voting power @@ -275,8 +276,6 @@ mod test_vote_extensions { use crate::facade::tower_abci::request; use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; - #[cfg(feature = "abcipp")] - use crate::node::ledger::shell::vote_extensions::VoteExtensionError; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; /// Test that we successfully receive ethereum events diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 6867e6ed6a..b03caaf314 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -6,6 +6,7 @@ use std::task::{Context, Poll}; use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; +#[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tower::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 a8b7b5ed8e..89bc78fd01 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -6,13 +6,15 @@ pub mod shim { use thiserror::Error; use super::{Request as Req, Response as Resp}; + #[cfg(not(feature = "abcipp"))] + use crate::facade::tendermint_proto::abci::ResponseEndBlock; use crate::facade::tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestPrepareProposal, RequestProcessProposal, RequestQuery, ResponseApplySnapshotChunk, - ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseEndBlock, - ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, + ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseFlush, + ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, ResponsePrepareProposal, ResponseQuery, }; @@ -255,8 +257,6 @@ pub mod shim { /// Custom types for response payloads pub mod response { - #[cfg(feature = "abcipp")] - use super::*; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::ConsensusParams; use crate::facade::tendermint_proto::abci::{ From e34c9aaf11b7f4816e50ac47db644bffab72c4ec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 13:12:25 +0100 Subject: [PATCH 0526/1995] Add test-unit-abcipp to Makefile --- Makefile | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a53d1effa6..90f27f5339 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,32 @@ test-e2e: --test-threads=1 \ -Z unstable-options --report-time +test-unit-abcipp: + $(cargo) test \ + --manifest-path ./apps/Cargo.toml \ + --no-default-features \ + --features "testing std abcipp" \ + -- \ + -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 abcipp ibc-mocks-abcipp" \ + -- \ + -Z unstable-options --report-time && \ + $(cargo) test \ + --manifest-path ./vm_env/Cargo.toml \ + --no-default-features \ + --features "abcipp" \ + -- \ + -Z unstable-options --report-time + test-unit: $(cargo) test \ -- \ @@ -191,4 +217,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 build-wasm-scripts clean-wasm-scripts dev-deps test-miri test-unit test-unit-abcipp clippy-abcipp From 058f64274ee2463bb1db96be24eb6118631817f2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 13:24:33 +0100 Subject: [PATCH 0527/1995] Small merge fix --- .../lib/node/ledger/shell/vote_extensions/eth_events.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 2e770bfc79..fe1bcd20f9 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -166,15 +166,16 @@ where &self, vote_extensions: Vec>, ) -> Option { - let vexts_epoch = - self.storage.get_epoch(self.storage.last_height).expect( - "The epoch of the last block height should always be known", - ); #[cfg(not(feature = "abcipp"))] if self.storage.last_height == BlockHeight(0) { return None; } + let vexts_epoch = + self.storage.get_epoch(self.storage.last_height).expect( + "The epoch of the last block height should always be known", + ); + let total_voting_power = u64::from(self.storage.get_total_voting_power(Some(vexts_epoch))); let mut voting_power = FractionalVotingPower::default(); From ed10d96e8e2c88877d04089b8ef5149e2064b956 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 13:44:39 +0100 Subject: [PATCH 0528/1995] Fix abcipp test_error_in_processing_tx() test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c0445d31c1..704e23779b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -876,7 +876,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 (mut shell, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -904,14 +904,17 @@ mod test_prepare_proposal { .to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { + #[cfg(feature = "abcipp")] + local_last_commit: get_local_last_commit(&shell), txs: vec![wrapper.clone()], max_tx_bytes: 0, ..Default::default() }; #[cfg(feature = "abcipp")] assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper)] + // NOTE: we process mempool txs after protocol txs + shell.prepare_proposal(req).tx_records.remove(1), + record::remove(wrapper) ); #[cfg(not(feature = "abcipp"))] assert!(shell.prepare_proposal(req).txs.is_empty()); From 8852009a9969aaf2f2c2fa68238907936e9208ed Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 13:50:06 +0100 Subject: [PATCH 0529/1995] Fix abcipp unit tests --- .../src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 79a8a842fb..2b29a44071 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -459,7 +459,7 @@ mod test_vote_extensions { i32::from(VerifyStatus::Reject) ); } - assert!(shell.validate_valset_upd_vext( + assert!(!shell.validate_valset_upd_vext( validator_set_update.unwrap(), shell.storage.get_current_decision_height() )); From e9d9d81422b3b6ca3d9d3cfdc3b74cee9235c128 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 14:06:56 +0100 Subject: [PATCH 0530/1995] Make decompress() calls identical between abcipp and abciplus --- .../lib/node/ledger/shell/prepare_proposal.rs | 3 --- .../lib/node/ledger/shell/process_proposal.rs | 3 --- .../types/vote_extensions/ethereum_events.rs | 17 +++++++---------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 704e23779b..fca59eeb18 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -570,13 +570,10 @@ mod test_prepare_proposal { let vote_extension_digest = ethereum_events::VextDigest { events, signatures }; - #[cfg(feature = "abcipp")] assert_eq!( vec![ext], vote_extension_digest.clone().decompress(last_height) ); - #[cfg(not(feature = "abcipp"))] - assert_eq!(vec![ext], vote_extension_digest.clone().decompress()); vote_extension_digest diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ecaa5c9bb7..00e299969a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -249,11 +249,8 @@ where TxType::Protocol(protocol_tx) => match protocol_tx.tx { ProtocolTxType::EthereumEvents(digest) => { counters.eth_ev_digest_num += 1; - #[cfg(feature = "abcipp")] let extensions = digest.decompress(self.storage.last_height); - #[cfg(not(feature = "abcipp"))] - let extensions = digest.decompress(); let valid_extensions = self.validate_eth_events_vext_list(extensions).map( |maybe_ext| maybe_ext.ok().map(|(power, _)| power), diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index bfa1babf8d..87674ee456 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -85,10 +85,13 @@ pub struct VextDigest { impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. - pub fn decompress( - self, - #[cfg(feature = "abcipp")] last_height: BlockHeight, - ) -> Vec> { + pub fn decompress(self, last_height: BlockHeight) -> Vec> { + #[cfg(not(feature = "abcipp"))] + { + #[allow(clippy::drop_copy)] + drop(last_height); + } + let VextDigest { signatures, events } = self; let mut extensions = vec![]; @@ -258,16 +261,10 @@ mod tests { // finally, decompress the `VextDigest` back into a // `Vec>` - #[cfg(feature = "abcipp")] let decompressed = digest .decompress(last_block_height) .into_iter() .collect::>>(); - #[cfg(not(feature = "abcipp"))] - let decompressed = digest - .decompress() - .into_iter() - .collect::>>(); assert_eq!(decompressed.len(), ext.len()); for vext in decompressed.into_iter() { From e2eaca5708639f396ca95c198f933957ecb3b2c3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 1 Sep 2022 14:23:32 +0100 Subject: [PATCH 0531/1995] Shim out validator set update vexts --- .../shell/vote_extensions/val_set_update.rs | 14 ++++++++++++++ .../types/vote_extensions/validator_set_update.rs | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 2b29a44071..487b861f98 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -165,6 +165,8 @@ where } let validator_addr = vote_extension.data.validator_addr; + #[cfg(not(feature = "abcipp"))] + let block_height = vote_extension.data.block_height; // update voting power let validator_voting_power = u64::from(validator_voting_power); @@ -181,6 +183,7 @@ where let addr = validator_addr.clone(); let sig = vote_extension.sig; + #[cfg(feature = "abcipp")] if let Some(sig) = signatures.insert(addr, sig) { tracing::warn!( ?sig, @@ -189,8 +192,19 @@ where constructing validator_set_update::VextDigest" ); } + + #[cfg(not(feature = "abcipp"))] + if let Some(sig) = signatures.insert((addr, block_height), sig) { + tracing::warn!( + ?sig, + ?validator_addr, + "Overwrote old signature from validator while \ + constructing validator_set_update::VextDigest" + ); + } } + #[cfg(feature = "abcipp")] if voting_power <= FractionalVotingPower::TWO_THIRDS { tracing::error!( "Tendermint has decided on a block including validator set \ diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b51fc83df1..678a7714eb 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -29,8 +29,15 @@ const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] pub struct VextDigest { + #[cfg(feature = "abcipp")] /// A mapping from a validator address to a [`Signature`]. pub signatures: HashMap, + #[cfg(not(feature = "abcipp"))] + /// A mapping from a validator address to a [`Signature`]. + /// + /// The key includes the block height at which a validator + /// set was signed by a given validator. + pub signatures: HashMap<(Address, BlockHeight), Signature>, /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. pub voting_powers: VotingPowersMap, @@ -39,6 +46,12 @@ pub struct VextDigest { impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, block_height: BlockHeight) -> Vec { + #[cfg(not(feature = "abcipp"))] + { + #[allow(clippy::drop_copy)] + drop(block_height); + } + let VextDigest { signatures, voting_powers, @@ -47,6 +60,8 @@ impl VextDigest { let mut extensions = vec![]; for (validator_addr, signature) in signatures.into_iter() { + #[cfg(not(feature = "abcipp"))] + let (validator_addr, block_height) = validator_addr; let voting_powers = voting_powers.clone(); let data = Vext { validator_addr, From 72878ad964835a3723ec456bd3aa25c77fb40157 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 2 Sep 2022 10:23:57 +0200 Subject: [PATCH 0532/1995] [feat]: Added a vp for the bridge pool and much of the logic. Still to fix paying gas fees and verifying the nonce --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 132 ++++++++++++++++++ shared/src/ledger/eth_bridge/mod.rs | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 28 ++++ shared/src/ledger/eth_bridge/storage/mod.rs | 6 +- shared/src/types/address.rs | 17 ++- shared/src/types/eth_bridge_pool.rs | 41 ++++++ shared/src/types/mod.rs | 1 + 7 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 shared/src/ledger/eth_bridge/bridge_pool_vp.rs create mode 100644 shared/src/ledger/eth_bridge/storage/bridge_pool.rs create mode 100644 shared/src/types/eth_bridge_pool.rs diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs new file mode 100644 index 0000000000..b855a83f6d --- /dev/null +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -0,0 +1,132 @@ +//! Validity predicate for the Ethereum bridge +use std::collections::{BTreeSet, HashSet}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use eyre::{Report, Result}; + +use crate::ledger::eth_bridge::storage::bridge_pool::{ + BRIDGE_POOL_ADDRESS, get_pending_key, get_signed_root_key, +} +use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::ledger::storage::{DB, DBIter, StorageHasher}; +use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_bridge_pool::{GasFee, PendingTransfer, TransferToEthereum}; +use crate::types::storage::{Key, KeySeg}; +use crate::types::token::Amount; +use crate::vm::WasmCacheAccess; + +/// A positive or negative amount +enum SignedAmount { + Positive(Amount), + Negative(Amount), +} + +/// Validity predicate for the Ethereum bridge +pub struct BridgePoolVp<'ctx, DB, H, CA> +where + DB: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'ctx, DB, H, CA>, +} + +impl<'a, DB, H, CA> BridgePoolVp<'a, DB, H, CA> +where + DB: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Helper function for reading values from storage + fn read_post_value(&self, key: &Key) -> Option + where + T: BorshDeserialize, + { + if let Ok(Some(bytes)) = self.ctx.read_post(key) { + ::try_from_slice(bytes.as_slice()).ok() + } else { + None + } + } + + /// Helper function for reading values from storage + fn read_pre_value(&self, key: &Key) -> Option + where + T: BorshDeserialize, + { + if let Ok(Some(bytes)) = self.ctx.read_pre(key) { + ::try_from_slice(bytes.as_slice()).ok() + } else { + None + } + } + + /// Get the change in the balance of an account + /// associated with an address + fn account_balance_delta(&self, address: &Address) -> Option { + let account_key = Key::from(address.to_db_key()); + let before: Amount = self.read_pre_value(&account_key)?; + let after: Amount = self.read_post_value(&account_key)? + if before > after { + Some(SignedAmount::Negative(before - after) + } else { + Some(SignedAmount::Positive(after - before) + } + } +} + +impl<'a, DB, H, CA> NativeVp for BridgePoolVp<'a, DB, H, CA> +where + DB: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + const ADDR: InternalAddress = InternalAddress::EthBridgePool; + + type Error = Report; + + fn validate_tx( + &self, + tx_data: &[u8], + keys_changed: &BTreeSet, + _verifiers: &BTreeSet
, + ) -> Result { + tracing::debug!( + tx_data_len = _tx_data.len(), + keys_changed_len = _keys_changed.len(), + verifiers_len = _verifiers.len(), + "Validity predicate triggered", + ); + let transfer: PendingTransfer = BorshDeserialize::try_from_slice(tx_data)?; + // check that the signed root is not modified + let signed_root_key = get_signed_root_key(); + if keys_changed.contains(&signed_root_key) { + return Ok(false) + } + // check that the pending transfer (and only that) was added to the pool + let pending_key = get_pending_key(); + let pending_pre: HashSet = self.read_pre_value(&get_pending_key()) + .ok_or(eyre!("The bridge pool transfers are missing from storage"))?; + let pending_post: HashSet = self.read_post_value(&get_pending_key()) + .ok_or(eyre!("The bridge pool transfers are missing from storage"))?; + for item in pending_pre.symmetric_difference(&pending_post){ + if item != &transfer { + return Ok(false); + } + } + + // check that gas fees were put into escrow + if let Some(SignedAmount::Negative(amount)) = self.account_balance_delta(&transfer.gas_fee.payer) { + if amount != transfer.gas_fee.amount { + return Ok(false); + } + } else { + return Ok(false); + } + // TODO: Check that correct amount was received in escrow + // TODO: Verify nonce + + Ok(true) + } +} diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index 3d9487276d..047bcda91e 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,4 +1,5 @@ //! Validity predicate and storage keys for the Ethereum bridge account +pub mod bridge_pool_vp; pub mod storage; pub mod vp; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs new file mode 100644 index 0000000000..7f9bb61deb --- /dev/null +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -0,0 +1,28 @@ +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::{DbKeySeg, Key}; + +pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); +const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; +const SIGNED_ROOT_SEG: &str = "signed_root"; + +/// Get the storage key for the transfers in the pool +pub fn get_pending_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), + DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + ], + } +} + + +/// Get the storage key for the root of the Merkle tree +/// containing the transfers in the pool +pub fn get_signed_root_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), + DbKeySeg::StringSeg(SIGNED_ROOT_SEG.into()), + ], + } +} \ No newline at end of file diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 55cbfc01f2..cb0b1f6eb3 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -1,9 +1,11 @@ //! Functionality for accessing the storage subspace +pub mod bridge_pool; +pub mod eth_msgs; +pub mod wrapped_erc20s; + use super::ADDRESS; use crate::types::storage::{Key, KeySeg}; -pub mod eth_msgs; -pub mod wrapped_erc20s; /// Key prefix for the storage subspace pub fn prefix() -> Key { diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 704dcdbdb2..9954b4e568 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -67,6 +67,8 @@ mod internal { "ano::IBC Mint Address "; pub const ETH_BRIDGE: &str = "ano::ETH Bridge Address "; + pub const ETH_BRIDGE_POOL: &str = + "ano::ETH Bridge Pool Address "; } /// Fixed-length address strings prefix for established addresses. @@ -194,6 +196,9 @@ impl Address { InternalAddress::EthBridge => { internal::ETH_BRIDGE.to_string() } + InternalAddress::EthBridgePool => { + internal::ETH_BRIDGE_POOL.to_string() + } }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -254,6 +259,9 @@ impl Address { internal::ETH_BRIDGE => { Ok(Address::Internal(InternalAddress::EthBridge)) } + internal::ETH_BRIDGE_POOL => { + Ok(Address::Internal(InternalAddress::EthBridgePool)) + } _ if raw.len() == HASH_LEN => Ok(Address::Internal( InternalAddress::IbcEscrow(raw.to_string()), )), @@ -451,6 +459,8 @@ pub enum InternalAddress { Treasury, /// Bridge to Ethereum EthBridge, + /// The pool of transactions to be relayed to Ethereum + EthBridgePool, } impl InternalAddress { @@ -480,6 +490,7 @@ impl Display for InternalAddress { Self::IbcBurn => "IbcBurn".to_string(), Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), + Self::EthBridgePool => "EthBridgePool".to_string(), } ) } @@ -717,8 +728,9 @@ pub mod testing { InternalAddress::IbcEscrow(_) => {} InternalAddress::IbcBurn => {} InternalAddress::IbcMint => {} - InternalAddress::EthBridge => {} /* Add new addresses in the - * `prop_oneof` below. */ + InternalAddress::EthBridge => {} + InternalAddress::EthBridgePool => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), @@ -732,6 +744,7 @@ pub mod testing { Just(InternalAddress::Governance), Just(InternalAddress::Treasury), Just(InternalAddress::EthBridge), + Just(InternalAddress::EthBridgePool), ] } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs new file mode 100644 index 0000000000..62eda4993d --- /dev/null +++ b/shared/src/types/eth_bridge_pool.rs @@ -0,0 +1,41 @@ +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::types::address::Address; +use crate::types::ethereum_events::{EthAddress, Uint}; +use crate::types::token::Amount; + +/// A transfer message to be submitted to Ethereum +/// to move assets from Namada across the bridge. +#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +pub struct TransferToEthereum { + /// The type of token + pub asset: EthAddress, + /// The recipient address + pub recipient: EthAddress, + /// The amount to be transferred + pub amount: Amount, + /// a nonce for replay protection + pub nonce: Uint, +} + +/// A transfer message to Ethereum sitting in the +/// bridge pool, waiting to be relayed +#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +pub struct PendingTransfer { + /// The message to send to Ethereum to + pub transfer: TransferToEthereum, + /// The amount of gas fees (in NAM) + /// paid by the user sending this transfer + pub gas_fee: GasFee +} + +/// The amount of NAM to be payed to the relayer of +/// a transfer across the Ethereum Bridge to compensate +/// for Ethereum gas fees. +#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +pub struct GasFee { + /// The amount of fess (in NAM) + pub amount: Amount, + /// The account of fee payer. + pub payer: Address, +} \ No newline at end of file diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index ddef48f8eb..fabae14116 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -4,6 +4,7 @@ pub mod address; pub mod chain; pub mod dylib; pub mod ethereum_events; +pub mod eth_bridge_pool; pub mod governance; pub mod hash; pub mod ibc; From 97baa094be2430980aa048a777d8756adc3faabd Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 2 Sep 2022 11:21:31 +0200 Subject: [PATCH 0533/1995] [fix]: Clarification on bridge pool merkle roots and crafting transfer of power messages --- .../ethereum-bridge/proofs.md | 7 +++- .../ethereum-bridge/transfers_to_ethereum.md | 39 +++++++++++++------ .../ethereum-bridge/transfers_to_namada.md | 6 --- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index 8b105102a4..5d000d2cfc 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -22,7 +22,12 @@ knowledge of: This means that by the end of every Namada epoch, a special transaction must be sent to the Ethereum smart contracts detailing the new public keys and stake of the new validator set. This message must also be signed by at least 2 / 3 -of the current validators as a "transfer of power". +of the current validators as a "transfer of power". + +If vote extensions are available, a fully crafted transfer of power message +will be made available on-chain. Otherwise, this message must be crafted +offline by aggregating the protocol txs from validators in which the sign +over the new validator set. If vote extensions are available, this signed data can be constructed using them. Otherwise, validators must send protocol txs to be included on diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md index 2177da9481..6c4cc81f47 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_ethereum.md @@ -3,10 +3,10 @@ Moving assets from Namada to Ethereum will not be automatic, as opposed the movement of value in the opposite direction. Instead, users must send an appropriate transaction to Namada to initiate a transfer across the bridge -to Ethereum. Once this transaction is approved, a "proof" will be created -and posted on Namada. +to Ethereum. Once this transaction is approved, a ["proof"](proofs.md), or +the parts necessary to create a proof, will be created and posted on Namada. -It is incumbent on the end user to request an appropriate ["proof"](proofs.md) +It is incumbent on the end user to request an appropriate proof of the transaction. This proof must be submitted to the appropriate Ethereum smart contract by the user to redeem Ethereum assets / mint wrapped assets. This also means all Ethereum gas costs are the responsibility of the end user. @@ -42,7 +42,7 @@ submit a transaction on Namada that: ## Batching -Ethereum gas fees make it prohibitively expensive in many cases to submit +Ethereum gas fees make it prohibitively expensive to submit the proof for a single transaction over the bridge. Instead, it is typically more economical to submit proofs of many transactions in bulk. This batching is described in this section. @@ -80,6 +80,12 @@ transactions, introducing latency to the pool. A user wishing to relay will need to wait until a Merkle tree root is signed for a tree that includes all the transactions they wish to relay. +The Ethereum smart contracts won't keep track of this signed Merkle root. +Instead, part of the proof of correct batching is submitting a root to the +contracts that is signed by quorum of validators. Since the smart contracts +can trust such a signed root, it can then use the root to verify inclusion +proofs. + ### Bridge Pool validity predicate The Bridge Pool will have associated storage under the control of a native @@ -95,27 +101,36 @@ The pending transfers are instances of the following type: ```rust pub struct TransferToEthereum { /// The type of token - asset: EthereumAddress, + pub asset: EthAddress, /// The recipient address - recipient: EthereumAddress, + pub recipient: EthAddress, /// The amount to be transferred - amount: Amount, + pub amount: Amount, /// a nonce for replay protection - nonce: Nonce, + pub nonce: u64, } pub struct PendingTransfer { /// The message to send to Ethereum to /// complete the transfer - transfer: TransferToEthereum, + pub transfer: TransferToEthereum, + /// The gas fees paid by the user sending + /// this transfer + pub gas_fee: GasFee, +} + +pub struct GasFee { /// The amount of gas fees (in NAM) /// paid by the user sending this transfer - gas_fee: Amount + pub amount: Amount, + /// The address of the account paying the fees + pub payer: Address, } ``` When a user submits initiates a transfer, their transaction should include wasm -to append craft a `PendingTransfer` and append it to the pool in storage. -This will be validated by the Bridge Pool vp. +to craft a `PendingTransfer` and append it to the pool in storage as well as +send the relevant gas fees into the Bridge Pool's escrow. This will be +validated by the Bridge Pool vp. The signed Merkle root is only modifiable by validators. The Merkle tree only consists of the `TransferToEthereum` messages as Ethereum does not need diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md index 4a05320aba..46e2f966be 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md @@ -20,12 +20,6 @@ the receiver. ```rust pub struct EthAddress(pub [u8; 20]); -/// Represents Ethereum assets on the Ethereum blockchain -pub enum EthereumAsset { - /// An ERC20 token and the address of its contract - ERC20(EthAddress), -} - /// An event transferring some kind of value from Ethereum to Anoma pub struct TransferToNamada { /// Quantity of ether in the transfer From de796d30ec9ada16e62c788af506f01e7576f19b Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 17:33:47 +0200 Subject: [PATCH 0534/1995] multitoken transfer and query --- apps/src/lib/cli.rs | 15 +++ apps/src/lib/client/rpc.rs | 128 ++++++++++++++++++---- apps/src/lib/client/tx.rs | 15 ++- shared/src/types/intent.rs | 4 + shared/src/types/token.rs | 68 ++++++++++++ tests/src/e2e/ledger_tests.rs | 1 + vm_env/src/governance.rs | 1 + vm_env/src/ibc.rs | 2 +- vm_env/src/proof_of_stake.rs | 2 +- vm_env/src/token.rs | 108 ++++++++++++++---- wasm/wasm_source/src/tx_from_intent.rs | 3 +- wasm/wasm_source/src/tx_transfer.rs | 3 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 8 +- wasm/wasm_source/src/vp_user.rs | 20 +++- wasm_for_tests/wasm_source/src/lib.rs | 1 + 15 files changed, 323 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..6eb6454bc4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1465,6 +1465,7 @@ pub mod args { const SOURCE: Arg = arg("source"); const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); + const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); const TARGET: Arg = arg("target"); const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -1610,6 +1611,8 @@ pub mod args { pub target: WalletAddress, /// Transferred token address pub token: WalletAddress, + /// Transferred token address + pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, } @@ -1620,12 +1623,14 @@ pub mod args { let source = SOURCE.parse(matches); let target = TARGET.parse(matches); let token = TOKEN.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); Self { tx, source, target, token, + sub_prefix, amount, } } @@ -1638,6 +1643,7 @@ pub mod args { )) .arg(TARGET.def().about("The target account address.")) .arg(TOKEN.def().about("The transfer token.")) + .arg(SUB_PREFIX.def().about("The token's sub prefix.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) } } @@ -2185,6 +2191,8 @@ pub mod args { pub owner: Option, /// Address of a token pub token: Option, + /// Sub prefix of an account + pub sub_prefix: Option, } impl Args for QueryBalance { @@ -2192,10 +2200,12 @@ pub mod args { let query = Query::parse(matches); let owner = OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, + sub_prefix, } } @@ -2211,6 +2221,11 @@ pub mod args { .def() .about("The token's address whose balance to query."), ) + .arg( + SUB_PREFIX.def().about( + "The token's sub prefix whose balance to query.", + ), + ) } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4209772be2..756f704344 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -27,7 +27,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, }; use namada::types::key::*; -use namada::types::storage::{Epoch, PrefixValue}; +use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; use tendermint::abci::Code; @@ -101,15 +101,29 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { (Some(token), Some(owner)) => { let token = ctx.get(&token); let owner = ctx.get(&owner); - let key = token::balance_key(&token, &owner); + let key = match &args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = + token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, &owner) + } + None => token::balance_key(&token, &owner), + }; let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); match query_storage_value::(&client, &key).await { - Some(balance) => { - println!("{}: {}", currency_code, balance); - } + Some(balance) => match &args.sub_prefix { + Some(sub_prefix) => { + println!( + "{} with {}: {}", + currency_code, sub_prefix, balance + ); + } + None => println!("{}: {}", currency_code, balance), + }, None => { println!("No {} balance found for {}", currency_code, owner) } @@ -119,12 +133,44 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { let owner = ctx.get(&owner); let mut found_any = false; for (token, currency_code) in tokens { - let key = token::balance_key(&token, &owner); - if let Some(balance) = - query_storage_value::(&client, &key).await - { - println!("{}: {}", currency_code, balance); - found_any = true; + let prefix = token.to_db_key().into(); + let balances = query_storage_prefix::( + client.clone(), + prefix, + ) + .await; + if let Some(balances) = balances { + let stdout = io::stdout(); + let mut w = stdout.lock(); + for (key, balance) in balances { + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, o)) if *o == owner => { + writeln!( + w, + "{} with {}: {}", + currency_code, sub_prefix, balance + ) + .unwrap(); + found_any = true; + } + Some(_) => {} + None => { + if let Some(o) = + token::is_any_token_balance_key(&key) + { + if *o == owner { + writeln!( + w, + "{}: {}", + currency_code, balance + ) + .unwrap(); + found_any = true; + } + } + } + } + } } } if !found_any { @@ -133,9 +179,9 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (Some(token), None) => { let token = ctx.get(&token); - let key = token::balance_prefix(&token); + let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(client, key).await; + query_storage_prefix::(client, prefix).await; match balances { Some(balances) => { let currency_code = tokens @@ -144,12 +190,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .unwrap_or_else(|| Cow::Owned(token.to_string())); let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap(); + } + } + } } } None => { @@ -167,12 +231,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .await; match balances { Some(balances) => { - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap() + } + } + } } } None => { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8daf318435..43eeb49c68 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -16,7 +16,7 @@ use namada::types::governance::{ }; use namada::types::key::*; use namada::types::nft::{self, Nft, NftToken}; -use namada::types::storage::Epoch; +use namada::types::storage::{Epoch, Key}; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, @@ -415,7 +415,17 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { } } // Check source balance - let balance_key = token::balance_key(&token, &source); + let (sub_prefix, balance_key) = match args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + ( + Some(sub_prefix), + token::multitoken_balance_key(&prefix, &source), + ) + } + None => (None, token::balance_key(&token, &source)), + }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { @@ -447,6 +457,7 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { source, target, token, + sub_prefix, amount: args.amount, }; tracing::debug!("Transfer data {:?}", transfer); diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs index c3effbb4fc..3acb43f0c0 100644 --- a/shared/src/types/intent.rs +++ b/shared/src/types/intent.rs @@ -316,12 +316,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] @@ -424,12 +426,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4d03138e60 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -250,6 +250,23 @@ pub fn balance_prefix(token_addr: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Obtain a storage key prefix for multitoken balances. +pub fn multitoken_balance_prefix( + token_addr: &Address, + sub_prefix: &Key, +) -> Key { + Key::from(token_addr.to_db_key()).join(sub_prefix) +} + +/// Obtain a storage key for user's multitoken balance. +pub fn multitoken_balance_key(prefix: &Key, owner: &Address) -> Key { + prefix + .push(&BALANCE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&owner.to_db_key()) + .expect("Cannot obtain a storage key") +} + /// Check if the given storage key is balance key for the given token. If it is, /// returns the owner. pub fn is_balance_key<'a>( @@ -297,6 +314,54 @@ pub fn is_non_owner_balance_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is multitoken balance key for the given +/// token. If it is, returns the sub prefix and the owner. +pub fn is_multitoken_balance_key<'a>( + token_addr: &Address, + key: &'a Key, +) -> Option<(Key, &'a Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(addr)) if addr == token_addr => { + multitoken_balance_owner(key) + } + _ => None, + } +} + +/// Check if the given storage key is multitoken balance key for unspecified +/// token. If it is, returns the sub prefix and the owner. +pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), + _ => None, + } +} + +fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { + let len = key.segments.len(); + if len < 4 { + // the key of a multitoken should have 1 or more segments other than + // token, balance, owner + return None; + } + match key.get_at(len - 2) { + Some(DbKeySeg::StringSeg(balance)) + if balance == BALANCE_STORAGE_KEY => + { + match key.segments.last() { + Some(DbKeySeg::AddressSeg(owner)) => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) + } + _ => None, + } + } + _ => None, + } +} + /// A simple bilateral token transfer #[derive( Debug, @@ -318,6 +383,8 @@ pub struct Transfer { pub target: Address, /// Token's address pub token: Address, + /// Source token's sub prefix + pub sub_prefix: Option, /// The amount of tokens pub amount: Amount, } @@ -354,6 +421,7 @@ impl TryFrom for Transfer { source, target, token, + sub_prefix: None, amount, }) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 22eb4f4ac5..1f19433ca1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -425,6 +425,7 @@ fn invalid_transactions() -> Result<()> { source: find_address(&test, DAEWON)?, target: find_address(&test, ALBERT)?, token: find_address(&test, XAN)?, + sub_prefix: None, amount: token::Amount::whole(1), }; let data = transfer diff --git a/vm_env/src/governance.rs b/vm_env/src/governance.rs index db4ea7916f..cfe16e2acb 100644 --- a/vm_env/src/governance.rs +++ b/vm_env/src/governance.rs @@ -63,6 +63,7 @@ pub mod tx { &data.author, &governance_address, &m1t(), + None, min_proposal_funds, ); } diff --git a/vm_env/src/ibc.rs b/vm_env/src/ibc.rs index febaa78560..c7abb82ec5 100644 --- a/vm_env/src/ibc.rs +++ b/vm_env/src/ibc.rs @@ -37,7 +37,7 @@ impl IbcActions for Ibc { token: &Address, amount: Amount, ) { - transfer(src, dest, token, amount) + transfer(src, dest, token, None, amount) } fn get_height(&self) -> BlockHeight { diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 8e4bba4223..afabd85ed7 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -256,6 +256,6 @@ impl namada_proof_of_stake::PosActions for PoS { src: &Self::Address, dest: &Self::Address, ) { - crate::token::tx::transfer(src, dest, token, amount) + crate::token::tx::transfer(src, dest, token, None, amount) } } diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs index 8a7367afb9..784b838707 100644 --- a/vm_env/src/token.rs +++ b/vm_env/src/token.rs @@ -20,7 +20,12 @@ pub mod vp { ) -> bool { let mut change: Change = 0; let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { + let owner: Option<&Address> = + match token::is_multitoken_balance_key(token, key) { + Some((_, o)) => Some(o), + None => token::is_balance_key(token, key), + }; + match owner { None => { // Unknown changes to this address space are disallowed, but // unknown changes anywhere else are permitted @@ -73,38 +78,101 @@ pub mod tx { src: &Address, dest: &Address, token: &Address, + sub_prefix: Option, amount: Amount, ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); + let src_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, src) + } + None => token::balance_key(token, src), + }; + let dest_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, dest) + } + None => token::balance_key(token, dest), + }; let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { + match src_bal { + None => { tx::log_string(format!("src {} has no balance", src)); unreachable!() } - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) + Some(mut src_bal) => { + src_bal.spend(&amount); + let mut dest_bal: Amount = + tx::read(&dest_key.to_string()).unwrap_or_default(); + dest_bal.receive(&amount); + tx::write(&src_key.to_string(), src_bal); + tx::write(&dest_key.to_string(), dest_bal); } - Address::Internal(InternalAddress::IbcBurn) => { + } + } + + /// A token transfer with storage keys that can be used in a transaction. + pub fn transfer_with_keys(src_key: &Key, dest_key: &Key, amount: Amount) { + let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); + let src_bal: Option = match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + Some(Amount::max()) + } + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::log_string("invalid transfer from the burn address"); unreachable!() } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { + Some(_) => tx::read(&src_key.to_string()), + None => { + // the key is not a multitoken key + match is_any_token_balance_key(src_key) { + Some(_) => tx::read(src_key.to_string()), + None => { + tx::log_string(format!( + "invalid balance key: {}", + src_key + )); + unreachable!() + } + } + } + }; + let mut src_bal = src_bal.unwrap_or_else(|| { + tx::log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount); + let dest_owner = + is_any_multitoken_balance_key(dest_key).map(|(_, o)| o); + let mut dest_bal: Amount = match dest_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { tx::log_string("invalid transfer to the mint address"); unreachable!() } - Address::Internal(InternalAddress::IbcBurn) => { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => match is_any_token_balance_key(dest_key) { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => { + tx::log_string(format!( + "invalid balance key: {}", + dest_key + )); + unreachable!() + } + }, + }; + dest_bal.receive(&amount); + match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + tx::write_temp(&src_key.to_string(), src_bal) + } + _ => tx::write(&src_key.to_string(), src_bal), + } + match dest_owner { + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::write_temp(&dest_key.to_string(), dest_bal) } _ => tx::write(&dest_key.to_string(), dest_bal), diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index e39963fae7..f86c412353 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -20,10 +20,11 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } in tx_data.matches.transfers { - token::transfer(&source, &target, &token, amount); + token::transfer(&source, &target, &token, sub_prefix, amount); } tx_data diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f0ab0ad2d0..059a3296e1 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -14,7 +14,8 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } = transfer; - token::transfer(&source, &target, &token, amount) + token::transfer(&source, &target, &token, sub_prefix, amount) } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..a2f9a6267b 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -136,7 +136,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -251,7 +253,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); @@ -284,7 +286,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..e1815cc0b1 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -34,6 +34,10 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = token::is_any_token_balance_key(key) { Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if let Some(address) = intent::is_invalid_intent_key(key) { @@ -412,7 +416,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -445,7 +451,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -482,7 +490,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let mut vp_env = vp_host_env::take(); @@ -520,7 +530,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx_host_env::insert_verifier(address); // Apply transfer in a transaction - tx_host_env::token::transfer(&source, &target, &token, amount); + tx_host_env::token::transfer( + &source, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..1062d1ce43 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -140,6 +140,7 @@ pub mod main { source: _, target, token, + sub_prefix: _, amount, } = transfer; let target_key = token::balance_key(&token, &target); From 86b373c747663d95e216a7f112c077fcb4f39ab7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 18:41:52 +0200 Subject: [PATCH 0535/1995] remove an error message --- apps/src/lib/client/rpc.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 756f704344..bf673b0dd3 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1414,14 +1414,7 @@ where Ok(values) => { let decode = |PrefixValue { key, value }: PrefixValue| { match T::try_from_slice(&value[..]) { - Err(err) => { - eprintln!( - "Skipping a value for key {}. Error in \ - decoding: {}", - key, err - ); - None - } + Err(_) => None, Ok(value) => Some((key, value)), } }; From 8789392fc41d5604df4ef19e669bfe6a36fa17a9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 21 Aug 2022 09:39:48 +0200 Subject: [PATCH 0536/1995] update a test wasm --- wasm_for_tests/tx_mint_tokens.wasm | Bin 226185 -> 229247 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e28ced047f374606582082da3d839d0..b6f8f074bf213c9a7bcd33fbf811e213ff4bab37 100755 GIT binary patch delta 78238 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 z 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 delta 75499 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
j?xxwrg2Y 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 Date: Tue, 23 Aug 2022 12:52:12 +0200 Subject: [PATCH 0537/1995] change the balance format --- apps/src/lib/client/rpc.rs | 168 ++++++++++++++----------------------- shared/src/types/token.rs | 23 +++-- 2 files changed, 71 insertions(+), 120 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index bf673b0dd3..ba02e82b5e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -131,8 +131,7 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (None, Some(owner)) => { let owner = ctx.get(&owner); - let mut found_any = false; - for (token, currency_code) in tokens { + for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = query_storage_prefix::( client.clone(), @@ -140,132 +139,87 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { ) .await; if let Some(balances) = balances { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, o)) if *o == owner => { - writeln!( - w, - "{} with {}: {}", - currency_code, sub_prefix, balance - ) - .unwrap(); - found_any = true; - } - Some(_) => {} - None => { - if let Some(o) = - token::is_any_token_balance_key(&key) - { - if *o == owner { - writeln!( - w, - "{}: {}", - currency_code, balance - ) - .unwrap(); - found_any = true; - } - } - } - } - } + print_balances(balances, &token, Some(&owner)); } } - if !found_any { - println!("No balance found for {}", owner); - } } (Some(token), None) => { let token = ctx.get(&token); let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(client, prefix).await; - match balances { - Some(balances) => { - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - let stdout = io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap(); - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } (None, None) => { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (token, currency_code) in tokens { + for (token, _) in tokens { let key = token::balance_prefix(&token); let balances = query_storage_prefix::(client.clone(), key) .await; - match balances { - Some(balances) => { - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap() - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } } } } +fn print_balances( + balances: impl Iterator, + token: &Address, + target: Option<&Address>, +) { + let stdout = io::stdout(); + let mut w = stdout.lock(); + + // Token + let tokens = address::tokens(); + let currency_code = tokens + .get(token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + writeln!(w, "Token {}", currency_code).unwrap(); + + let print_num = balances + .filter_map( + |(key, balance)| match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => Some(( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix, balance, owner + ), + )), + None => token::is_any_token_balance_key(&key).map(|owner| { + ( + owner.clone(), + format!(": {}, owned by {}", balance, owner), + ) + }), + }, + ) + .filter_map(|(o, s)| match target { + Some(t) if o == *t => Some(s), + Some(_) => None, + None => Some(s), + }) + .map(|s| { + writeln!(w, "{}", s).unwrap(); + }) + .count(); + + if print_num == 0 { + match target { + Some(t) => writeln!(w, "No balances owned by {}", t).unwrap(), + None => { + writeln!(w, "No balances for token {}", currency_code).unwrap() + } + } + } +} + /// Query Proposals pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { async fn print_proposal( diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 4d03138e60..a4a123da12 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -344,19 +344,16 @@ fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { // token, balance, owner return None; } - match key.get_at(len - 2) { - Some(DbKeySeg::StringSeg(balance)) - if balance == BALANCE_STORAGE_KEY => - { - match key.segments.last() { - Some(DbKeySeg::AddressSeg(owner)) => { - let sub_prefix = Key { - segments: key.segments[1..(len - 2)].to_vec(), - }; - Some((sub_prefix, owner)) - } - _ => None, - } + match &key.segments[..] { + [ + .., + DbKeySeg::StringSeg(balance), + DbKeySeg::AddressSeg(owner), + ] if balance == BALANCE_STORAGE_KEY => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) } _ => None, } From fe95e805810226ab492337907e060cd2af6ea798 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Sep 2022 12:40:32 +0000 Subject: [PATCH 0538/1995] 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 a337a7b1a8..5aff977ad1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.e0cf6b4c7cae7606f597f66a2b467ed44ab817744471554650e59c1504e1cfd8.wasm", - "tx_from_intent.wasm": "tx_from_intent.2190adc094be799ff1c8383e4b57c7d2ab5da328e845f8f3095cce4ed8b0041b.wasm", - "tx_ibc.wasm": "tx_ibc.014a3a93980f04147826f1e568551b1127de763e6c07480990db1c240877141b.wasm", - "tx_init_account.wasm": "tx_init_account.f4eceee710c20f402a9984338294a87ca5b33b45ac3e4c2229550c2a00597230.wasm", - "tx_init_nft.wasm": "tx_init_nft.809d0b46379ea9ea4cd7cd026e52ac7ad25094b090976a748a349691026db95b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b6b1014750b667bca57d82979c2295e99d0d3ab3723301029dcaa4b125ed401a.wasm", - "tx_init_validator.wasm": "tx_init_validator.da09c65fad79490ededfbf9ebc75df6626031f36f62791dc4e092743c91914aa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.07d3118e626ca59c95571ccf00e24265570388b66f8e67228b109b7c095653a7.wasm", - "tx_transfer.wasm": "tx_transfer.e84e5b4d77c81d2d6fe248d2c3b5ae0bb6d5fb7ef13da5e62e4b7ac7764fd80a.wasm", - "tx_unbond.wasm": "tx_unbond.35bb3196fd77c138541507d139cb68ca0f752df6cad0fb22db4a00bc64471771.wasm", - "tx_update_vp.wasm": "tx_update_vp.f73a121138f8b9d2fa0a50ae0b1dfb0b69ca4fd6559f5600aec61cd2aeece960.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.66f3c60eb109f2c631c2ee5f5fb47d73a086fa2a786b2ff63907e4e95bd79fcf.wasm", - "tx_withdraw.wasm": "tx_withdraw.a630427bcb03ec5cbd96b85ef86f79f85a5d1cff12f6b10308a9cb006ac95533.wasm", - "vp_nft.wasm": "vp_nft.bd7840f82f1ad27546266c01884ef69e81bd9e365cee53c8b013339e395ce392.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.feb9eec34cc11bd2233281a199e78d1bc284ce9eb4465198f483c5eb8075d8ab.wasm", - "vp_token.wasm": "vp_token.07f6e0b8a161125c7c1ed3356694a28e340332be1674e1d561802ba213736417.wasm", - "vp_user.wasm": "vp_user.b856d1a609021e0a4728ecd961268840eddff938fd67d09b648f31fc6422bf5a.wasm" + "tx_bond.wasm": "tx_bond.d8eb4f60b307ff0c0e5ccd76b38dac64aa9c40b32fe222e6180367f8adf61566.wasm", + "tx_from_intent.wasm": "tx_from_intent.a9fec9c3e07acf21676a4559017722c75db0d4e2013d8a24a2413cc1dcf51fbf.wasm", + "tx_ibc.wasm": "tx_ibc.f17dddf05a835f7bcd898ed90f10f4cb64c4b3246b15f4a92fa098c30eacf45e.wasm", + "tx_init_account.wasm": "tx_init_account.36f85e33e85816c73d24fdae42cef7c80309de05460e78ac5a78024b54d9c90d.wasm", + "tx_init_nft.wasm": "tx_init_nft.6e156e65bc19858ade99f91705b475fb9ab9c2641d23cf22db85958cc626146d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4e2cc918ea175b29c8b2fcf48865eb6251b8db88799f80226e796d070cd3f2e9.wasm", + "tx_init_validator.wasm": "tx_init_validator.90c115e298fb7906c83eed0e965c84ce4657a163d828b1a676f231f04cebea2a.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.86e54bbefb7b6bc4f189df15943f4b7a9247fa6faf30ebf31c8b450bacae6e08.wasm", + "tx_transfer.wasm": "tx_transfer.40d4409576a238aa447da2a5728cc2c67d0e6896e50f66c76f10f2ab28660d30.wasm", + "tx_unbond.wasm": "tx_unbond.b41aadf4b8f1f1b9188de26a54d0cfc1097a69d2e3632548144af46efe988547.wasm", + "tx_update_vp.wasm": "tx_update_vp.9a68c44fc97171a8d2218df2b1267dcf9faffb4b12393f72c4a7cc726192a351.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7053bdd44c9996c3b22d8ef65747e019328f334ed58016f7ff93d0f61d76331d.wasm", + "tx_withdraw.wasm": "tx_withdraw.dccadb23e21d1ac3f19e8c71702f5b896834757d6aa9e7d0acd369f27539dee4.wasm", + "vp_nft.wasm": "vp_nft.08c41172486de46d40e1fe4e38d742f740fc2c91929e183ec69b091907665068.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1598a5c1a78fc1df8e04c16a247a86b44e003954edc8594f4c73d3d226c1bb6c.wasm", + "vp_token.wasm": "vp_token.956607c8a4199be635a2b27799be5f78ae34dab23654c6f8b0ad36501ae740d3.wasm", + "vp_user.wasm": "vp_user.251d6f05c0a51dad915ccc11606206fdf1edb897ddcb8ce0a632d7c442372b65.wasm" } \ No newline at end of file From c33862b155616bf55866c7793a68f7b8d07fa214 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 30 Aug 2022 16:41:07 +0100 Subject: [PATCH 0539/1995] Add config for Ethereum interop --- apps/src/lib/config/ethereum.rs | 19 +++++++++++++++++++ apps/src/lib/config/mod.rs | 13 +++---------- apps/src/lib/node/ledger/mod.rs | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 apps/src/lib/config/ethereum.rs diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs new file mode 100644 index 0000000000..7f4e9fafe5 --- /dev/null +++ b/apps/src/lib/config/ethereum.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// Default [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) endpoint used by the oracle +pub const DEFAULT_ORACLE_RPC_ENDPOINT: &str = "http://127.0.0.1:8545"; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Config { + /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use + /// to listen for events from the Ethereum bridge smart contracts + pub oracle_rpc_endpoint: String, +} + +impl Default for Config { + fn default() -> Self { + Self { + oracle_rpc_endpoint: DEFAULT_ORACLE_RPC_ENDPOINT.to_owned(), + } + } +} diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index edf13cbeff..35ddc76842 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -1,5 +1,6 @@ //! Node and client configuration +pub mod ethereum; pub mod genesis; pub mod global; pub mod utils; @@ -38,8 +39,6 @@ pub const FILENAME: &str = "config.toml"; pub const TENDERMINT_DIR: &str = "tendermint"; /// Chain-specific Anoma DB. Nested in chain dirs. pub const DB_DIR: &str = "db"; -/// Websocket address for Ethereum fullnode RPC -pub const ETHEREUM_URL: &str = "http://127.0.0.1:8545"; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { @@ -84,6 +83,7 @@ pub struct Ledger { pub chain_id: ChainId, pub shell: Shell, pub tendermint: Tendermint, + pub ethereum: ethereum::Config, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -103,8 +103,6 @@ pub struct Shell { db_dir: PathBuf, /// Use the [`Ledger::tendermint_dir()`] method to read the value. tendermint_dir: PathBuf, - /// Use the [`Ledger::ethereum_url()`] method to read the value. - ethereum_url: String, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -176,7 +174,6 @@ impl Ledger { tx_wasm_compilation_cache_bytes: None, db_dir: DB_DIR.into(), tendermint_dir: TENDERMINT_DIR.into(), - ethereum_url: ETHEREUM_URL.into(), }, tendermint: Tendermint { rpc_address: SocketAddr::new( @@ -200,6 +197,7 @@ impl Ledger { ), instrumentation_namespace: "anoman_tm".to_string(), }, + ethereum: ethereum::Config::default(), } } @@ -217,11 +215,6 @@ impl Ledger { pub fn tendermint_dir(&self) -> PathBuf { self.shell.tendermint_dir(&self.chain_id) } - - /// Get the websocket url for the Ethereum fullnode - pub fn ethereum_url(&self) -> String { - self.shell.ethereum_url.clone() - } } impl Shell { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b1c0d4f2e6..16896fa143 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -305,7 +305,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let (broadcaster_sender, broadcaster_receiver) = tokio::sync::mpsc::unbounded_channel(); - let ethereum_url = config.ethereum_url(); + let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); let (ethereum_node, oracle, broadcaster) = if matches!( config.tendermint.tendermint_mode, TendermintMode::Validator From 956ee9c4af00fb68ef82fc13d1bdeff4ad7358d6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 2 Sep 2022 10:09:15 +0100 Subject: [PATCH 0540/1995] Log the Ethereum endpoint being used in more places --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 4 +++- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 39b6d282af..3ded71765b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -141,15 +141,17 @@ pub mod eth_fullnode { let client = Web3::new(url, CLIENT_TIMEOUT); const SLEEP_DUR: Duration = Duration::from_secs(1); + tracing::info!(?url, "Checking Geth status"); loop { if let Ok(false) = client.eth_syncing().await { - tracing::info!("Finished syncing"); + tracing::info!(?url, "Finished syncing"); break; } if let Err(error) = client.eth_syncing().await { // This is very noisy and usually not interesting. // Still can be very useful tracing::debug!( + ?url, ?error, "Couldn't check Geth sync status" ); diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 144f89a7af..2ed1944e25 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -101,12 +101,16 @@ pub mod oracle_process { rt.block_on(async move { LocalSet::new() .run_until(async move { - tracing::info!("Ethereum event oracle is starting"); + tracing::info!( + ?url, + "Ethereum event oracle is starting" + ); let oracle = Oracle::new(&url, sender, abort_sender); run_oracle_aux(oracle).await; tracing::info!( + ?url, "Ethereum event oracle is no longer running" ); }) From b816ee1989f59d129638b6ece770bebe42540c1f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 4 Sep 2022 16:14:15 +0100 Subject: [PATCH 0541/1995] Fix broken links in the specs --- .../specs/src/interoperability/ethereum-bridge/proofs.md | 6 +++--- .../interoperability/ethereum-bridge/transfers_to_namada.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md index 5d000d2cfc..5c67147d68 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/proofs.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/proofs.md @@ -5,11 +5,11 @@ bridge header is a proof attached to a message understandable to the Ethereum smart contracts. For transferring value to Ethereum, a proof is a signed Merkle tree root and inclusion proofs of asset transfer messages understandable to the Ethereum smart contracts, as described in the section on -[batching](transfers_to_ethereum.md/#batching) +[batching](transfers_to_ethereum.md#batching) A message for transferring value to Ethereum is a `TransferToNamada` instance as described -[here](./transfers_to_ethereum.md/#bridge-pool-validity-predicate). +[here](./transfers_to_ethereum.md#bridge-pool-validity-predicate). Additionally, when the validator set changes, the smart contracts on Ethereum must be updated so that it can continue to recognize valid proofs. @@ -98,4 +98,4 @@ transaction with a quorum of signatures offline and submit it on-chain. This transaction should include the validator set update. The only way this is impossible is if more than 1/3 of the validators by -stake from that epoch delete their ethereum keys, which is extremely unlikely. \ No newline at end of file +stake from that epoch delete their ethereum keys, which is extremely unlikely. diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md index 7036f2d9b3..106196e7b3 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md @@ -3,7 +3,7 @@ In order to facilitate transferring assets from Ethereum to Namada, There will be two internal accounts with associated native validity predicates: -- `#EthBridge` - Controls the `/eth_msgs/` [storage](ethereum_events_attestation.md/#storage) +- `#EthBridge` - Controls the `/eth_msgs/` [storage](ethereum_events_attestation.md#storage) - and ledgers of balances for wrapped Ethereum assets (ERC20 tokens) structured in a ["multitoken"](https://github.com/anoma/anoma/issues/1102) hierarchy From bf9a0966722719232d9f1343dd81d8162464aef8 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 5 Sep 2022 09:06:48 +0100 Subject: [PATCH 0542/1995] Update vm_env/src/token.rs Co-authored-by: Jacob Turner --- vm_env/src/token.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs index 784b838707..5ba2d7e33f 100644 --- a/vm_env/src/token.rs +++ b/vm_env/src/token.rs @@ -81,21 +81,19 @@ pub mod tx { sub_prefix: Option, amount: Amount, ) { - let src_key = match &sub_prefix { + let (src_key, dest_key) = match &sub_prefix { Some(sub_prefix) => { let prefix = token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, src) + ( + token::multitoken_balance_key(&prefix, src), + token::multitoken_balance_key(&prefix, dest), + ) } - None => token::balance_key(token, src), - }; - let dest_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, dest) - } - None => token::balance_key(token, dest), + None => ( + token::balance_key(token, src), + token::balance_key(token, dest), + ), }; let src_bal: Option = tx::read(&src_key.to_string()); match src_bal { From 2fa1a38eb69f9f7c2446d4f20310b505d0261eef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 10:27:42 +0100 Subject: [PATCH 0543/1995] Shimming fixes for valset upd vexts --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 1 + .../ledger/shell/vote_extensions/val_set_update.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b070cf255a..ea0b0fb8fe 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -26,6 +26,7 @@ const VALIDATOR_EXPECT_MSG: &str = "Only validators receive this method call."; pub enum VoteExtensionError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, + #[cfg(feature = "abcipp")] #[error( "The vote extension has an unexpected sequence number (e.g. block \ height)." diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 487b861f98..d3fa07ce1c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -57,6 +57,7 @@ where (VotingPower, validator_set_update::SignedVext), VoteExtensionError, > { + #[cfg(feature = "abcipp")] if ext.data.block_height != last_height { let ext_height = ext.data.block_height; tracing::error!( @@ -143,6 +144,11 @@ where &self, vote_extensions: Vec, ) -> Option { + #[cfg(not(feature = "abcipp"))] + if self.storage.last_height == BlockHeight(0) { + return None; + } + let vexts_epoch = self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", @@ -213,11 +219,15 @@ where return None; } + #[cfg(feature = "abcipp")] let voting_powers = voting_powers.expect( "We have enough voting power, so at least one validator set \ update vote extension must have been validated.", ); + #[cfg(not(feature = "abcipp"))] + let voting_powers = voting_powers.unwrap_or_else(HashMap::new); + Some(validator_set_update::VextDigest { signatures, voting_powers, From b0df28b989011b399079ace6043a25560bf19d1d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 5 Sep 2022 09:49:49 +0000 Subject: [PATCH 0544/1995] [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 5aff977ad1..4d5f3196e2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,17 @@ { - "tx_bond.wasm": "tx_bond.d8eb4f60b307ff0c0e5ccd76b38dac64aa9c40b32fe222e6180367f8adf61566.wasm", - "tx_from_intent.wasm": "tx_from_intent.a9fec9c3e07acf21676a4559017722c75db0d4e2013d8a24a2413cc1dcf51fbf.wasm", - "tx_ibc.wasm": "tx_ibc.f17dddf05a835f7bcd898ed90f10f4cb64c4b3246b15f4a92fa098c30eacf45e.wasm", + "tx_bond.wasm": "tx_bond.ca09fef16b5566de27ed431899e0efe9050623371f481369b68af32676c850b7.wasm", + "tx_from_intent.wasm": "tx_from_intent.6be622fab8082f4be8180c212a271d7e103d003526c7684b335cbbdbf0329ad1.wasm", + "tx_ibc.wasm": "tx_ibc.ef07534b7cf30af60ebc91f53bab5796647e1fd97cd2e096a60c3e064340abbf.wasm", "tx_init_account.wasm": "tx_init_account.36f85e33e85816c73d24fdae42cef7c80309de05460e78ac5a78024b54d9c90d.wasm", "tx_init_nft.wasm": "tx_init_nft.6e156e65bc19858ade99f91705b475fb9ab9c2641d23cf22db85958cc626146d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4e2cc918ea175b29c8b2fcf48865eb6251b8db88799f80226e796d070cd3f2e9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bba11610d204f103585016275d96c53c8d7f53d13853def667efa9c245b8b759.wasm", "tx_init_validator.wasm": "tx_init_validator.90c115e298fb7906c83eed0e965c84ce4657a163d828b1a676f231f04cebea2a.wasm", "tx_mint_nft.wasm": "tx_mint_nft.86e54bbefb7b6bc4f189df15943f4b7a9247fa6faf30ebf31c8b450bacae6e08.wasm", - "tx_transfer.wasm": "tx_transfer.40d4409576a238aa447da2a5728cc2c67d0e6896e50f66c76f10f2ab28660d30.wasm", + "tx_transfer.wasm": "tx_transfer.daef631bcc3ceda491dafdea95afa9d912e891c7a166fcd45b33febdac1224c7.wasm", "tx_unbond.wasm": "tx_unbond.b41aadf4b8f1f1b9188de26a54d0cfc1097a69d2e3632548144af46efe988547.wasm", "tx_update_vp.wasm": "tx_update_vp.9a68c44fc97171a8d2218df2b1267dcf9faffb4b12393f72c4a7cc726192a351.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.7053bdd44c9996c3b22d8ef65747e019328f334ed58016f7ff93d0f61d76331d.wasm", - "tx_withdraw.wasm": "tx_withdraw.dccadb23e21d1ac3f19e8c71702f5b896834757d6aa9e7d0acd369f27539dee4.wasm", + "tx_withdraw.wasm": "tx_withdraw.9b1db13df08924f6d94c6a5e982f52522e160b6c57078f9165f7aff8877abade.wasm", "vp_nft.wasm": "vp_nft.08c41172486de46d40e1fe4e38d742f740fc2c91929e183ec69b091907665068.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.1598a5c1a78fc1df8e04c16a247a86b44e003954edc8594f4c73d3d226c1bb6c.wasm", "vp_token.wasm": "vp_token.956607c8a4199be635a2b27799be5f78ae34dab23654c6f8b0ad36501ae740d3.wasm", From 92dbc598c97e5b206969fe7aa224731c21af6519 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 10:49:51 +0100 Subject: [PATCH 0545/1995] Add unit test filter to makefile targets --- Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 90f27f5339..3e9cee0f10 100644 --- a/Makefile +++ b/Makefile @@ -112,30 +112,30 @@ test-unit-abcipp: --manifest-path ./apps/Cargo.toml \ --no-default-features \ --features "testing std abcipp" \ - -- \ + $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path \ ./proof_of_stake/Cargo.toml \ --features "testing" \ - -- \ + $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./shared/Cargo.toml \ --no-default-features \ --features "testing wasm-runtime abcipp ibc-mocks-abcipp" \ - -- \ + $(TEST_FILTER) -- \ -Z unstable-options --report-time && \ $(cargo) test \ --manifest-path ./vm_env/Cargo.toml \ --no-default-features \ --features "abcipp" \ - -- \ + $(TEST_FILTER) -- \ -Z unstable-options --report-time test-unit: $(cargo) test \ - -- \ + $(TEST_FILTER) -- \ --skip e2e \ -Z unstable-options --report-time From 62c55180117baa19324cbf40124ec0c639baec4c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 25 Aug 2022 09:36:42 +0100 Subject: [PATCH 0546/1995] specs: Ethereum bridge bootstrapping --- .../ethereum-bridge/bootstrapping.md | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index 7cfe599aa8..fb1fd7e5c1 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -1,4 +1,27 @@ # Bootstrapping the bridge -Before the bridge can start running, some storage may need to be initialized in -Namada. TBD. \ No newline at end of file +The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, there is a governance parameter, `eth_bridge_proxy_address`, which is initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: + +- A governance proposal should be held to agree on a block height `l` at which to launch the Ethereum bridge by means of a hard fork. +- If the proposal passes, the Namada chain must halt after finalizing block `l-1`. +- The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are deployed to the relevant EVM chain, with the active validator set at block height `l` as the initial validator set that controls the bridge. +- Details are published so that the deployed contracts can be verified by anyone who wishes to do so. +- If active validators for block height `l` regard the deployment as valid, the chain should be restarted with a new genesis file that specifies `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. + +At this point, the bridge is launched and it may start being used. Validators' ledger nodes will immediately and automatically coordinate in order to craft the first validator set update protocol transaction. + +## Governance proposal + +The governance proposal can be freeform and simply indicate what the value of `l` should be. Validators should then configure their nodes to halt at this height. + +## Value for launch height `l` + +The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height should ideally be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. + +## Deployer + +Once the smart contracts are fully deployed, only the active validator set for block height `l` should have control of the contracts so in theory anyone could do the Ethereum bridge smart contract deployment. + +## Backing out of Ethereum bridge launch + +If for some reason the validity of the smart contract deployment cannot be agreed upon by the validators who will responsible for restarting Namada, it must remain possible to restart the chain with the Ethereum bridge still not enabled i.e. with `eth_bridge_proxy_address = "0x0000000000000000000000000000000000000000"`. From 14f4512e9faefff8ebe121c0e543b416245c5646 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 25 Aug 2022 15:42:33 +0100 Subject: [PATCH 0547/1995] WIP --- .../ethereum-bridge/bootstrapping.md | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index fb1fd7e5c1..b2b7d72a06 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -1,27 +1,62 @@ # Bootstrapping the bridge +## Overview The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, there is a governance parameter, `eth_bridge_proxy_address`, which is initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: - A governance proposal should be held to agree on a block height `l` at which to launch the Ethereum bridge by means of a hard fork. -- If the proposal passes, the Namada chain must halt after finalizing block `l-1`. +- If the proposal passes, the Namada chain must halt after finalizing block `l-1`. This requires - The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are deployed to the relevant EVM chain, with the active validator set at block height `l` as the initial validator set that controls the bridge. - Details are published so that the deployed contracts can be verified by anyone who wishes to do so. - If active validators for block height `l` regard the deployment as valid, the chain should be restarted with a new genesis file that specifies `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. At this point, the bridge is launched and it may start being used. Validators' ledger nodes will immediately and automatically coordinate in order to craft the first validator set update protocol transaction. -## Governance proposal +## Facets + +### Governance proposal The governance proposal can be freeform and simply indicate what the value of `l` should be. Validators should then configure their nodes to halt at this height. -## Value for launch height `l` +### Value for launch height `l` The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height should ideally be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. -## Deployer +### Deployer Once the smart contracts are fully deployed, only the active validator set for block height `l` should have control of the contracts so in theory anyone could do the Ethereum bridge smart contract deployment. -## Backing out of Ethereum bridge launch +### Backing out of Ethereum bridge launch If for some reason the validity of the smart contract deployment cannot be agreed upon by the validators who will responsible for restarting Namada, it must remain possible to restart the chain with the Ethereum bridge still not enabled i.e. with `eth_bridge_proxy_address = "0x0000000000000000000000000000000000000000"`. + +## Example + +In this example, all epochs are assumed to be `100` blocks long, and the active validator set does not change at any point. + +- A governance proposal is made to launch the Ethereum bridge at height `l = 3400`, i.e. the first block of epoch `34`. + +```json +{ + "content": { + "title": "Launch the Ethereum bridge", + "authors": "hello@heliax.dev", + "discussions-to": "hello@heliax.dev", + "created": "2023-01-01T08:00:00Z", + "license": "Unlicense", + "abstract": "Halt the chain and launch the Ethereum bridge at Namada block height 3400", + "motivation": "", + }, + "author": "hello@heliax.dev", + "voting_start_epoch": 30, + "voting_end_epoch": 33, + "grace_epoch": 0, +} +``` + +- The governance proposal passes at block `3300` + +- Putative Ethereum bridge smart contracts are deployed, with the proxy contract located at `0x00000000000000000000000000000000DeaDBeef` + +- At block height `3400`, the chain halts + +- The chain restarts with the governance parameter `eth_bridge_proxy_address` set to `0x00000000000000000000000000000000DeaDBeef` From a712dab493c72397aadd9ec8f750d59720758f1a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Sep 2022 14:03:12 +0100 Subject: [PATCH 0548/1995] Updating example and governance proposal --- .../ethereum-bridge/bootstrapping.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index b2b7d72a06..cb39771438 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -1,6 +1,7 @@ # Bootstrapping the bridge ## Overview + The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, there is a governance parameter, `eth_bridge_proxy_address`, which is initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: - A governance proposal should be held to agree on a block height `l` at which to launch the Ethereum bridge by means of a hard fork. @@ -19,7 +20,7 @@ The governance proposal can be freeform and simply indicate what the value of `l ### Value for launch height `l` -The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height should ideally be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. +The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height must be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. In practise, this means the governance proposal to launch the chain should commit to doing so within an epoch of passing, so that the validator set is definitely known in advance. ### Deployer @@ -53,10 +54,20 @@ In this example, all epochs are assumed to be `100` blocks long, and the active } ``` -- The governance proposal passes at block `3300` +- The governance proposal passes at block `3300` (the first block of epoch `33`) + +- Validators for epoch `33` manually configure their nodes to halt after having finalized block `3399`, before that block is reached + +- The chain halts after having finalized block `3399` (the last block of epoch `33`) + +- Putative Ethereum bridge smart contracts are deployed at this point, with the proxy contract located at `0x00000000000000000000000000000000DeaDBeef` + +- Verification of the Ethereum bridge smart contracts take place + +- Validators coordinate to craft a new genesis file for the chain restart at `3400`, with the governance parameter `eth_bridge_proxy_address` set to `0x00000000000000000000000000000000DeaDBeef` -- Putative Ethereum bridge smart contracts are deployed, with the proxy contract located at `0x00000000000000000000000000000000DeaDBeef` +- The chain restarts at `3400` (the first block of epoch `34`) -- At block height `3400`, the chain halts +- The first ever validator set update (for epoch `35`) becomes possible within a few blocks (e.g. by block `3410`) -- The chain restarts with the governance parameter `eth_bridge_proxy_address` set to `0x00000000000000000000000000000000DeaDBeef` +- A validator set update for epoch `35` is submitted to the Ethereum bridge smart contracts From a12dda3653c9bff7cdd7ec8c90753ef591e2025f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sun, 4 Sep 2022 11:16:28 +0100 Subject: [PATCH 0549/1995] Change `l` to `h` --- .../ethereum-bridge/bootstrapping.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index cb39771438..ce1f4e618a 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -4,11 +4,11 @@ The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, there is a governance parameter, `eth_bridge_proxy_address`, which is initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: -- A governance proposal should be held to agree on a block height `l` at which to launch the Ethereum bridge by means of a hard fork. -- If the proposal passes, the Namada chain must halt after finalizing block `l-1`. This requires -- The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are deployed to the relevant EVM chain, with the active validator set at block height `l` as the initial validator set that controls the bridge. +- A governance proposal should be held to agree on a block height `h` at which to launch the Ethereum bridge by means of a hard fork. +- If the proposal passes, the Namada chain must halt after finalizing block `h-1`. This requires +- The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are deployed to the relevant EVM chain, with the active validator set at block height `h` as the initial validator set that controls the bridge. - Details are published so that the deployed contracts can be verified by anyone who wishes to do so. -- If active validators for block height `l` regard the deployment as valid, the chain should be restarted with a new genesis file that specifies `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. +- If active validators for block height `h` regard the deployment as valid, the chain should be restarted with a new genesis file that specifies `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. At this point, the bridge is launched and it may start being used. Validators' ledger nodes will immediately and automatically coordinate in order to craft the first validator set update protocol transaction. @@ -16,15 +16,15 @@ At this point, the bridge is launched and it may start being used. Validators' l ### Governance proposal -The governance proposal can be freeform and simply indicate what the value of `l` should be. Validators should then configure their nodes to halt at this height. +The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. -### Value for launch height `l` +### Value for launch height `h` The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height must be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. In practise, this means the governance proposal to launch the chain should commit to doing so within an epoch of passing, so that the validator set is definitely known in advance. ### Deployer -Once the smart contracts are fully deployed, only the active validator set for block height `l` should have control of the contracts so in theory anyone could do the Ethereum bridge smart contract deployment. +Once the smart contracts are fully deployed, only the active validator set for block height `h` should have control of the contracts so in theory anyone could do the Ethereum bridge smart contract deployment. ### Backing out of Ethereum bridge launch @@ -34,7 +34,7 @@ If for some reason the validity of the smart contract deployment cannot be agree In this example, all epochs are assumed to be `100` blocks long, and the active validator set does not change at any point. -- A governance proposal is made to launch the Ethereum bridge at height `l = 3400`, i.e. the first block of epoch `34`. +- A governance proposal is made to launch the Ethereum bridge at height `h = 3400`, i.e. the first block of epoch `34`. ```json { From ed26208ab9e8130f719be16b9778d16aadde2a19 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sun, 4 Sep 2022 11:23:08 +0100 Subject: [PATCH 0550/1995] Make `grace_epoch` same as `voting_end_epoch` Also add comment --- .../src/interoperability/ethereum-bridge/bootstrapping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index ce1f4e618a..6d9d7bc21c 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -16,7 +16,7 @@ At this point, the bridge is launched and it may start being used. Validators' l ### Governance proposal -The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. +The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. The `grace_epoch` is arbitrary as there is no code to be executed as part of the proposal, instead validators must take action manually as soon as the proposal passes. `h` must be in an epoch that is strictly greater than `voting_end_epoch`. ### Value for launch height `h` @@ -50,7 +50,7 @@ In this example, all epochs are assumed to be `100` blocks long, and the active "author": "hello@heliax.dev", "voting_start_epoch": 30, "voting_end_epoch": 33, - "grace_epoch": 0, + "grace_epoch": 33, } ``` From 4b365698b311fe5b285c29703980e3848bef6ab5 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 5 Sep 2022 09:13:16 +0100 Subject: [PATCH 0551/1995] Update documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md Co-authored-by: Jacob Turner --- .../specs/src/interoperability/ethereum-bridge/bootstrapping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index 6d9d7bc21c..a3653c01df 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -16,7 +16,7 @@ At this point, the bridge is launched and it may start being used. Validators' l ### Governance proposal -The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. The `grace_epoch` is arbitrary as there is no code to be executed as part of the proposal, instead validators must take action manually as soon as the proposal passes. `h` must be in an epoch that is strictly greater than `voting_end_epoch`. +The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. The `grace_epoch` is arbitrary as there is no code to be executed as part of the proposal, instead validators must take action manually as soon as the proposal passes. The block height `h` must be in an epoch that is strictly greater than `voting_end_epoch`. ### Value for launch height `h` From a427c4a58d0e0c3f102b62fd258e3d3e70f47522 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 5 Sep 2022 09:23:17 +0100 Subject: [PATCH 0552/1995] Wrap to 80 characters --- .../ethereum-bridge/bootstrapping.md | 82 ++++++++++++++----- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index a3653c01df..1a1f141db3 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -2,39 +2,72 @@ ## Overview -The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, there is a governance parameter, `eth_bridge_proxy_address`, which is initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: - -- A governance proposal should be held to agree on a block height `h` at which to launch the Ethereum bridge by means of a hard fork. -- If the proposal passes, the Namada chain must halt after finalizing block `h-1`. This requires -- The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are deployed to the relevant EVM chain, with the active validator set at block height `h` as the initial validator set that controls the bridge. -- Details are published so that the deployed contracts can be verified by anyone who wishes to do so. -- If active validators for block height `h` regard the deployment as valid, the chain should be restarted with a new genesis file that specifies `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. - -At this point, the bridge is launched and it may start being used. Validators' ledger nodes will immediately and automatically coordinate in order to craft the first validator set update protocol transaction. +The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, +there is a governance parameter, `eth_bridge_proxy_address`, which is +initialized to the zero Ethereum address +(`"0x0000000000000000000000000000000000000000"`). An overview of the steps to +enable the Ethereum bridge for a given Namada chain are: + +- A governance proposal should be held to agree on a block height `h` at which + to launch the Ethereum bridge by means of a hard fork. +- If the proposal passes, the Namada chain must halt after finalizing block + `h-1`. This requires +- The [Ethereum bridge smart contracts](./ethereum_smart_contracts.md) are + deployed to the relevant EVM chain, with the active validator set at block + height `h` as the initial validator set that controls the bridge. +- Details are published so that the deployed contracts can be verified by anyone + who wishes to do so. +- If active validators for block height `h` regard the deployment as valid, the + chain should be restarted with a new genesis file that specifies + `eth_bridge_proxy_address` as the Ethereum address of the proxy contract. + +At this point, the bridge is launched and it may start being used. Validators' +ledger nodes will immediately and automatically coordinate in order to craft the +first validator set update protocol transaction. ## Facets ### Governance proposal -The governance proposal can be freeform and simply indicate what the value of `h` should be. Validators should then configure their nodes to halt at this height. The `grace_epoch` is arbitrary as there is no code to be executed as part of the proposal, instead validators must take action manually as soon as the proposal passes. The block height `h` must be in an epoch that is strictly greater than `voting_end_epoch`. +The governance proposal can be freeform and simply indicate what the value of +`h` should be. Validators should then configure their nodes to halt at this +height. The `grace_epoch` is arbitrary as there is no code to be executed as +part of the proposal, instead validators must take action manually as soon as +the proposal passes. The block height `h` must be in an epoch that is strictly +greater than `voting_end_epoch`. ### Value for launch height `h` -The active validator set at the launch height chosen for starting the Ethereum bridge will have the extra responsibility of restarting the chain if they consider the deployed smart contracts as valid. For this reason, the validator set at this height must be known in advance of the governance proposal resolving, and a channel set up for offchain communication and co-ordination of the chain restart. In practise, this means the governance proposal to launch the chain should commit to doing so within an epoch of passing, so that the validator set is definitely known in advance. +The active validator set at the launch height chosen for starting the Ethereum +bridge will have the extra responsibility of restarting the chain if they +consider the deployed smart contracts as valid. For this reason, the validator +set at this height must be known in advance of the governance proposal +resolving, and a channel set up for offchain communication and co-ordination of +the chain restart. In practise, this means the governance proposal to launch the +chain should commit to doing so within an epoch of passing, so that the +validator set is definitely known in advance. ### Deployer -Once the smart contracts are fully deployed, only the active validator set for block height `h` should have control of the contracts so in theory anyone could do the Ethereum bridge smart contract deployment. +Once the smart contracts are fully deployed, only the active validator set for +block height `h` should have control of the contracts so in theory anyone could +do the Ethereum bridge smart contract deployment. ### Backing out of Ethereum bridge launch -If for some reason the validity of the smart contract deployment cannot be agreed upon by the validators who will responsible for restarting Namada, it must remain possible to restart the chain with the Ethereum bridge still not enabled i.e. with `eth_bridge_proxy_address = "0x0000000000000000000000000000000000000000"`. +If for some reason the validity of the smart contract deployment cannot be +agreed upon by the validators who will responsible for restarting Namada, it +must remain possible to restart the chain with the Ethereum bridge still not +enabled i.e. with `eth_bridge_proxy_address = +"0x0000000000000000000000000000000000000000"`. ## Example -In this example, all epochs are assumed to be `100` blocks long, and the active validator set does not change at any point. +In this example, all epochs are assumed to be `100` blocks long, and the active +validator set does not change at any point. -- A governance proposal is made to launch the Ethereum bridge at height `h = 3400`, i.e. the first block of epoch `34`. +- A governance proposal is made to launch the Ethereum bridge at height `h = + 3400`, i.e. the first block of epoch `34`. ```json { @@ -56,18 +89,25 @@ In this example, all epochs are assumed to be `100` blocks long, and the active - The governance proposal passes at block `3300` (the first block of epoch `33`) -- Validators for epoch `33` manually configure their nodes to halt after having finalized block `3399`, before that block is reached +- Validators for epoch `33` manually configure their nodes to halt after having + finalized block `3399`, before that block is reached -- The chain halts after having finalized block `3399` (the last block of epoch `33`) +- The chain halts after having finalized block `3399` (the last block of epoch + `33`) -- Putative Ethereum bridge smart contracts are deployed at this point, with the proxy contract located at `0x00000000000000000000000000000000DeaDBeef` +- Putative Ethereum bridge smart contracts are deployed at this point, with the + proxy contract located at `0x00000000000000000000000000000000DeaDBeef` - Verification of the Ethereum bridge smart contracts take place -- Validators coordinate to craft a new genesis file for the chain restart at `3400`, with the governance parameter `eth_bridge_proxy_address` set to `0x00000000000000000000000000000000DeaDBeef` +- Validators coordinate to craft a new genesis file for the chain restart at + `3400`, with the governance parameter `eth_bridge_proxy_address` set to + `0x00000000000000000000000000000000DeaDBeef` - The chain restarts at `3400` (the first block of epoch `34`) -- The first ever validator set update (for epoch `35`) becomes possible within a few blocks (e.g. by block `3410`) +- The first ever validator set update (for epoch `35`) becomes possible within a + few blocks (e.g. by block `3410`) -- A validator set update for epoch `35` is submitted to the Ethereum bridge smart contracts +- A validator set update for epoch `35` is submitted to the Ethereum bridge + smart contracts From 881f8d9946cef62b98a5a6e936ba258a48fc4d06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 11:01:42 +0100 Subject: [PATCH 0553/1995] Misc changes --- .../lib/node/ledger/shell/vote_extensions.rs | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index ea0b0fb8fe..7ace4dd853 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -277,16 +277,22 @@ pub fn deserialize_vote_extensions( use namada::types::transaction::protocol::ProtocolTx; txs.iter().filter_map(|tx| { - if let Ok(tx) = Tx::try_from(tx.as_slice()) { - match process_tx(tx).ok()? { - TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::VoteExtension(ext), - .. - }) => Some(ext), - _ => None, + let tx = match Tx::try_from(tx.as_slice()) { + Ok(tx) => tx, + Err(err) => { + tracing::warn!( + ?err, + "Failed to deserialize tx in deserialize_vote_extensions" + ); + return None; } - } else { - None + }; + match process_tx(tx).ok()? { + TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::VoteExtension(ext), + .. + }) => Some(ext), + _ => None, } }) } @@ -309,9 +315,9 @@ pub fn iter_protocol_txs( /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering /// out invalid data, and splits these into [`ethereum_events::Vext`] /// and [`validator_set_update::Vext`] instances. -#[cfg(feature = "abcipp")] pub fn split_vote_extensions( - vote_extensions: Vec, + #[cfg(feature = "abcipp")] vote_extensions: Vec, + #[cfg(not(feature = "abcipp"))] vote_extensions: &[TxBytes], ) -> ( Vec>, Vec, @@ -328,26 +334,3 @@ pub fn split_vote_extensions( (eth_evs, valset_upds) } - -/// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering -/// out invalid data, and splits these into [`ethereum_events::Vext`] -/// and [`validator_set_update::Vext`] instances. -#[cfg(not(feature = "abcipp"))] -pub fn split_vote_extensions( - transactions: &[TxBytes], -) -> ( - Vec>, - Vec, -) { - let mut eth_evs = vec![]; - let mut valset_upds = vec![]; - - for ext in deserialize_vote_extensions(transactions) { - if let Some(validator_set_update) = ext.validator_set_update { - valset_upds.push(validator_set_update); - } - eth_evs.push(ext.ethereum_events); - } - - (eth_evs, valset_upds) -} From 80537e1db1ee3b087d515c21aaf3c8b18f5b884a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 11:02:10 +0100 Subject: [PATCH 0554/1995] Add clippy suggestion --- .../src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index d3fa07ce1c..66649a2e89 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -226,7 +226,7 @@ where ); #[cfg(not(feature = "abcipp"))] - let voting_powers = voting_powers.unwrap_or_else(HashMap::new); + let voting_powers = voting_powers.unwrap_or_default(); Some(validator_set_update::VextDigest { signatures, From cf9fbfc17d2673cfb70dafe4a445651cd49d328d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 13:08:20 +0100 Subject: [PATCH 0555/1995] Allow sending valset upd at every block height with abciplus --- .../lib/node/ledger/shell/prepare_proposal.rs | 63 ++++++++----------- apps/src/lib/node/ledger/shell/queries.rs | 45 +++++++++++++ 2 files changed, 72 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index fca59eeb18..ed16879bc3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,7 +14,6 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, TxRecord, }; -#[cfg(feature = "abcipp")] use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shell::vote_extensions::{ iter_protocol_txs, split_vote_extensions, @@ -122,45 +121,22 @@ where ); #[cfg(not(feature = "abcipp"))] let (eth_events, valset_upds) = split_vote_extensions(txs); - #[cfg(feature = "abcipp")] - const NOT_ENOUGH_VOTING_POWER_MSG: &str = - "A Tendermint quorum should never decide on a block including \ - vote extensions reflecting less than or equal to 2/3 of the \ - total stake."; let ethereum_events = self .compress_ethereum_events(eth_events) - .unwrap_or_else(|| { - panic!("{}", { - #[cfg(feature = "abcipp")] - { - NOT_ENOUGH_VOTING_POWER_MSG - } - - #[cfg(not(feature = "abcipp"))] - { - "CONSENSUS FAILURE!!!!!" - } - }) - }); + .unwrap_or_else(|| panic!("{}", not_enough_voting_power_msg())); - #[cfg(feature = "abcipp")] - let validator_set_update = if self - .storage - .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) - { - Some( - self.compress_valset_updates(valset_upds) - .expect(NOT_ENOUGH_VOTING_POWER_MSG), - ) - } else { - None - }; - #[cfg(not(feature = "abcipp"))] - let validator_set_update = Some( - self.compress_valset_updates(valset_upds) - .expect("CONSENSUS FAILURE!!!!!"), - ); + let validator_set_update = + if self + .storage + .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) + { + Some(self.compress_valset_updates(valset_upds).unwrap_or_else( + || panic!("{}", not_enough_voting_power_msg()), + )) + } else { + None + }; let protocol_key = self .mode @@ -241,6 +217,21 @@ where } } +/// Returns a suitable message to be displayed when Tendermint +/// somehow decides on a block containing vote extensions +/// reflecting `<= 2/3` of the total stake. +const fn not_enough_voting_power_msg() -> &'static str { + #[cfg(feature = "abcipp")] + { + "A Tendermint quorum should never decide on a block including vote \ + extensions reflecting less than or equal to 2/3 of the total stake." + } + #[cfg(not(feature = "abcipp"))] + { + "CONSENSUS FAILURE!!!!!11one!" + } +} + /// Functions for creating the appropriate TxRecord given the /// numeric code #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e98d08ca5e..6ff038c401 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -549,6 +549,7 @@ where }) } + #[cfg(feature = "abcipp")] fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { let (check_prev_heights, height) = match can_send { SendValsetUpd::Now => (false, self.get_current_decision_height()), @@ -580,6 +581,12 @@ where && fst_heights_of_each_epoch.binary_search(&height).is_ok() } + #[cfg(not(feature = "abcipp"))] + #[inline(always)] + fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { + true + } + #[inline] fn get_epoch(&self, height: BlockHeight) -> Option { self.block.pred_epochs.get_epoch(height) @@ -691,6 +698,7 @@ mod test_queries { }; } + #[cfg(feature = "abcipp")] test_can_send_validator_set_update! { epoch_assertions: [ // (current epoch, current block height, can send valset upd) @@ -726,4 +734,41 @@ mod test_queries { (2, 28, false), ], } + + #[cfg(not(feature = "abcipp"))] + test_can_send_validator_set_update! { + epoch_assertions: [ + // (current epoch, current block height, can send valset upd) + (0, 1, true), + (0, 2, true), + (0, 3, true), + (0, 4, true), + (0, 5, true), + (0, 6, true), + (0, 7, true), + (0, 8, true), + (0, 9, true), + (0, 10, true), + (0, 11, true), + // we will change epoch here + (1, 12, true), + (1, 13, true), + (1, 14, true), + (1, 15, true), + (1, 16, true), + (1, 17, true), + (1, 18, true), + (1, 19, true), + (1, 20, true), + (1, 21, true), + (1, 22, true), + (1, 23, true), + (1, 24, true), + // we will change epoch here + (2, 25, true), + (2, 26, true), + (2, 27, true), + (2, 28, true), + ], + } } From 40e914cf46775e17981bf9298c720d954b485d9b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 13:21:34 +0100 Subject: [PATCH 0556/1995] Do not drop broadcaster receiver chan --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 ++++++------- .../lib/node/ledger/shell/process_proposal.rs | 28 +++++++++---------- apps/src/lib/node/ledger/shell/queries.rs | 2 +- .../shell/vote_extensions/val_set_update.rs | 6 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ed16879bc3..7ee68c3b26 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -346,7 +346,7 @@ mod test_prepare_proposal { /// proposed block. #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), @@ -385,7 +385,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_out_bad_vext_signatures() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -418,7 +418,7 @@ mod test_prepare_proposal { const LAST_HEIGHT: BlockHeight = BlockHeight(3); const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -463,7 +463,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_out_bad_vext_validators() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -494,7 +494,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_filter_duped_ethereum_events() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -581,7 +581,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_vext_normal_op() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -662,7 +662,7 @@ mod test_prepare_proposal { fn test_prepare_proposal_vext_normal_op() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the block height shell.storage.last_height = LAST_HEIGHT; @@ -742,7 +742,7 @@ mod test_prepare_proposal { // starting the shell like this will contain insufficient voting // power - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); // artificially change the voting power of the default validator to // zero, change the block height, and commit a dummy block, @@ -864,7 +864,7 @@ mod test_prepare_proposal { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -913,7 +913,7 @@ mod test_prepare_proposal { /// corresponding wrappers #[test] fn test_decrypted_txs_in_correct_order() { - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); let keypair = gen_keypair(); let mut expected_wrapper = vec![]; let mut expected_decrypted = vec![]; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 00e299969a..bf439e6bfa 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -448,7 +448,7 @@ mod test_process_proposal { #[test] fn test_more_than_one_vext_digest_rejected() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { @@ -522,7 +522,7 @@ mod test_process_proposal { #[test] fn test_drop_vext_digest_with_invalid_sigs() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { @@ -576,7 +576,7 @@ mod test_process_proposal { fn test_drop_vext_digest_with_invalid_bheights() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { @@ -640,7 +640,7 @@ mod test_process_proposal { #[test] fn test_drop_vext_digest_with_invalid_validators() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); - let (mut shell, _, _) = test_utils::setup(); + let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (addr, protocol_key) = { let bertha_key = wallet::defaults::bertha_keypair(); @@ -691,7 +691,7 @@ mod test_process_proposal { /// by [`process_proposal`]. #[test] fn test_unsigned_wrapper_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -737,7 +737,7 @@ mod test_process_proposal { /// Test that a wrapper tx with invalid signature is rejected #[test] fn test_wrapper_bad_signature_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -820,7 +820,7 @@ mod test_process_proposal { /// non-zero, [`process_proposal`] rejects that tx #[test] fn test_wrapper_unknown_address() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -864,7 +864,7 @@ mod test_process_proposal { /// [`process_proposal`] rejects that tx #[test] fn test_wrapper_insufficient_balance_address() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -911,7 +911,7 @@ mod test_process_proposal { /// validated, [`process_proposal`] rejects it #[test] fn test_decrypted_txs_out_of_order() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { @@ -976,7 +976,7 @@ mod test_process_proposal { /// is rejected by [`process_proposal`] #[test] fn test_incorrectly_labelled_as_undecryptable() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); let tx = Tx::new( @@ -1027,7 +1027,7 @@ mod test_process_proposal { /// undecryptable but still accepted #[test] fn test_invalid_hash_commitment() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let tx = Tx::new( @@ -1073,7 +1073,7 @@ mod test_process_proposal { /// marked undecryptable and the errors handled correctly #[test] fn test_undecryptable() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); let pubkey = EncryptionKey::default(); // not valid tx bytes @@ -1115,7 +1115,7 @@ mod test_process_proposal { /// [`process_proposal`] than expected, they are rejected #[test] fn test_too_many_decrypted_txs() { - let (mut shell, _, _) = TestShell::new(); + let (mut shell, _recv, _) = TestShell::new(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), @@ -1148,7 +1148,7 @@ mod test_process_proposal { /// Process Proposal should reject a RawTx, but not panic #[test] fn test_raw_tx_rejected() { - let (mut shell, _, _) = test_utils::setup_at_height(3u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 6ff038c401..15d9fb98c2 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -625,7 +625,7 @@ mod test_queries { /// expected. #[test] fn test_can_send_validator_set_update() { - let (mut shell, _, _) = test_utils::setup_at_height(0u64); + let (mut shell, _recv, _) = test_utils::setup_at_height(0u64); let epoch_assertions = $epoch_assertions; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 66649a2e89..fc07a19f76 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -268,7 +268,7 @@ mod test_vote_extensions { // - add validator voting powers from storage #[test] fn test_reject_incorrect_block_height() { - let (shell, _, _) = test_utils::setup(); + let (shell, _recv, _) = test_utils::setup(); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); @@ -322,7 +322,7 @@ mod test_vote_extensions { /// a non-validator are rejected #[test] fn test_valset_upd_must_be_signed_by_validator() { - let (shell, _, _) = test_utils::setup(); + let (shell, _recv, _) = test_utils::setup(); let (protocol_key, validator_addr) = { let bertha_key = wallet::defaults::bertha_keypair(); let bertha_addr = wallet::defaults::bertha_address(); @@ -442,7 +442,7 @@ mod test_vote_extensions { // - add validator voting powers from storage #[test] fn test_reject_bad_signatures() { - let (shell, _, _) = test_utils::setup(); + let (shell, _recv, _) = test_utils::setup(); let validator_addr = shell.mode.get_validator_address().unwrap().clone(); From 976988760e940b8cbc9bd261d4dad124deacb157 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 13:51:21 +0100 Subject: [PATCH 0557/1995] Fix can send valset upd unit test --- apps/src/lib/node/ledger/shell/queries.rs | 135 ++++++++++++---------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 15d9fb98c2..d4ade3b380 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -629,6 +629,17 @@ mod test_queries { let epoch_assertions = $epoch_assertions; + // TODO: switch to `Result::into_ok_or_err` when it becomes + // stable + const fn extract( + can_send: ::std::result::Result, + ) -> bool { + match can_send { + Ok(x) => x, + Err(x) => x, + } + } + // test `SendValsetUpd::Now` and `SendValsetUpd::AtPrevHeight` for (idx, (curr_epoch, curr_block_height, can_send)) in epoch_assertions.iter().copied().enumerate() @@ -647,7 +658,7 @@ mod test_queries { shell .storage .can_send_validator_set_update(SendValsetUpd::Now), - can_send + extract(can_send) ); if let Some((epoch, height, can_send)) = epoch_assertions.get(idx.wrapping_sub(1)).copied() @@ -660,12 +671,12 @@ mod test_queries { shell.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight ), - can_send + extract(can_send) ); } if epoch_assertions .get(idx + 1) - .map(|&(_, _, change_epoch)| change_epoch) + .map(|&(_, _, change_epoch)| change_epoch.is_ok()) .unwrap_or(false) { let time = namada::types::time::DateTimeUtc::now(); @@ -691,7 +702,7 @@ mod test_queries { curr_block_height.into() ) ), - can_send + extract(can_send) ); } } @@ -701,74 +712,74 @@ mod test_queries { #[cfg(feature = "abcipp")] test_can_send_validator_set_update! { epoch_assertions: [ - // (current epoch, current block height, can send valset upd) - (0, 1, true), - (0, 2, false), - (0, 3, false), - (0, 4, false), - (0, 5, false), - (0, 6, false), - (0, 7, false), - (0, 8, false), - (0, 9, false), - (0, 10, false), - (0, 11, false), + // (current epoch, current block height, can send valset upd / Ok = change epoch) + (0, 1, Ok(true)), + (0, 2, Err(false)), + (0, 3, Err(false)), + (0, 4, Err(false)), + (0, 5, Err(false)), + (0, 6, Err(false)), + (0, 7, Err(false)), + (0, 8, Err(false)), + (0, 9, Err(false)), + (0, 10, Err(false)), + (0, 11, Err(false)), // we will change epoch here - (1, 12, true), - (1, 13, false), - (1, 14, false), - (1, 15, false), - (1, 16, false), - (1, 17, false), - (1, 18, false), - (1, 19, false), - (1, 20, false), - (1, 21, false), - (1, 22, false), - (1, 23, false), - (1, 24, false), + (1, 12, Ok(true)), + (1, 13, Err(false)), + (1, 14, Err(false)), + (1, 15, Err(false)), + (1, 16, Err(false)), + (1, 17, Err(false)), + (1, 18, Err(false)), + (1, 19, Err(false)), + (1, 20, Err(false)), + (1, 21, Err(false)), + (1, 22, Err(false)), + (1, 23, Err(false)), + (1, 24, Err(false)), // we will change epoch here - (2, 25, true), - (2, 26, false), - (2, 27, false), - (2, 28, false), + (2, 25, Ok(true)), + (2, 26, Err(false)), + (2, 27, Err(false)), + (2, 28, Err(false)), ], } #[cfg(not(feature = "abcipp"))] test_can_send_validator_set_update! { epoch_assertions: [ - // (current epoch, current block height, can send valset upd) - (0, 1, true), - (0, 2, true), - (0, 3, true), - (0, 4, true), - (0, 5, true), - (0, 6, true), - (0, 7, true), - (0, 8, true), - (0, 9, true), - (0, 10, true), - (0, 11, true), + // (current epoch, current block height, can send valset upd / Ok = change epoch) + (0, 1, Ok(true)), + (0, 2, Err(true)), + (0, 3, Err(true)), + (0, 4, Err(true)), + (0, 5, Err(true)), + (0, 6, Err(true)), + (0, 7, Err(true)), + (0, 8, Err(true)), + (0, 9, Err(true)), + (0, 10, Err(true)), + (0, 11, Err(true)), // we will change epoch here - (1, 12, true), - (1, 13, true), - (1, 14, true), - (1, 15, true), - (1, 16, true), - (1, 17, true), - (1, 18, true), - (1, 19, true), - (1, 20, true), - (1, 21, true), - (1, 22, true), - (1, 23, true), - (1, 24, true), + (1, 12, Ok(true)), + (1, 13, Err(true)), + (1, 14, Err(true)), + (1, 15, Err(true)), + (1, 16, Err(true)), + (1, 17, Err(true)), + (1, 18, Err(true)), + (1, 19, Err(true)), + (1, 20, Err(true)), + (1, 21, Err(true)), + (1, 22, Err(true)), + (1, 23, Err(true)), + (1, 24, Err(true)), // we will change epoch here - (2, 25, true), - (2, 26, true), - (2, 27, true), - (2, 28, true), + (2, 25, Ok(true)), + (2, 26, Err(true)), + (2, 27, Err(true)), + (2, 28, Err(true)), ], } } From 26b11f31215b1bb270f6d27a71c519cd7e9c32c6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 14:01:56 +0100 Subject: [PATCH 0558/1995] Fix test_error_in_processing_tx() test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7ee68c3b26..73bb14b4e5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -905,7 +905,18 @@ mod test_prepare_proposal { record::remove(wrapper) ); #[cfg(not(feature = "abcipp"))] - assert!(shell.prepare_proposal(req).txs.is_empty()); + assert!('assertion: loop { + // this includes valset upd and eth events + // vote extension diggests + let transactions = shell.prepare_proposal(req).txs; + assert_eq!(transactions.len(), 2); + for tx in transactions { + if &tx == &wrapper { + break 'assertion false; + } + } + break 'assertion true; + }); } /// Test that the decrypted txs are included From eabd255f50a8ce014a321a052b1b2993e0649513 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 14:08:00 +0100 Subject: [PATCH 0559/1995] Fix test_prepare_proposal_filter_duped_ethereum_events() unit test --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 73bb14b4e5..1231ea8c4f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -521,11 +521,21 @@ mod test_prepare_proposal { let maybe_digest = shell.compress_ethereum_events(vec![signed_vote_extension]); - // we should be filtering out the vote extension with - // duped ethereum events; therefore, no valid vote - // extensions will remain, and we will get no - // digest from compressing nil vote extensions - assert!(maybe_digest.is_none()); + #[cfg(feature = "abcipp")] + { + // we should be filtering out the vote extension with + // duped ethereum events; therefore, no valid vote + // extensions will remain, and we will get no + // digest from compressing nil vote extensions + assert!(maybe_digest.is_none()); + } + + #[cfg(not(feature = "abcipp"))] + { + use assert_matches::assert_matches; + + assert_matches!(maybe_digest, Some(d) if d.signatures.is_empty()); + } } /// Creates an Ethereum events digest manually, and encodes it as a From e813184c03ea1aa2d6d23e557051a27ec4aeb729 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 14:19:22 +0100 Subject: [PATCH 0560/1995] Fix test_prepare_proposal_vext_normal_op() unit test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1231ea8c4f..898477581f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -707,9 +707,18 @@ mod test_prepare_proposal { txs: vec![tx], ..Default::default() }); + #[cfg(feature = "abcipp")] assert_eq!(rsp.txs.len(), 1); + #[cfg(not(feature = "abcipp"))] + assert_eq!(rsp.txs.len(), 2); + #[cfg(feature = "abcipp")] let tx_bytes = rsp.txs.pop().unwrap(); + #[cfg(not(feature = "abcipp"))] + // NOTE: we remove the first pos, bc the ethereum events + // vote extension protocol tx will always precede the + // valset upd vext protocol tx + let tx_bytes = rsp.txs.remove(0); let got = Tx::try_from(&tx_bytes[..]).unwrap(); let got_signed_tx = SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); From 597503d21eda28b4f289d1246f594b46f58d233c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 14:25:59 +0100 Subject: [PATCH 0561/1995] Fix test_prepare_proposal_rejects_non_wrapper_tx() unit test --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 898477581f..d014496ef2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -347,14 +347,14 @@ mod test_prepare_proposal { #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); - let tx = Tx::new( + let non_wrapper_tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), ); let req = RequestPrepareProposal { #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), - txs: vec![tx.to_bytes()], + txs: vec![non_wrapper_tx.to_bytes()], max_tx_bytes: 0, ..Default::default() }; @@ -362,10 +362,22 @@ mod test_prepare_proposal { assert_eq!( // NOTE: we process mempool txs after protocol txs shell.prepare_proposal(req).tx_records.remove(1), - record::remove(tx.to_bytes()) + record::remove(non_wrapper_tx.to_bytes()) ); #[cfg(not(feature = "abcipp"))] - assert!(shell.prepare_proposal(req).txs.is_empty()); + assert!('assertion: loop { + // this includes valset upd and eth events + // vote extension diggests + let transactions = shell.prepare_proposal(req).txs; + assert_eq!(transactions.len(), 2); + let non_wrapper_tx = non_wrapper_tx.to_bytes(); + for tx in transactions { + if &tx == &non_wrapper_tx { + break 'assertion false; + } + } + break 'assertion true; + }); } /// Check if we are filtering out an invalid vote extension `vext` From c75394be00e6eb07bf9ffedf0fe924f7f390d726 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 14:31:47 +0100 Subject: [PATCH 0562/1995] Fix test_prepare_proposal_vext_insufficient_voting_power() unit test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d014496ef2..e0857178ad 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -771,8 +771,6 @@ mod test_prepare_proposal { const FIRST_HEIGHT: BlockHeight = BlockHeight(0); const LAST_HEIGHT: BlockHeight = BlockHeight(FIRST_HEIGHT.0 + 11); - // starting the shell like this will contain insufficient voting - // power let (mut shell, _recv, _) = test_utils::setup(); // artificially change the voting power of the default validator to @@ -861,9 +859,9 @@ mod test_prepare_proposal { txs: vec![vote], ..Default::default() }); - assert_eq!(rsp.txs.len(), 1); + assert_eq!(rsp.txs.len(), 2); - let tx_bytes = rsp.txs.pop().unwrap(); + let tx_bytes = rsp.txs.remove(0); let got = Tx::try_from(&tx_bytes[..]).unwrap(); let got_signed_tx = SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); From f2cfb5058fa2c614133b9c9a9660e494155a65ed Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Sep 2022 15:19:33 +0100 Subject: [PATCH 0563/1995] Clippy suggestions --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e0857178ad..765ace2b6c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -365,18 +365,20 @@ mod test_prepare_proposal { record::remove(non_wrapper_tx.to_bytes()) ); #[cfg(not(feature = "abcipp"))] - assert!('assertion: loop { + assert!({ + let mut assertion = true; // this includes valset upd and eth events // vote extension diggests let transactions = shell.prepare_proposal(req).txs; assert_eq!(transactions.len(), 2); let non_wrapper_tx = non_wrapper_tx.to_bytes(); for tx in transactions { - if &tx == &non_wrapper_tx { - break 'assertion false; + if tx == non_wrapper_tx { + assertion = false; + break; } } - break 'assertion true; + assertion }); } @@ -934,17 +936,19 @@ mod test_prepare_proposal { record::remove(wrapper) ); #[cfg(not(feature = "abcipp"))] - assert!('assertion: loop { + assert!({ + let mut assertion = true; // this includes valset upd and eth events // vote extension diggests let transactions = shell.prepare_proposal(req).txs; assert_eq!(transactions.len(), 2); for tx in transactions { - if &tx == &wrapper { - break 'assertion false; + if tx == wrapper { + assertion = false; + break; } } - break 'assertion true; + assertion }); } From 5423a7663444ec69d24938af3e8cbd264701a9aa Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 5 Sep 2022 16:36:35 +0200 Subject: [PATCH 0564/1995] [feat]: Added unit test for the bridge pool vp --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 503 ++++++++++++++++-- .../ledger/eth_bridge/storage/bridge_pool.rs | 12 +- shared/src/ledger/eth_bridge/storage/mod.rs | 1 - shared/src/types/eth_bridge_pool.rs | 39 +- shared/src/types/ethereum_events.rs | 1 + shared/src/types/mod.rs | 2 +- vm_env/src/lib.rs | 1 + wasm/checksums.json | 35 +- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/tx_bridge_pool.rs | 27 + 11 files changed, 559 insertions(+), 64 deletions(-) create mode 100644 wasm/wasm_source/src/tx_bridge_pool.rs diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b855a83f6d..3e810bb89b 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1,20 +1,26 @@ //! Validity predicate for the Ethereum bridge use std::collections::{BTreeSet, HashSet}; -use borsh::{BorshDeserialize, BorshSerialize}; -use eyre::{Report, Result}; +use borsh::BorshDeserialize; +use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ - BRIDGE_POOL_ADDRESS, get_pending_key, get_signed_root_key, -} + get_pending_key, get_signed_root_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp}; -use crate::ledger::storage::{DB, DBIter, StorageHasher}; -use crate::types::address::{Address, InternalAddress}; -use crate::types::eth_bridge_pool::{GasFee, PendingTransfer, TransferToEthereum}; -use crate::types::storage::{Key, KeySeg}; -use crate::types::token::Amount; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::proto::SignedTxData; +use crate::types::address::{xan, Address, InternalAddress}; +use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::storage::Key; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error that may be returned by the validity predicate +pub struct Error(#[from] eyre::Error); + /// A positive or negative amount enum SignedAmount { Positive(Amount), @@ -22,19 +28,19 @@ enum SignedAmount { } /// Validity predicate for the Ethereum bridge -pub struct BridgePoolVp<'ctx, DB, H, CA> +pub struct BridgePoolVp<'ctx, D, H, CA> where - DB: DB + for<'iter> DBIter<'iter>, + D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, CA: 'static + WasmCacheAccess, { /// Context to interact with the host structures. - pub ctx: Ctx<'ctx, DB, H, CA>, + pub ctx: Ctx<'ctx, D, H, CA>, } -impl<'a, DB, H, CA> BridgePoolVp<'a, DB, H, CA> +impl<'a, D, H, CA> BridgePoolVp<'a, D, H, CA> where - DB: 'static + DB + for<'iter> DBIter<'iter>, + D: 'static + DB + for<'iter> DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { @@ -65,68 +71,491 @@ where /// Get the change in the balance of an account /// associated with an address fn account_balance_delta(&self, address: &Address) -> Option { - let account_key = Key::from(address.to_db_key()); + let account_key = balance_key(&xan(), address); let before: Amount = self.read_pre_value(&account_key)?; - let after: Amount = self.read_post_value(&account_key)? + let after: Amount = self.read_post_value(&account_key)?; if before > after { - Some(SignedAmount::Negative(before - after) + Some(SignedAmount::Negative(before - after)) } else { - Some(SignedAmount::Positive(after - before) + Some(SignedAmount::Positive(after - before)) } } } -impl<'a, DB, H, CA> NativeVp for BridgePoolVp<'a, DB, H, CA> +impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> where - DB: 'static + DB + for<'iter> DBIter<'iter>, + D: 'static + DB + for<'iter> DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - const ADDR: InternalAddress = InternalAddress::EthBridgePool; + type Error = Error; - type Error = Report; + const ADDR: InternalAddress = InternalAddress::EthBridgePool; fn validate_tx( &self, tx_data: &[u8], keys_changed: &BTreeSet, _verifiers: &BTreeSet
, - ) -> Result { + ) -> Result { tracing::debug!( - tx_data_len = _tx_data.len(), - keys_changed_len = _keys_changed.len(), + tx_data_len = tx_data.len(), + keys_changed_len = keys_changed.len(), verifiers_len = _verifiers.len(), "Validity predicate triggered", ); - let transfer: PendingTransfer = BorshDeserialize::try_from_slice(tx_data)?; + let signed: SignedTxData = + BorshDeserialize::try_from_slice(&tx_data[..]) + .map_err(|e| Error(e.into()))?; + + let transfer: PendingTransfer = match signed.data { + Some(data) => BorshDeserialize::try_from_slice(data.as_slice()) + .map_err(|e| Error(e.into()))?, + None => return Ok(false), + }; + // check that the signed root is not modified let signed_root_key = get_signed_root_key(); if keys_changed.contains(&signed_root_key) { - return Ok(false) + return Ok(false); } // check that the pending transfer (and only that) was added to the pool - let pending_key = get_pending_key(); - let pending_pre: HashSet = self.read_pre_value(&get_pending_key()) - .ok_or(eyre!("The bridge pool transfers are missing from storage"))?; - let pending_post: HashSet = self.read_post_value(&get_pending_key()) - .ok_or(eyre!("The bridge pool transfers are missing from storage"))?; - for item in pending_pre.symmetric_difference(&pending_post){ - if item != &transfer { + // TODO: This will change slightly when we merkelize the pool, + // but that will be a separate PR. + let pending_pre: HashSet = + self.read_pre_value(&get_pending_key()).ok_or(eyre!( + "The bridge pool transfers are missing from storage" + ))?; + let pending_post: HashSet = + self.read_post_value(&get_pending_key()).ok_or(eyre!( + "The bridge pool transfers are missing from storage" + ))?; + if !pending_post.contains(&transfer) { + return Ok(false); + } + for item in pending_pre.symmetric_difference(&pending_post) { + if item != &transfer { return Ok(false); } } // check that gas fees were put into escrow - if let Some(SignedAmount::Negative(amount)) = self.account_balance_delta(&transfer.gas_fee.payer) { + + // check that the correct amount was deducted from the fee payer + if let Some(SignedAmount::Negative(amount)) = + self.account_balance_delta(&transfer.gas_fee.payer) + { if amount != transfer.gas_fee.amount { return Ok(false); } } else { return Ok(false); } - // TODO: Check that correct amount was received in escrow - // TODO: Verify nonce + // check that the correct amount was credited to escrow + if let Some(SignedAmount::Positive(amount)) = + self.account_balance_delta(&BRIDGE_POOL_ADDRESS) + { + if amount != transfer.gas_fee.amount { + return Ok(false); + } + } else { + return Ok(false); + } + // TODO: Verify nonce? Ok(true) } } + +#[cfg(test)] +mod test_bridge_pool_vp { + use std::env::temp_dir; + + use borsh::{BorshDeserialize, BorshSerialize}; + + use super::*; + use crate::ledger::gas::VpGasMeter; + use crate::ledger::storage::mockdb::MockDB; + use crate::ledger::storage::write_log::WriteLog; + use crate::ledger::storage::{Sha256Hasher, Storage}; + use crate::proto::Tx; + use crate::types::chain::ChainId; + use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use crate::types::ethereum_events::EthAddress; + use crate::types::hash::Hash; + use crate::types::key::{common, ed25519, SecretKey, SigScheme}; + use crate::vm::wasm::VpCache; + use crate::vm::WasmCacheRwAccess; + + /// The amount of NAM Bertha has + const BERTHA_WEALTH: u64 = 1_000_000; + const ESCROWED_AMOUNT: u64 = 1_000; + const GAS_FEE: u64 = 100; + + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + + fn bertha_keypair() -> common::SecretKey { + // generated from + // [`namada::types::key::ed25519::gen_keypair`] + let bytes = [ + 240, 3, 224, 69, 201, 148, 60, 53, 112, 79, 80, 107, 101, 127, 186, + 6, 176, 162, 113, 224, 62, 8, 183, 187, 124, 234, 244, 251, 92, 36, + 119, 243, + ]; + let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); + ed_sk.try_to_sk().unwrap() + } + + /// The bridge pool at the beginning of all tests + fn initial_pool() -> HashSet { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + HashSet::::from([transfer]) + } + + /// Create a new storage + fn new_writelog() -> WriteLog { + let mut writelog = WriteLog::default(); + // setup the bridge pool storage + writelog + .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) + .unwrap(); + + writelog + .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .unwrap(); + let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let amount: Amount = ESCROWED_AMOUNT.into(); + writelog + .write(&escrow_key, amount.try_to_vec().unwrap()) + .unwrap(); + + // setup a user with a balance + let bertha_account_key = balance_key(&xan(), &bertha_address()); + let bertha_wealth: Amount = BERTHA_WEALTH.into(); + writelog + .write(&bertha_account_key, bertha_wealth.try_to_vec().unwrap()) + .unwrap(); + writelog.commit_tx(); + writelog + } + + /// Setup a ctx for running native vps + fn setup_ctx<'a>( + tx: &'a Tx, + storage: &'a Storage, + write_log: &'a WriteLog, + ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { + Ctx::new( + storage, + &write_log, + tx, + VpGasMeter::new(0u64), + VpCache::new(temp_dir(), 100usize), + ) + } + + /// Helper function that tests various ways gas can be escrowed, + /// either correctly or incorrectly, is handled appropriately + fn assert_bidge_pool( + payer_delta: SignedAmount, + escrow_delta: SignedAmount, + insert_transfer: F, + expect: bool, + ) where + F: FnOnce( + PendingTransfer, + HashSet, + ) -> HashSet, + { + // setup + let mut write_log = new_writelog(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + // change the payers account + let bertha_account_key = balance_key(&xan(), &bertha_address()); + let new_bertha_balance = match payer_delta { + SignedAmount::Positive(amount) => { + Amount::from(BERTHA_WEALTH) + amount + } + SignedAmount::Negative(amount) => { + Amount::from(BERTHA_WEALTH) - amount + } + } + .try_to_vec() + .expect("Test failed"); + write_log + .write(&bertha_account_key, new_bertha_balance) + .expect("Test failed"); + // change the escrow account + let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let new_escrow_balance = match escrow_delta { + SignedAmount::Positive(amount) => { + Amount::from(ESCROWED_AMOUNT) + amount + } + SignedAmount::Negative(amount) => { + Amount::from(ESCROWED_AMOUNT) - amount + } + } + .try_to_vec() + .expect("Test failed"); + write_log + .write(&escrow, new_escrow_balance) + .expect("Test failed"); + + // add transfer to pool + let pool = insert_transfer(transfer.clone(), initial_pool()); + write_log + .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let keys_changed = BTreeSet::default(); + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert_eq!(res, expect); + } + + /// Test adding a transfer to the pool and escrowing gas passes vp + #[test] + fn test_happy_flow() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + true, + ); + } + + /// Test that if the balance for the gas payer + /// was not correctly adjusted, reject + #[test] + fn test_incorrect_gas_withdrawn() { + assert_bidge_pool( + SignedAmount::Negative(10.into()), + SignedAmount::Positive(GAS_FEE.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + false, + ); + } + + /// Test that if the gas payer's balance + /// does not decrease, we reject the tx + #[test] + fn test_payer_balance_must_decrease() { + assert_bidge_pool( + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + false, + ); + } + + /// Test that if the gas amount escrowed is incorrect, + /// the tx is rejected + #[test] + fn test_incorrect_gas_deposited() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(10.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + false, + ); + } + + /// Test that the amount of gas escrowed increases, + /// otherwise the tx is rejected. + #[test] + fn test_escrowed_gas_must_increase() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Negative(GAS_FEE.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + false, + ); + } + + /// Test that if a transaction is removed from + /// the pool, the tx is rejected. + #[test] + fn test_remove_transfer_rejected() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |transfer, _pool| HashSet::from([transfer]), + false, + ); + } + + /// Test that if the transfer was not added to the + /// pool, the vp rejects + #[test] + fn test_not_adding_transfer_rejected() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |_transfer, pool| pool, + false, + ); + } + + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. + #[test] + fn test_add_wrong_transfer() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |_transfer, pool| { + let mut pool = pool; + let wrong_transfer = + initial_pool().into_iter().next().expect("Test failed"); + pool.insert(wrong_transfer); + pool + }, + false, + ); + } + + /// Test that no tx may alter the storage containing + /// the signed merkle root. + #[test] + fn test_signed_merkle_root_changes_rejected() { + // setup + let mut write_log = new_writelog(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + // change the payers account + let bertha_account_key = balance_key(&xan(), &bertha_address()); + let new_bertha_balance = Amount::from(BERTHA_WEALTH - GAS_FEE) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&bertha_account_key, new_bertha_balance) + .expect("Test failed"); + + // change the escrow account + let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let new_escrow_balance = Amount::from(ESCROWED_AMOUNT + GAS_FEE) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&escrow, new_escrow_balance) + .expect("Test failed"); + + // add transfer to pool + let mut pool = initial_pool(); + pool.insert(transfer.clone()); + write_log + .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + // inform the vp that the merkle root changed + let keys_changed = BTreeSet::from([get_signed_root_key()]); + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert_eq!(res, false); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7f9bb61deb..73a5d0b382 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -1,8 +1,15 @@ +//! Tools for accessing the storage subspaces of the Ethereum +//! bridge pool + use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{DbKeySeg, Key}; -pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); +/// The main address of the Ethereum bridge pool +pub const BRIDGE_POOL_ADDRESS: Address = + Address::Internal(InternalAddress::EthBridgePool); +/// Sub-segmnet for getting the contents of the pool const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; +/// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; /// Get the storage key for the transfers in the pool @@ -15,7 +22,6 @@ pub fn get_pending_key() -> Key { } } - /// Get the storage key for the root of the Merkle tree /// containing the transfers in the pool pub fn get_signed_root_key() -> Key { @@ -25,4 +31,4 @@ pub fn get_signed_root_key() -> Key { DbKeySeg::StringSeg(SIGNED_ROOT_SEG.into()), ], } -} \ No newline at end of file +} diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index cb0b1f6eb3..7377e423d0 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -6,7 +6,6 @@ pub mod wrapped_erc20s; use super::ADDRESS; use crate::types::storage::{Key, KeySeg}; - /// Key prefix for the storage subspace pub fn prefix() -> Key { Key::from(ADDRESS.to_db_key()) diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 62eda4993d..c02c9fef82 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,3 +1,5 @@ +//! The necessary type definitions for the contents of the +//! Ethereum bridge pool use borsh::{BorshDeserialize, BorshSerialize}; use crate::types::address::Address; @@ -6,7 +8,16 @@ use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. -#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, +)] pub struct TransferToEthereum { /// The type of token pub asset: EthAddress, @@ -20,22 +31,40 @@ pub struct TransferToEthereum { /// A transfer message to Ethereum sitting in the /// bridge pool, waiting to be relayed -#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, +)] pub struct PendingTransfer { /// The message to send to Ethereum to pub transfer: TransferToEthereum, /// The amount of gas fees (in NAM) /// paid by the user sending this transfer - pub gas_fee: GasFee + pub gas_fee: GasFee, } /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. -#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] +#[derive( + Debug, + Clone, + Hash, + PartialOrd, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, +)] pub struct GasFee { /// The amount of fess (in NAM) pub amount: Amount, /// The account of fee payer. pub payer: Address, -} \ No newline at end of file +} diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index df4df44e8c..869b4a4f14 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -14,6 +14,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Hash, PartialEq, Eq, PartialOrd, diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index fabae14116..4f491d0a99 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -3,8 +3,8 @@ pub mod address; pub mod chain; pub mod dylib; -pub mod ethereum_events; pub mod eth_bridge_pool; +pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..41ea097c35 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -16,6 +16,7 @@ pub mod proof_of_stake; pub mod token; pub mod tx_prelude { + pub use namada::ledger::eth_bridge::storage::bridge_pool; pub use namada::ledger::governance::storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; diff --git a/wasm/checksums.json b/wasm/checksums.json index a337a7b1a8..a64112ef6c 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { - "tx_bond.wasm": "tx_bond.e0cf6b4c7cae7606f597f66a2b467ed44ab817744471554650e59c1504e1cfd8.wasm", - "tx_from_intent.wasm": "tx_from_intent.2190adc094be799ff1c8383e4b57c7d2ab5da328e845f8f3095cce4ed8b0041b.wasm", - "tx_ibc.wasm": "tx_ibc.014a3a93980f04147826f1e568551b1127de763e6c07480990db1c240877141b.wasm", - "tx_init_account.wasm": "tx_init_account.f4eceee710c20f402a9984338294a87ca5b33b45ac3e4c2229550c2a00597230.wasm", - "tx_init_nft.wasm": "tx_init_nft.809d0b46379ea9ea4cd7cd026e52ac7ad25094b090976a748a349691026db95b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b6b1014750b667bca57d82979c2295e99d0d3ab3723301029dcaa4b125ed401a.wasm", - "tx_init_validator.wasm": "tx_init_validator.da09c65fad79490ededfbf9ebc75df6626031f36f62791dc4e092743c91914aa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.07d3118e626ca59c95571ccf00e24265570388b66f8e67228b109b7c095653a7.wasm", - "tx_transfer.wasm": "tx_transfer.e84e5b4d77c81d2d6fe248d2c3b5ae0bb6d5fb7ef13da5e62e4b7ac7764fd80a.wasm", - "tx_unbond.wasm": "tx_unbond.35bb3196fd77c138541507d139cb68ca0f752df6cad0fb22db4a00bc64471771.wasm", - "tx_update_vp.wasm": "tx_update_vp.f73a121138f8b9d2fa0a50ae0b1dfb0b69ca4fd6559f5600aec61cd2aeece960.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.66f3c60eb109f2c631c2ee5f5fb47d73a086fa2a786b2ff63907e4e95bd79fcf.wasm", - "tx_withdraw.wasm": "tx_withdraw.a630427bcb03ec5cbd96b85ef86f79f85a5d1cff12f6b10308a9cb006ac95533.wasm", - "vp_nft.wasm": "vp_nft.bd7840f82f1ad27546266c01884ef69e81bd9e365cee53c8b013339e395ce392.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.feb9eec34cc11bd2233281a199e78d1bc284ce9eb4465198f483c5eb8075d8ab.wasm", - "vp_token.wasm": "vp_token.07f6e0b8a161125c7c1ed3356694a28e340332be1674e1d561802ba213736417.wasm", - "vp_user.wasm": "vp_user.b856d1a609021e0a4728ecd961268840eddff938fd67d09b648f31fc6422bf5a.wasm" + "tx_bond.wasm": "tx_bond.2542b95c73e40436e04c912c0200f4c21e4770fe6da416d06089b1aeeefcd83b.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_from_intent.wasm": "tx_from_intent.0dedc43aba5bbf8b9c279e31a8aaa396400ce0f9e39be719dd3e6ce21c1e4fad.wasm", + "tx_ibc.wasm": "tx_ibc.42bd027579e80cc8351414dadd0f30a66b8548a57681b533517ddb454be2ae52.wasm", + "tx_init_account.wasm": "tx_init_account.874b3be0c1b9e543c9e2464807d118ffb95377cc2cdc9cb97e870a27af000951.wasm", + "tx_init_nft.wasm": "tx_init_nft.3f6cb8831665fa5bf2301faea863f0276568e444cdf380397f067941175f6add.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f56156ef477003bedadef9a732b0207394ce57ed077f54a42cb97914bb702d54.wasm", + "tx_init_validator.wasm": "tx_init_validator.45675f866b60420d165706c9374d95bc288a60b7f4cfb26917ae4c49aeadfdcc.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1165846939711e007c79fd2178ab8e75fcf1f89be4ff15070375fefe4bbe7856.wasm", + "tx_transfer.wasm": "tx_transfer.24fc080fbbf4fcbf0a2deab0c42a99409f3ca0f71a4912e6ce1dd9711f591b43.wasm", + "tx_unbond.wasm": "tx_unbond.d876d56f8ebe9deaed6037746996b8ed8b6520f028939311719061a87b544624.wasm", + "tx_update_vp.wasm": "tx_update_vp.6a1a71536e876be3efd30ab8fb6d989340f8a96c3d913a77eaa6b403fa4a1f92.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.cabebda2fd0d7e280f09b5630014ca8a76991f57f1b289dc443aa26cac11b615.wasm", + "tx_withdraw.wasm": "tx_withdraw.b10ed3413a96fd0b97d528b347c063164ce7ea7aee5293a68518daf29c5d7438.wasm", + "vp_nft.wasm": "vp_nft.82180d7d7f81ddd78fa993541a012a87755c0a7835898b853b6b840dfd02670f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.96c122828273a95d8ef380a4ace6c634b91a18fccc7c2d2dbde6fc324b7519d0.wasm", + "vp_token.wasm": "vp_token.d81e5f48b4b176f3c4a8613584d8f546afb4c84387f26031306f91d2f3a6b110.wasm", + "vp_user.wasm": "vp_user.a9abbc44cf83fe7b318db81c29b3b13c2fa7b0aea10a4d2699119bdc6197c154.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 4e9c0f1cae..12fe3c72be 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -13,6 +13,7 @@ crate-type = ["cdylib"] # Newly added wasms should also be added into the Makefile `$(wasms)` list. [features] tx_bond = ["namada_tx_prelude"] +tx_bridge_pool = ["namada_tx_prelude"] tx_from_intent = ["namada_tx_prelude"] tx_ibc = ["namada_tx_prelude"] tx_init_account = ["namada_tx_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 39562a4a1e..f09c09c3c5 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -6,6 +6,7 @@ nightly := $(shell cat ../../rust-nightly-version) # All the wasms that can be built from this source, switched via Cargo features # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_bond +wasms := tx_bridge_pool wasms += tx_from_intent wasms += tx_ibc wasms += tx_init_account diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs new file mode 100644 index 0000000000..bed8e3820e --- /dev/null +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -0,0 +1,27 @@ +//! A tx for adding a transfer request across the Ethereum bridge +//! into the bridge pool. +use std::collections::HashSet; + +use borsh::{BorshDeserialize, BorshSerialize}; +use eth_bridge_pool::{GasFee, PendingTransfer}; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(tx_data: Vec) { + let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); + let transfer = PendingTransfer::try_from_slice( + &signed.data.unwrap()[..] + ) + .unwrap(); + // pay the gas fees + let GasFee { + amount, + ref payer, + } = transfer.gas_fees; + token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); + // add transfer into the pool + let pending_key = bridge_pool::get_pending_key(); + let mut pending: HashSet = read(&pending_key).unwrap(); + pending.insert(transfer); + write(pending_key, pending.try_to_vec().unwrap()); +} \ No newline at end of file From 3eba64ba8107467255ba1bc80db01abf0ccfdba2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 5 Sep 2022 14:47:49 +0000 Subject: [PATCH 0565/1995] 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 4d5f3196e2..3f3ec99d81 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.ca09fef16b5566de27ed431899e0efe9050623371f481369b68af32676c850b7.wasm", - "tx_from_intent.wasm": "tx_from_intent.6be622fab8082f4be8180c212a271d7e103d003526c7684b335cbbdbf0329ad1.wasm", - "tx_ibc.wasm": "tx_ibc.ef07534b7cf30af60ebc91f53bab5796647e1fd97cd2e096a60c3e064340abbf.wasm", - "tx_init_account.wasm": "tx_init_account.36f85e33e85816c73d24fdae42cef7c80309de05460e78ac5a78024b54d9c90d.wasm", - "tx_init_nft.wasm": "tx_init_nft.6e156e65bc19858ade99f91705b475fb9ab9c2641d23cf22db85958cc626146d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bba11610d204f103585016275d96c53c8d7f53d13853def667efa9c245b8b759.wasm", - "tx_init_validator.wasm": "tx_init_validator.90c115e298fb7906c83eed0e965c84ce4657a163d828b1a676f231f04cebea2a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.86e54bbefb7b6bc4f189df15943f4b7a9247fa6faf30ebf31c8b450bacae6e08.wasm", - "tx_transfer.wasm": "tx_transfer.daef631bcc3ceda491dafdea95afa9d912e891c7a166fcd45b33febdac1224c7.wasm", - "tx_unbond.wasm": "tx_unbond.b41aadf4b8f1f1b9188de26a54d0cfc1097a69d2e3632548144af46efe988547.wasm", - "tx_update_vp.wasm": "tx_update_vp.9a68c44fc97171a8d2218df2b1267dcf9faffb4b12393f72c4a7cc726192a351.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7053bdd44c9996c3b22d8ef65747e019328f334ed58016f7ff93d0f61d76331d.wasm", - "tx_withdraw.wasm": "tx_withdraw.9b1db13df08924f6d94c6a5e982f52522e160b6c57078f9165f7aff8877abade.wasm", - "vp_nft.wasm": "vp_nft.08c41172486de46d40e1fe4e38d742f740fc2c91929e183ec69b091907665068.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1598a5c1a78fc1df8e04c16a247a86b44e003954edc8594f4c73d3d226c1bb6c.wasm", - "vp_token.wasm": "vp_token.956607c8a4199be635a2b27799be5f78ae34dab23654c6f8b0ad36501ae740d3.wasm", - "vp_user.wasm": "vp_user.251d6f05c0a51dad915ccc11606206fdf1edb897ddcb8ce0a632d7c442372b65.wasm" + "tx_bond.wasm": "tx_bond.456738f85e02947895d1a695295584904b6fa432c0b231a7f58b0bc2eeb0206b.wasm", + "tx_from_intent.wasm": "tx_from_intent.b04f24f39ec2c013ca0ec67e8d9e15fe141358491df09231e57f26e0a86eaafd.wasm", + "tx_ibc.wasm": "tx_ibc.eee2a2020711125f082580a21795bd29bce6b44cfc5e398e7d9b571aea0bb644.wasm", + "tx_init_account.wasm": "tx_init_account.1d9032e3aa3b7a693b5100fef4677c9db089bf641c73d0bb86c3dc84744618da.wasm", + "tx_init_nft.wasm": "tx_init_nft.b795244c50ddb8d94331dd9b92afba88c151f9ab255bb849e705d28203c55481.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.29281f089a7feb6428de6d3beb47eb8a8cd0d2bbad66659e8c34febfbbe1d5d3.wasm", + "tx_init_validator.wasm": "tx_init_validator.232e6d66fecc993acf05d4049d61fc6f30208786bf15cbe490b4f508dca53331.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.6c955afebc58e82526a49f264d71001cda5121f35065461af2dc37cdd414844a.wasm", + "tx_transfer.wasm": "tx_transfer.2670a6446294393b21416fdadf1d4f7a21c2954bb5d2ae2766c2502ad2e565c2.wasm", + "tx_unbond.wasm": "tx_unbond.5617adea97b1c93854e4e2a4662cfe4f8d7d40f38db23b7e25bcfdcf8a14f271.wasm", + "tx_update_vp.wasm": "tx_update_vp.6f3ba15ee0bdb0ede2be98379c7413b462f271cd01c03fe5bbdad23fa9cb3f3e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d8cc675a01f82cc065aa45ceee51c1b069aaa2dc044b417bc259b15cfc8f78b2.wasm", + "tx_withdraw.wasm": "tx_withdraw.e2bb69661cf9f9f4601cfb188bd8ddb3137ae7e51d83125de9f1b867075ab7ea.wasm", + "vp_nft.wasm": "vp_nft.b9e2900e12ecdeb96b9ae31f37a2cf01c15ca22534d8e9127c219cb743d94f90.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1110ac3801811e7a1841acffc6cd919fcc35a07133015bd8c30f1fff93da44ea.wasm", + "vp_token.wasm": "vp_token.71e39786e9f21dd245fc30af7c5014472ac7b2f9a5bf8c8badad082753643a9d.wasm", + "vp_user.wasm": "vp_user.6583b04dca969fc58e3c9664a90610e303ba20c18f5249235c3eb75be05281b0.wasm" } \ No newline at end of file From fb4398dcecb1ad4ccd0711ed78d38c8877bdda25 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 5 Sep 2022 17:14:31 +0200 Subject: [PATCH 0566/1995] [chore]: Cleanup up tests. Clippy and formatting --- apps/src/lib/node/ledger/protocol/mod.rs | 11 ++++ .../src/ledger/eth_bridge/bridge_pool_vp.rs | 60 +++++++++++-------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 1bfe51ddc2..4bf055a01f 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -2,6 +2,7 @@ use std::collections::BTreeSet; use std::panic; +use namada::ledger::eth_bridge::bridge_pool_vp::BridgePoolVp; use namada::ledger::eth_bridge::vp::EthBridge; use namada::ledger::gas::{self, BlockGasMeter, VpGasMeter}; use namada::ledger::governance::GovernanceVp; @@ -57,6 +58,8 @@ pub enum Error { TreasuryNativeVpError(namada::ledger::treasury::Error), #[error("Ethereum bridge native VP error: {0}")] EthBridgeNativeVpError(namada::ledger::eth_bridge::vp::Error), + #[error("Ethereum bridge pool native VP error: {0}")] + BridgePoolNativeVpError(namada::ledger::eth_bridge::bridge_pool_vp::Error), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), } @@ -451,6 +454,14 @@ where gas_meter = bridge.ctx.gas_meter.into_inner(); result } + InternalAddress::EthBridgePool => { + let bridge_pool = BridgePoolVp { ctx }; + let result = bridge_pool + .validate_tx(tx_data, &keys_changed, &verifiers) + .map_err(Error::BridgePoolNativeVpError); + gas_meter = bridge_pool.ctx.gas_meter.into_inner(); + result + } }; accepted diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3e810bb89b..009ff73403 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -105,7 +105,7 @@ where "Validity predicate triggered", ); let signed: SignedTxData = - BorshDeserialize::try_from_slice(&tx_data[..]) + BorshDeserialize::try_from_slice(tx_data) .map_err(|e| Error(e.into()))?; let transfer: PendingTransfer = match signed.data { @@ -263,7 +263,7 @@ mod test_bridge_pool_vp { ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { Ctx::new( storage, - &write_log, + write_log, tx, VpGasMeter::new(0u64), VpCache::new(temp_dir(), 100usize), @@ -276,6 +276,7 @@ mod test_bridge_pool_vp { payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, + keys_changed: BTreeSet, expect: bool, ) where F: FnOnce( @@ -346,8 +347,6 @@ mod test_bridge_pool_vp { let vp = BridgePoolVp { ctx: setup_ctx(&tx, &storage, &write_log), }; - let keys_changed = BTreeSet::default(); - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); @@ -358,6 +357,7 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); + let verifiers = BTreeSet::default(); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) .expect("Test failed"); @@ -375,6 +375,7 @@ mod test_bridge_pool_vp { pool.insert(transfer); pool }, + BTreeSet::default(), true, ); } @@ -391,6 +392,7 @@ mod test_bridge_pool_vp { pool.insert(transfer); pool }, + BTreeSet::default(), false, ); } @@ -407,6 +409,7 @@ mod test_bridge_pool_vp { pool.insert(transfer); pool }, + BTreeSet::default(), false, ); } @@ -423,6 +426,7 @@ mod test_bridge_pool_vp { pool.insert(transfer); pool }, + BTreeSet::default(), false, ); } @@ -439,6 +443,7 @@ mod test_bridge_pool_vp { pool.insert(transfer); pool }, + BTreeSet::default(), false, ); } @@ -451,6 +456,7 @@ mod test_bridge_pool_vp { SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, _pool| HashSet::from([transfer]), + BTreeSet::default(), false, ); } @@ -463,6 +469,7 @@ mod test_bridge_pool_vp { SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |_transfer, pool| pool, + BTreeSet::default(), false, ); } @@ -481,6 +488,7 @@ mod test_bridge_pool_vp { pool.insert(wrong_transfer); pool }, + BTreeSet::default(), false, ); } @@ -489,6 +497,23 @@ mod test_bridge_pool_vp { /// the signed merkle root. #[test] fn test_signed_merkle_root_changes_rejected() { + assert_bidge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + |transfer, pool| { + let mut pool = pool; + pool.insert(transfer); + pool + }, + BTreeSet::from([get_signed_root_key()]), + false, + ); + } + + /// Test that a transfer added to the pool with zero gas fees + /// is rejected. + #[test] + fn test_zero_gas_fees_rejected() { // setup let mut write_log = new_writelog(); let storage = Storage::::open( @@ -507,27 +532,10 @@ mod test_bridge_pool_vp { nonce: 1u64.into(), }, gas_fee: GasFee { - amount: GAS_FEE.into(), + amount: 0.into(), payer: bertha_address(), }, }; - // change the payers account - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let new_bertha_balance = Amount::from(BERTHA_WEALTH - GAS_FEE) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&bertha_account_key, new_bertha_balance) - .expect("Test failed"); - - // change the escrow account - let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let new_escrow_balance = Amount::from(ESCROWED_AMOUNT + GAS_FEE) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&escrow, new_escrow_balance) - .expect("Test failed"); // add transfer to pool let mut pool = initial_pool(); @@ -541,7 +549,7 @@ mod test_bridge_pool_vp { ctx: setup_ctx(&tx, &storage, &write_log), }; // inform the vp that the merkle root changed - let keys_changed = BTreeSet::from([get_signed_root_key()]); + let keys_changed = BTreeSet::default(); let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -550,12 +558,12 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) .expect("Test failed"); - assert_eq!(res, false); + assert!(!res); } } From 8bbf043c2fb8221382e5644ea870cfc59a8d4140 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2022 17:57:25 +0100 Subject: [PATCH 0567/1995] Add option to run ledger with endpoint for submitting Ethereum events --- Cargo.lock | 107 ++++++++++++++++++ apps/Cargo.toml | 3 + apps/src/lib/config/ethereum.rs | 8 ++ apps/src/lib/node/ledger/ethereum_node/mod.rs | 4 - .../node/ledger/ethereum_node/test_tools.rs | 63 +++++++++++ apps/src/lib/node/ledger/mod.rs | 22 +++- 6 files changed, 201 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0eba89dc81..1be76213bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -933,6 +933,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.10.0" @@ -4332,6 +4342,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "quick-error 1.2.3", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "multistream-select" version = "0.10.3" @@ -4422,6 +4450,7 @@ dependencies = [ "borsh", "byte-unit", "byteorder", + "bytes 1.1.0", "cargo-watch", "clap 3.0.0-beta.2", "clarity", @@ -4491,6 +4520,7 @@ dependencies = [ "tracing 0.1.35", "tracing-log", "tracing-subscriber 0.3.11", + "warp", "web30", "websocket", "winapi 0.3.9", @@ -6311,6 +6341,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -7450,6 +7486,19 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log 0.4.17", + "pin-project 1.0.10", + "tokio", + "tungstenite 0.14.0", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -7810,6 +7859,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.1.0", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.9.8", + "thiserror", + "url 2.2.2", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.16.0" @@ -7829,6 +7897,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -8105,6 +8182,36 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes 1.1.0", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.19", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "multipart", + "percent-encoding 2.1.0", + "pin-project 1.0.10", + "scoped-tls", + "serde 1.0.137", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.10", + "tower-service", + "tracing 0.1.35", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ea081a27f6..94178b46b7 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -128,6 +128,9 @@ web30 = "0.19.1" websocket = "0.26.2" winapi = "0.3.9" +warp = "0.3.2" +bytes = "1.1.0" + [dev-dependencies] assert_matches = "1.5.0" namada = {path = "../shared", features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index 7f4e9fafe5..cdbc600c51 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -8,12 +8,20 @@ pub struct Config { /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use /// to listen for events from the Ethereum bridge smart contracts pub oracle_rpc_endpoint: String, + /// If this is set to `true`, then instead of the oracle listening for + /// events at a Ethereum JSON-RPC endpoint, an endpoint will be exposed by + /// the ledger for submission of Borsh-serialized + /// [`namada::types::ethereum_events::EthereumEvent`]s + #[cfg(not(feature = "eth-fullnode"))] + pub oracle_event_endpoint: bool, } impl Default for Config { fn default() -> Self { Self { oracle_rpc_endpoint: DEFAULT_ORACLE_RPC_ENDPOINT.to_owned(), + #[cfg(not(feature = "eth-fullnode"))] + oracle_event_endpoint: false, } } } diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 3ded71765b..d95d320a21 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -1,12 +1,8 @@ pub mod events; pub mod oracle; -#[cfg(feature = "eth-fullnode")] -pub use oracle::{run_oracle, Oracle}; pub mod test_tools; use std::ffi::OsString; -#[cfg(not(feature = "eth-fullnode"))] -pub use test_tools::mock_oracle::run_oracle; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 6c8a46065c..77dc163c4f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -47,6 +47,69 @@ pub mod mock_oracle { } } +#[cfg(not(feature = "eth-fullnode"))] +pub mod event_endpoint { + use borsh::BorshDeserialize; + use namada::types::ethereum_events::EthereumEvent; + use tokio::sync::mpsc::UnboundedSender; + + const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); + + /// The path to which Borsh-serialized Ethereum events should be submitted + const PATH: &str = "eth_events"; + + pub fn start_oracle( + sender: UnboundedSender, + ) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + use warp::Filter; + + tracing::info!( + ?ETHEREUM_EVENTS_ENDPOINT, + "Ethereum event endpoint is starting" + ); + + let eth_events = warp::post() + .and(warp::path(PATH)) + .and(warp::body::bytes()) + .map(move |bytes: bytes::Bytes| { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event) { + Ok(()) => warp::reply::with_status( + "OK", + warp::http::StatusCode::OK, + ), + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) + } + } + }); + + warp::serve(eth_events).run(ETHEREUM_EVENTS_ENDPOINT).await; + + tracing::info!( + ?ETHEREUM_EVENTS_ENDPOINT, + "Ethereum event endpoint is no longer running" + ); + }) + } +} + #[cfg(all(test, feature = "eth-fullnode"))] pub mod mock_web3_client { use std::cell::RefCell; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 16896fa143..2f4385bbb2 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -340,8 +340,26 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { res }); - let oracle = - ethereum_node::run_oracle(ethereum_url, eth_sender, abort_sender); + let oracle = { + #[cfg(not(feature = "eth-fullnode"))] + if config.ethereum.oracle_event_endpoint { + ethereum_node::test_tools::event_endpoint::start_oracle( + eth_sender, + ) + } else { + ethereum_node::test_tools::mock_oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ) + } + #[cfg(feature = "eth-fullnode")] + ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ) + }; // Shutdown ethereum_node via a message to ensure that the child process // is properly cleaned-up. From 058935d554ff2d8d8e4694fd3803235d7fe0c1be Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 5 Sep 2022 09:35:22 +0100 Subject: [PATCH 0568/1995] Add docstring for ethereum.rs --- apps/src/lib/config/ethereum.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index cdbc600c51..10b75c389b 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -1,3 +1,4 @@ +//! Configuration settings to do with the Ethereum bridge. use serde::{Deserialize, Serialize}; /// Default [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) endpoint used by the oracle From fd243f21c882468a72b3d88dc4b5b60ef9e207f9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 5 Sep 2022 09:37:31 +0100 Subject: [PATCH 0569/1995] Add import for EthereumEvent for use in docstring --- apps/src/lib/config/ethereum.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index 10b75c389b..db48f8ef45 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -1,4 +1,6 @@ //! Configuration settings to do with the Ethereum bridge. +#[allow(unused_imports)] +use namada::types::ethereum_events::EthereumEvent; use serde::{Deserialize, Serialize}; /// Default [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) endpoint used by the oracle @@ -12,7 +14,7 @@ pub struct Config { /// If this is set to `true`, then instead of the oracle listening for /// events at a Ethereum JSON-RPC endpoint, an endpoint will be exposed by /// the ledger for submission of Borsh-serialized - /// [`namada::types::ethereum_events::EthereumEvent`]s + /// [`EthereumEvent`]s #[cfg(not(feature = "eth-fullnode"))] pub oracle_event_endpoint: bool, } From 61dee075cb27da9a0d5155722e473b357f2623c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 08:59:53 +0100 Subject: [PATCH 0570/1995] Remove voting power checks wiht abciplus enabled --- .../lib/node/ledger/shell/process_proposal.rs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index bf439e6bfa..e269e36dff 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -3,6 +3,7 @@ use namada::ledger::pos::types::VotingPower; use namada::types::transaction::protocol::ProtocolTxType; +#[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; use super::queries::{QueriesExt, SendValsetUpd}; @@ -136,7 +137,9 @@ where where I: Iterator>, { + #[cfg(feature = "abcipp")] let mut voting_power = FractionalVotingPower::default(); + #[cfg(feature = "abcipp")] let total_power = { let epoch = self.storage.get_epoch(self.storage.last_height); u64::from(self.storage.get_total_voting_power(epoch)) @@ -144,18 +147,22 @@ where if vote_extensions.all(|maybe_ext| { maybe_ext - .map(|power| { - voting_power += FractionalVotingPower::new( - u64::from(power), - total_power, - ) - .expect( - "The voting power we obtain from storage should \ - always be valid", - ); + .map(|_power| { + #[cfg(feature = "abcipp")] + { + voting_power += FractionalVotingPower::new( + u64::from(_power), + total_power, + ) + .expect( + "The voting power we obtain from storage should \ + always be valid", + ); + } }) .is_some() }) { + #[cfg(feature = "abcipp")] if voting_power > FractionalVotingPower::TWO_THIRDS { TxResult { code: ErrorCodes::Ok.into(), @@ -170,6 +177,14 @@ where .into(), } } + + #[cfg(not(feature = "abcipp"))] + { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process proposal accepted this transaction".into(), + } + } } else { TxResult { code: ErrorCodes::InvalidVoteExtension.into(), From 7c0fac31035a0ef8c41984396f92775170f3bbf6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Sep 2022 09:05:12 +0100 Subject: [PATCH 0571/1995] Rename: check_rejected_digest -> check_rejected_eth_events_digest --- .../lib/node/ledger/shell/process_proposal.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e269e36dff..ed8916f551 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -506,7 +506,7 @@ mod test_process_proposal { ); } - fn check_rejected_digest( + fn check_rejected_eth_events_digest( shell: &mut TestShell, vote_extension_digest: ethereum_events::VextDigest, protocol_key: common::SecretKey, @@ -582,7 +582,11 @@ mod test_process_proposal { }], } }; - check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); } /// Test that if a proposal contains Ethereum events with @@ -633,7 +637,11 @@ mod test_process_proposal { } }; #[cfg(feature = "abcipp")] - check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); #[cfg(not(feature = "abcipp"))] { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) @@ -699,7 +707,11 @@ mod test_process_proposal { }], } }; - check_rejected_digest(&mut shell, vote_extension_digest, protocol_key); + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); } /// Test that if a wrapper tx is not signed, it is rejected From 52a74ccfbfe7f1c086fecd693cd17413b0117eb7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 6 Sep 2022 10:09:54 +0000 Subject: [PATCH 0572/1995] [ci skip] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index a64112ef6c..5b6626ce1d 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.2542b95c73e40436e04c912c0200f4c21e4770fe6da416d06089b1aeeefcd83b.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.0dedc43aba5bbf8b9c279e31a8aaa396400ce0f9e39be719dd3e6ce21c1e4fad.wasm", - "tx_ibc.wasm": "tx_ibc.42bd027579e80cc8351414dadd0f30a66b8548a57681b533517ddb454be2ae52.wasm", - "tx_init_account.wasm": "tx_init_account.874b3be0c1b9e543c9e2464807d118ffb95377cc2cdc9cb97e870a27af000951.wasm", - "tx_init_nft.wasm": "tx_init_nft.3f6cb8831665fa5bf2301faea863f0276568e444cdf380397f067941175f6add.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f56156ef477003bedadef9a732b0207394ce57ed077f54a42cb97914bb702d54.wasm", - "tx_init_validator.wasm": "tx_init_validator.45675f866b60420d165706c9374d95bc288a60b7f4cfb26917ae4c49aeadfdcc.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.1165846939711e007c79fd2178ab8e75fcf1f89be4ff15070375fefe4bbe7856.wasm", - "tx_transfer.wasm": "tx_transfer.24fc080fbbf4fcbf0a2deab0c42a99409f3ca0f71a4912e6ce1dd9711f591b43.wasm", - "tx_unbond.wasm": "tx_unbond.d876d56f8ebe9deaed6037746996b8ed8b6520f028939311719061a87b544624.wasm", - "tx_update_vp.wasm": "tx_update_vp.6a1a71536e876be3efd30ab8fb6d989340f8a96c3d913a77eaa6b403fa4a1f92.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.cabebda2fd0d7e280f09b5630014ca8a76991f57f1b289dc443aa26cac11b615.wasm", - "tx_withdraw.wasm": "tx_withdraw.b10ed3413a96fd0b97d528b347c063164ce7ea7aee5293a68518daf29c5d7438.wasm", - "vp_nft.wasm": "vp_nft.82180d7d7f81ddd78fa993541a012a87755c0a7835898b853b6b840dfd02670f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.96c122828273a95d8ef380a4ace6c634b91a18fccc7c2d2dbde6fc324b7519d0.wasm", - "vp_token.wasm": "vp_token.d81e5f48b4b176f3c4a8613584d8f546afb4c84387f26031306f91d2f3a6b110.wasm", - "vp_user.wasm": "vp_user.a9abbc44cf83fe7b318db81c29b3b13c2fa7b0aea10a4d2699119bdc6197c154.wasm" + "tx_from_intent.wasm": "tx_from_intent.a757ac9b673838596e83bf33165928ed72785c34646dd6f8a051e7f8722af55e.wasm", + "tx_ibc.wasm": "tx_ibc.95a796794a2e9ff7fd207d5ae44bb15dfbdad4dbd9af9bb9821df7cccad2ac0c.wasm", + "tx_init_account.wasm": "tx_init_account.9cf8a7edd916f9f525b409c0b5dd6cb6ab3f0c56fcbfdbcd12aa85c93f18fa27.wasm", + "tx_init_nft.wasm": "tx_init_nft.5fd33b12eeaeee04071a5b5992a7ade35264911b0f6e58f7f73d3abd3db6dd5c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ec5fdcc9c1357d8b605d0a5332d2eeb0cf295ffbe01847beeeb1492caf877799.wasm", + "tx_init_validator.wasm": "tx_init_validator.1d3f64de435631ab8a22d8dda03335cc3ca985a9fef9321e5749e54042f70dd3.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.a7dc2a035ef126e37920373a334c6018b9f86a522af2271cf0cd520b01f7cd36.wasm", + "tx_transfer.wasm": "tx_transfer.dce689ac8924baaad0b214a76b4350768e24fafd83e36c0193f125a1b907c8b3.wasm", + "tx_unbond.wasm": "tx_unbond.97f20427841154ca5723fea364aa808cf169dce3cf7c3de81d5b8c539b06b5bf.wasm", + "tx_update_vp.wasm": "tx_update_vp.dd36fe5c547e7b6d4131b4160c71569259f4b4457b3ff78c737ff5fe9ba32f6d.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.b03ec390390b7242904aa00df28a8f35ffbd5d5e51ae0c17929f7498087e447f.wasm", + "tx_withdraw.wasm": "tx_withdraw.fc0b0a43640033a8e8576247cde5ee518c99a09a4da9e072286d8279df67d4b8.wasm", + "vp_nft.wasm": "vp_nft.b98d17a18cf98b43a84083870d1be0502260e58ff08db8ca98c102b46f5ec3df.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.45b4877bac5c6a1341751f4c1059d1b2ef2f9387f0cf381cbed564eafd4583e8.wasm", + "vp_token.wasm": "vp_token.a6f531a0d93cba4de43f38af271a439b3b3ad1ef1c5771519126052dadc68fd1.wasm", + "vp_user.wasm": "vp_user.d95fa274daebe8816b3dd508968d83e64ecaa1b1b1669c95769beea259d82e48.wasm" } \ No newline at end of file From a0e3bf955577c07267e05862a9fd8bc0e20083ba Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 12:41:21 +0200 Subject: [PATCH 0573/1995] [fix]: Small cleanups from PR review. Added a couple of helper methods to Ctx --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 72 ++++++++----------- shared/src/ledger/native_vp.rs | 27 +++++++ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 009ff73403..0b2b76a2dd 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1,4 +1,14 @@ -//! Validity predicate for the Ethereum bridge +//! Validity predicate for the Ethereum bridge pool +//! +//! This pool holds user initiated transfers of value from +//! Namada to Ethereum. It is to act like a mempool: users +//! add in their desired transfers and their chosen amount +//! of NAM to cover Ethereum side gas fees. These transfers +//! can be relayed in batches along with Merkle proofs. +//! +//! This VP checks that additions to the pool are handled +//! correctly. This means that the appropriate data is +//! added to the pool and gas fees are submitted appropriately. use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; @@ -44,36 +54,12 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - /// Helper function for reading values from storage - fn read_post_value(&self, key: &Key) -> Option - where - T: BorshDeserialize, - { - if let Ok(Some(bytes)) = self.ctx.read_post(key) { - ::try_from_slice(bytes.as_slice()).ok() - } else { - None - } - } - - /// Helper function for reading values from storage - fn read_pre_value(&self, key: &Key) -> Option - where - T: BorshDeserialize, - { - if let Ok(Some(bytes)) = self.ctx.read_pre(key) { - ::try_from_slice(bytes.as_slice()).ok() - } else { - None - } - } - /// Get the change in the balance of an account /// associated with an address fn account_balance_delta(&self, address: &Address) -> Option { let account_key = balance_key(&xan(), address); - let before: Amount = self.read_pre_value(&account_key)?; - let after: Amount = self.read_post_value(&account_key)?; + let before: Amount = self.ctx.read_pre_value(&account_key)?; + let after: Amount = self.ctx.read_post_value(&account_key)?; if before > after { Some(SignedAmount::Negative(before - after)) } else { @@ -102,11 +88,10 @@ where tx_data_len = tx_data.len(), keys_changed_len = keys_changed.len(), verifiers_len = _verifiers.len(), - "Validity predicate triggered", + "Ethereum Bridge Pool VP triggered", ); - let signed: SignedTxData = - BorshDeserialize::try_from_slice(tx_data) - .map_err(|e| Error(e.into()))?; + let signed: SignedTxData = BorshDeserialize::try_from_slice(tx_data) + .map_err(|e| Error(e.into()))?; let transfer: PendingTransfer = match signed.data { Some(data) => BorshDeserialize::try_from_slice(data.as_slice()) @@ -122,12 +107,13 @@ where // check that the pending transfer (and only that) was added to the pool // TODO: This will change slightly when we merkelize the pool, // but that will be a separate PR. + let pending_key = get_pending_key(); let pending_pre: HashSet = - self.read_pre_value(&get_pending_key()).ok_or(eyre!( + self.ctx.read_pre_value(&pending_key).ok_or(eyre!( "The bridge pool transfers are missing from storage" ))?; let pending_post: HashSet = - self.read_post_value(&get_pending_key()).ok_or(eyre!( + self.ctx.read_post_value(&pending_key).ok_or(eyre!( "The bridge pool transfers are missing from storage" ))?; if !pending_post.contains(&transfer) { @@ -272,7 +258,7 @@ mod test_bridge_pool_vp { /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately - fn assert_bidge_pool( + fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, @@ -367,7 +353,7 @@ mod test_bridge_pool_vp { /// Test adding a transfer to the pool and escrowing gas passes vp #[test] fn test_happy_flow() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, pool| { @@ -384,7 +370,7 @@ mod test_bridge_pool_vp { /// was not correctly adjusted, reject #[test] fn test_incorrect_gas_withdrawn() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, pool| { @@ -401,7 +387,7 @@ mod test_bridge_pool_vp { /// does not decrease, we reject the tx #[test] fn test_payer_balance_must_decrease() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, pool| { @@ -418,7 +404,7 @@ mod test_bridge_pool_vp { /// the tx is rejected #[test] fn test_incorrect_gas_deposited() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), |transfer, pool| { @@ -435,7 +421,7 @@ mod test_bridge_pool_vp { /// otherwise the tx is rejected. #[test] fn test_escrowed_gas_must_increase() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), |transfer, pool| { @@ -452,7 +438,7 @@ mod test_bridge_pool_vp { /// the pool, the tx is rejected. #[test] fn test_remove_transfer_rejected() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, _pool| HashSet::from([transfer]), @@ -465,7 +451,7 @@ mod test_bridge_pool_vp { /// pool, the vp rejects #[test] fn test_not_adding_transfer_rejected() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |_transfer, pool| pool, @@ -478,7 +464,7 @@ mod test_bridge_pool_vp { /// to the pool, it is rejected. #[test] fn test_add_wrong_transfer() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |_transfer, pool| { @@ -497,7 +483,7 @@ mod test_bridge_pool_vp { /// the signed merkle root. #[test] fn test_signed_merkle_root_changes_rejected() { - assert_bidge_pool( + assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), |transfer, pool| { diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index faad84a0f4..252ac8a49a 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 borsh::BorshDeserialize; use thiserror::Error; use crate::ledger::gas::VpGasMeter; @@ -131,6 +132,32 @@ where .map_err(Error::ContextError) } + /// Helper function. After reading posterior state, + /// borsh deserialize to specified type + pub fn read_post_value(&self, key: &Key) -> Option + where + T: BorshDeserialize, + { + if let Ok(Some(bytes)) = self.read_post(key) { + ::try_from_slice(bytes.as_slice()).ok() + } else { + None + } + } + + /// Helper function. After reading prior state, + /// borsh deserialize to specified type + pub fn read_pre_value(&self, key: &Key) -> Option + where + T: BorshDeserialize, + { + if let Ok(Some(bytes)) = self.read_pre(key) { + ::try_from_slice(bytes.as_slice()).ok() + } else { + None + } + } + /// 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>> { From ca560a6e2c55309aa32736b18cf2871f47d4fd0d Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 14:28:05 +0200 Subject: [PATCH 0574/1995] [feat]: Removed tm events log, put websocket back everywhere in client --- Cargo.lock | 31 -- apps/Cargo.toml | 1 - apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/tendermint_rpc_types.rs | 278 +++--------- .../lib/client/tendermint_websocket_client.rs | 412 ++++++++++++++++++ apps/src/lib/client/tm_jsonrpc_client.rs | 261 ----------- apps/src/lib/client/tx.rs | 117 ++--- apps/src/lib/node/ledger/tendermint_node.rs | 8 - 8 files changed, 532 insertions(+), 577 deletions(-) delete mode 100644 apps/src/lib/client/tm_jsonrpc_client.rs diff --git a/Cargo.lock b/Cargo.lock index ae1007b406..586699f576 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1541,36 +1541,6 @@ dependencies = [ "rand 0.7.3", ] -[[package]] -name = "curl" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.4.4", - "winapi 0.3.9", -] - -[[package]] -name = "curl-sys" -version = "0.4.55+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi 0.3.9", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -4354,7 +4324,6 @@ dependencies = [ "clarity", "color-eyre", "config", - "curl", "derivative", "directories", "ed25519-consensus", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 02c272b189..d99fd3ae15 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -80,7 +80,6 @@ clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default clarity = "0.5.1" color-eyre = "0.5.10" config = "0.11.0" -curl = "0.4.43" derivative = "2.2.0" directories = "4.0.1" ed25519-consensus = "1.2.0" diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index a3d2ddece9..3fff8d94ec 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -3,6 +3,5 @@ pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; mod tendermint_websocket_client; -mod tm_jsonrpc_client; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index ff185d2718..c8bca25cb5 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,30 +1,14 @@ +use std::convert::TryFrom; + use jsonpath_lib as jsonpath; use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; -use thiserror::Error; use crate::cli::safe_exit; -use crate::node::ledger::events::Attributes; - -/// Errors from interacting with Tendermint's jsonrpc endpoint -#[derive(Error, Debug)] -pub enum Error { - #[error("Invalid address given to JSON RPC client: {0}")] - Address(String), - #[error("Error in sending JSON RPC request to Tendermint")] - Send, - #[error("Received an error response from Tendermint: {0:?}")] - Rpc(crate::facade::tendermint_rpc::response_error::ResponseError), - #[error("Received malformed JSON response from Tendermint")] - MalformedJson, - #[error("Received an empty response from Tendermint")] - EmptyResponse, - #[error("Could not deserialize JSON response: {0}")] - Deserialize(serde_json::Error), - #[error("Could not find event for the given hash: {0}")] - NotFound(String), -} +use crate::node::ledger::events::{ + Attributes, Error, EventType as NamadaEventType, +}; /// Data needed for broadcasting a tx and /// monitoring its progress on chain @@ -55,6 +39,54 @@ pub struct TxResponse { } impl TxResponse { + /// Parse the JSON payload received from a subscription + /// + /// Searches for custom events emitted from the ledger and converts + /// them back to thin wrapper around a hashmap for further parsing. + pub fn parse( + json: serde_json::Value, + event_type: NamadaEventType, + tx_hash: &str, + ) -> Result, Error> { + let mut selector = jsonpath::selector(&json); + let mut event = { + match selector(&format!("$.events.[?(@.type=='{}')]", event_type)) + .unwrap() + .pop() + { + Some(event) => { + let attrs = Attributes::try_from(event)?; + match attrs.get("hash") { + Some(hash) if hash == tx_hash => attrs, + _ => return Ok(None), + } + } + _ => return Ok(None), + } + }; + let info = event.take("info").unwrap(); + let log = event.take("log").unwrap(); + let height = event.take("height").unwrap(); + let hash = event.take("hash").unwrap(); + let code = event.take("code").unwrap(); + let gas_used = + event.take("gas_used").unwrap_or_else(|| String::from("0")); + let initialized_accounts = event.take("initialized_accounts"); + let initialized_accounts = match initialized_accounts { + Some(values) => serde_json::from_str(&values).unwrap(), + _ => vec![], + }; + Ok(Some(TxResponse { + info, + log, + height, + hash, + code, + gas_used, + initialized_accounts, + })) + } + /// Find a tx with a given hash from the the websocket subscription /// to Tendermint events. pub fn find_tx(json: serde_json::Value, tx_hash: &str) -> Self { @@ -128,207 +160,3 @@ impl TxResponse { } } } - -mod params { - use std::convert::TryFrom; - use std::time::Duration; - - use serde::ser::SerializeTuple; - use serde::{Deserialize, Serializer}; - - use super::*; - use crate::facade::tendermint_rpc::query::Query; - - /// Opaque type for ordering events. Set by Tendermint - #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] - #[serde(transparent)] - pub struct Cursor(String); - - impl From for Cursor { - fn from(cursor: String) -> Self { - Cursor(cursor) - } - } - - /// Struct used for querying Tendermint's event logs - #[derive(Debug)] - pub struct EventParams { - /// The filter an event must satisfy in order to - /// be returned - pub filter: Query, - /// The maximum number of eligible results to return. - /// If zero or negative, the server will report a default number. - pub max_results: u64, - /// Return only items after this cursor. If empty, the limit is just - /// before the the beginning of the event log - pub after: Cursor, - /// Return only items before this cursor. If empty, the limit is just - /// after the head of the event log. - before: Cursor, - /// Wait for up to this long for events to be available. - pub wait_time: Duration, - } - - /// Struct to help serialize [`EventParams`] - #[derive(Serialize)] - struct Filter { - query: String, - } - - impl Serialize for EventParams { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut ser = serializer.serialize_tuple(5)?; - ser.serialize_element(&Filter { - query: self.filter.to_string(), - })?; - ser.serialize_element(&self.max_results)?; - ser.serialize_element(self.after.0.as_str())?; - ser.serialize_element(self.before.0.as_str())?; - ser.serialize_element(&self.wait_time.as_nanos())?; - ser.end() - } - } - - impl EventParams { - /// Initialize a new set of [`EventParams`] - pub fn new( - filter: Query, - max_results: u64, - wait_time: Duration, - ) -> Self { - Self { - filter, - max_results, - after: Default::default(), - before: Default::default(), - wait_time, - } - } - } - - /// A reply from Tendermint for events matching the given [`EventParams`] - #[derive(Serialize, Deserialize)] - pub struct EventReply { - /// The items matching the request parameters, from newest - /// to oldest, if any were available within the timeout. - pub items: Vec, - /// This is true if there is at least one older matching item - /// available in the log that was not returned. - #[allow(dead_code)] - more: bool, - /// The cursor of the oldest item in the log at the time of this reply, - /// or "" if the log is empty. - #[allow(dead_code)] - oldest: Cursor, - /// The cursor of the newest item in the log at the time of this reply, - /// or "" if the log is empty. - pub newest: Cursor, - } - - /// An event returned from Tendermint - #[derive(Debug, PartialEq, Serialize, Deserialize)] - pub struct EventItem { - /// this specifies where in the event log this event is - #[allow(dead_code)] - pub cursor: Cursor, - /// The event type - pub event: String, - /// The raw event value - pub data: EventData, - } - - /// Raw data of an event returned from Tendermint - #[derive(Debug, PartialEq, Serialize, Deserialize)] - pub struct EventData { - pub r#type: String, - pub value: serde_json::Value, - } - - /// Parse the JSON payload received from the `events` JSON-RPC - /// endpoint of Tendermint. - /// - /// 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. - pub fn parse(reply: EventReply, tx_hash: &str) -> Option { - let mut event = reply - .items - .iter() - .filter_map(|event| { - if event.event == *"NewBlockHeader" { - let events: Option> = - event.data.value.get("result_finalize_block").map( - |res| match res.get("events") { - Some(v) => serde_json::from_value(v.clone()) - .unwrap_or_default(), - None => vec![], - }, - ); - events - } else { - None - } - }) - .flatten() - .find_map(|attr| { - if let Ok(attrs) = Attributes::try_from(&attr) { - match attrs.get("hash") { - Some(hash) if hash == tx_hash => Some(attrs), - _ => None, - } - } else { - None - } - })?; - - let info = event.take("info").unwrap(); - let log = event.take("log").unwrap(); - let height = event.take("height").unwrap(); - let hash = event.take("hash").unwrap(); - let code = event.take("code").unwrap(); - let gas_used = - event.take("gas_used").unwrap_or_else(|| String::from("0")); - let initialized_accounts = event.take("initialized_accounts"); - let initialized_accounts = match initialized_accounts { - Some(values) => serde_json::from_str(&values).unwrap(), - _ => vec![], - }; - - Some(TxResponse { - info, - log, - height, - hash, - code, - gas_used, - initialized_accounts, - }) - } - - #[cfg(test)] - mod test_rpc_types { - use super::*; - use crate::facade::tendermint_rpc::query::EventType; - - /// Test that [`EventParams`] is serialized correctly - #[test] - fn test_serialize_event_params() { - let params = EventParams { - filter: Query::from(EventType::NewBlockHeader), - max_results: 5, - after: Cursor("16CCC798FB5F4670-0123".into()), - before: Default::default(), - wait_time: Duration::from_secs(59), - }; - assert_eq!( - serde_json::to_string(¶ms).expect("Test failed"), - r#"[{"query":"tm.event = 'NewBlockHeader'"},5,"16CCC798FB5F4670-0123","",59000000000]"# - ) - } - } -} - -pub use params::*; diff --git a/apps/src/lib/client/tendermint_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs index 6f70464e58..61bea1d990 100644 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ b/apps/src/lib/client/tendermint_websocket_client.rs @@ -12,6 +12,7 @@ use websocket::result::WebSocketError; use websocket::{ClientBuilder, Message, OwnedMessage}; use crate::facade::tendermint_config::net::Address; +use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, Error as RpcError, Request, Response, SimpleRequest, }; @@ -38,6 +39,10 @@ pub enum Error { MissingId, #[error("Connection timed out")] ConnectionTimeout, + #[error("Received malformed JSON from websocket: {0:?}")] + MalformedJson(crate::node::ledger::events::Error), + #[error("Event for transaction {0} was not received")] + MissingEvent(String), } type Json = serde_json::Value; @@ -170,6 +175,8 @@ impl Display for WebSocketAddress { } } +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 /// Furthermore, TendermintWebsocketClient must be `Send` since it will be @@ -177,8 +184,14 @@ impl Display for WebSocketAddress { type Websocket = Arc>>; type ResponseQueue = Arc>>; +struct Subscription { + id: String, + query: Query, +} + pub struct TendermintWebsocketClient { websocket: Websocket, + subscribed: Option, received_responses: ResponseQueue, connection_timeout: Duration, } @@ -196,6 +209,7 @@ impl TendermintWebsocketClient { { Ok(websocket) => Ok(Self { websocket: Arc::new(Mutex::new(websocket)), + subscribed: None, received_responses: Arc::new(Mutex::new(HashMap::new())), connection_timeout: connection_timeout .unwrap_or_else(|| Duration::new(300, 0)), @@ -208,8 +222,142 @@ impl TendermintWebsocketClient { pub fn close(&mut self) { // Even in the case of errors, this will be shutdown let _ = self.websocket.lock().unwrap().shutdown(); + self.subscribed = None; self.received_responses.lock().unwrap().clear(); } + + /// Subscribes to an event specified by the query argument. + 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 + 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 + 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. + 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] @@ -298,3 +446,267 @@ 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(test)] +mod test_tendermint_websocket_client { + use std::time::Duration; + + use namada::types::transaction::hash_tx as hash_tx_bytes; + use serde::{Deserialize, Serialize}; + use websocket::sync::Server; + use websocket::{Message, OwnedMessage}; + + use crate::client::tendermint_websocket_client::{ + TendermintWebsocketClient, WebSocketAddress, + }; + use crate::facade::tendermint::abci::transaction; + use crate::facade::tendermint_rpc::endpoint::abci_info::AbciInfo; + use crate::facade::tendermint_rpc::query::{EventType, Query}; + use crate::facade::tendermint_rpc::Client; + + #[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 deleted file mode 100644 index ccdf402577..0000000000 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ /dev/null @@ -1,261 +0,0 @@ -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 crate::client::tendermint_rpc_types::{ - parse, Error, EventParams, EventReply, TxResponse, -}; -use crate::facade::tendermint_config::net::Address as TendermintAddress; -use crate::facade::tendermint_rpc::query::Query; - -/// 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, -} - -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())), - } - } -} - -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, -} - -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, -} - -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); - -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, -} - -impl<'a> Deref for Client<'a> { - type Target = Easy2; - - 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> 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") - } - } - } - 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(); - } -} - -/// 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() -} - -#[cfg(test)] -mod test_rpc_types { - use serde_json::json; - - 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, - "result":{ - "items": [{ - "cursor":"16f1b066717b4261-0060", - "event":"NewRoundStep", - "data":{ - "type":"tendermint/event/RoundState", - "value":{ - "height":"17416", - "round":0, - "step":"RoundStepCommit" - } - } - }], - "more":true, - "oldest":"16f1b065029b23d0-0001", - "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" - }), - } - }] - ) - } -} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8f6f4413b6..ddf8aedf18 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -30,15 +30,15 @@ use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::{find_keypair, sign_tx}; -use crate::client::tendermint_rpc_types::{Error, TxBroadcastData, TxResponse}; +use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::client::tendermint_websocket_client::{ Error as WsError, TendermintWebsocketClient, WebSocketAddress, }; -use crate::client::tm_jsonrpc_client::{fetch_event, JsonRpcAddress}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::query::{EventType, Query}; use crate::facade::tendermint_rpc::{Client, HttpClient}; +use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; const ACCEPTED_QUERY_KEY: &str = "accepted.hash"; @@ -1131,7 +1131,7 @@ pub async fn broadcast_tx( address: TendermintAddress, to_broadcast: &TxBroadcastData, ) -> Result { - let (tx, wrapper_tx_hash, _decrypted_tx_hash) = match to_broadcast { + let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { TxBroadcastData::Wrapper { tx, wrapper_hash, @@ -1156,13 +1156,6 @@ pub async fn broadcast_tx( Some(websocket_timeout), )?; - #[cfg(not(feature = "ABCI"))] - let response = wrapper_tx_subscription - .broadcast_tx_sync(tx.to_bytes().into()) - .await - .map_err(|err| WsError::Response(format!("{:?}", err)))?; - - #[cfg(feature = "ABCI")] let response = wrapper_tx_subscription .broadcast_tx_sync(tx.to_bytes().into()) .await @@ -1176,7 +1169,7 @@ pub async fn broadcast_tx( // acceptance/application results later { println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); - println!("Inner transaction hash: {:?}", _decrypted_tx_hash); + println!("Inner transaction hash: {:?}", decrypted_tx_hash); } Ok(response) } else { @@ -1195,56 +1188,80 @@ pub async fn broadcast_tx( pub async fn submit_tx( address: TendermintAddress, to_broadcast: TxBroadcastData, -) -> Result { - // the data for finding the relevant events +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Wrapper { tx, wrapper_hash, decrypted_hash, } => (tx, wrapper_hash, decrypted_hash), - TxBroadcastData::DryRun(_) => { - panic!("Cannot broadcast a dry-run transaction") - } + _ => panic!("Cannot broadcast a dry-run transaction"), }; - let url = JsonRpcAddress::try_from(&address)?.to_string(); + let mut wrapper_tx_subscription = TendermintWebsocketClient::open( + WebSocketAddress::try_from(address.clone())?, + None, + )?; - // the filters for finding the relevant events - 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_str()); + // 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)?; + + // We also subscribe to the event emitted when the encrypted + // payload makes its way onto the blockchain + let mut decrypted_tx_subscription = { + let mut decrypted_tx_subscription = TendermintWebsocketClient::open( + WebSocketAddress::try_from(address.clone())?, + None, + )?; + let query = Query::from(EventType::NewBlock) + .and_eq(ACCEPTED_QUERY_KEY, decrypted_hash.as_str()); + decrypted_tx_subscription.subscribe(query)?; + decrypted_tx_subscription + }; - // broadcast the tx - if let Err(err) = broadcast_tx(address, &to_broadcast).await { - eprintln!("Encountered error while broadcasting transaction: {}", err); - safe_exit(1) - } + // Broadcast the supplied transaction + broadcast_tx(address, &to_broadcast).await?; - // get the event for the wrapper tx - let response = - fetch_event(&url, wrapper_query, wrapper_hash.as_str()).await?; - println!( - "Transaction accepted with result: {}", - serde_json::to_string_pretty(&response).unwrap() - ); + let parsed = { + let parsed = TxResponse::parse( + wrapper_tx_subscription.receive_response()?, + NamadaEventType::Accepted, + wrapper_hash, + ) + .map_err(WsError::MalformedJson)? + .ok_or_else(|| WsError::MissingEvent(wrapper_hash.clone()))?; - // The transaction is now on chain. We wait for it to be decrypted - // 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_str()).await?; println!( - "Transaction applied with result: {}", - serde_json::to_string_pretty(&response).unwrap() + "Transaction accepted with result: {}", + serde_json::to_string_pretty(&parsed).unwrap() ); - Ok(response) - } else { - tracing::warn!( - "Received an error from the associated wrapper tx: {}", - response.code - ); - Ok(response) - } + // The transaction is now on chain. We wait for it to be decrypted + // and applied + if parsed.code == 0.to_string() { + let parsed = TxResponse::parse( + decrypted_tx_subscription.receive_response()?, + NamadaEventType::Applied, + decrypted_hash.as_str(), + ) + .map_err(WsError::MalformedJson)? + .ok_or_else(|| WsError::MissingEvent(decrypted_hash.clone()))?; + println!( + "Transaction applied with result: {}", + serde_json::to_string_pretty(&parsed).unwrap() + ); + Ok(parsed) + } else { + Ok(parsed) + } + }; + + wrapper_tx_subscription.unsubscribe()?; + wrapper_tx_subscription.close(); + decrypted_tx_subscription.unsubscribe()?; + decrypted_tx_subscription.close(); + parsed } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 5918ab814f..80a532597a 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -356,14 +356,6 @@ async fn update_tendermint_config( config.instrumentation.namespace = tendermint_config.instrumentation_namespace; - // setup the events log - { - // keep events for one minute - config.rpc.event_log_window_size = - std::time::Duration::from_secs(59).into(); - // we do not limit the size of the events log - config.rpc.event_log_max_items = 0; - } let mut file = OpenOptions::new() .write(true) From 96ed8ac1b857f606a44f415bff0f3438d6b0e8d7 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 14:59:03 +0200 Subject: [PATCH 0575/1995] [fix]: Updated the stubborn lock file --- Cargo.lock | 1536 ++++++++++--------- apps/src/lib/node/ledger/tendermint_node.rs | 3 +- 2 files changed, 784 insertions(+), 755 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 586699f576..1176a0aad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" dependencies = [ "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "log 0.4.17", @@ -32,7 +32,7 @@ dependencies = [ "ahash", "base64 0.13.0", "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "bytestring", "derive_more", "encoding_rs", @@ -51,7 +51,7 @@ dependencies = [ "pin-project-lite 0.2.9", "rand 0.8.5", "sha-1 0.10.0", - "smallvec 1.8.0", + "smallvec 1.9.0", "zstd", ] @@ -111,7 +111,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]] @@ -126,7 +126,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]] @@ -137,7 +137,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", ] @@ -161,20 +161,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -186,9 +195,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" [[package]] name = "ark-bls12-381" @@ -364,9 +373,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -389,26 +398,26 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] [[package]] name = "async-io" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" dependencies = [ + "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -417,7 +426,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.4", + "socket2 0.4.7", "waker-fn", "winapi 0.3.9", ] @@ -431,22 +440,14 @@ 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" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" dependencies = [ "async-io", + "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", @@ -468,7 +469,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", "futures-channel", "futures-core", "futures-io", @@ -522,15 +523,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", @@ -559,7 +560,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", @@ -583,12 +584,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", ] @@ -621,7 +622,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "cfg-if 1.0.0", "derive_more", "futures-core", @@ -635,7 +636,7 @@ dependencies = [ "percent-encoding 2.1.0", "pin-project-lite 0.2.9", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.144", "serde_json", "serde_urlencoded", "tokio", @@ -643,16 +644,16 @@ dependencies = [ [[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", ] @@ -699,7 +700,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.144", ] [[package]] @@ -711,7 +712,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static 1.4.0", + "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", @@ -723,9 +724,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", ] @@ -828,16 +829,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -922,16 +923,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "memchr", "regex-automata", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-slice-cast" @@ -956,9 +957,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", @@ -966,9 +967,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", @@ -999,9 +1000,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 = "bytestring" @@ -1009,7 +1010,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", ] [[package]] @@ -1029,12 +1030,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "camino" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" + [[package]] name = "cargo-watch" -version = "7.7.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" +checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" dependencies = [ + "camino", "clap 2.34.0", "log 0.4.17", "shell-escape", @@ -1044,9 +1052,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", ] @@ -1086,13 +1094,13 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", ] [[package]] @@ -1110,14 +1118,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits 0.2.15", "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -1133,7 +1143,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]] @@ -1157,7 +1167,6 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", - "term_size", "textwrap 0.11.0", "unicode-width", "vec_map", @@ -1171,7 +1180,7 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static 1.4.0", + "lazy_static", "os_str_bytes", "strsim 0.10.0", "termcolor", @@ -1182,20 +1191,33 @@ dependencies = [ [[package]] name = "clarity" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" +checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num-bigint 0.4.3", "num-traits 0.2.15", "num256", "secp256k1", - "serde 1.0.137", + "serde 1.0.144", "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.2", + "sha3 0.10.4", +] + +[[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]] @@ -1235,10 +1257,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" @@ -1251,9 +1283,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", ] @@ -1264,10 +1296,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.144", "serde-hjson", "serde_json", "toml", @@ -1322,9 +1354,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -1351,7 +1383,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", ] @@ -1385,7 +1417,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", ] @@ -1400,36 +1432,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", ] @@ -1441,17 +1473,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]] @@ -1462,11 +1494,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", ] @@ -1486,7 +1518,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", ] @@ -1507,9 +1539,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", @@ -1699,9 +1731,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" @@ -1724,7 +1756,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]] @@ -1733,7 +1765,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle 2.4.1", ] @@ -1747,6 +1779,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" @@ -1788,7 +1830,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", @@ -1812,7 +1854,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.144", "signature", ] @@ -1825,7 +1867,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.3", - "serde 1.0.137", + "serde 1.0.144", "sha2 0.9.9", "thiserror", "zeroize", @@ -1841,7 +1883,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1849,22 +1891,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.8.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 = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "encoding_rs" @@ -1928,15 +1957,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 = "error" version = "0.1.9" @@ -1955,7 +1975,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.137", + "serde 1.0.144", "serde_json", ] @@ -1969,9 +1989,9 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.137", + "serde 1.0.144", "serde_json", - "sha3 0.10.2", + "sha3 0.10.4", "thiserror", "uint", ] @@ -2005,9 +2025,9 @@ dependencies = [ [[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" @@ -2045,9 +2065,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", ] @@ -2055,7 +2075,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2081,7 +2101,7 @@ dependencies = [ "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", "serde_json", "subproductdomain", @@ -2092,33 +2112,33 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.137", + "serde 1.0.144", "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]] name = "file-serve" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a09c8127b1a49f66ac56a7c9efe420e2ab23e00266ea4144cc2b905076a7f1" +checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" dependencies = [ "log 0.4.17", "mime_guess", @@ -2127,14 +2147,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]] @@ -2157,9 +2177,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" @@ -2274,9 +2294,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -2289,9 +2309,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -2299,15 +2319,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -2317,9 +2337,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-lite" @@ -2338,9 +2358,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -2360,15 +2380,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-timer" @@ -2378,9 +2398,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -2405,9 +2425,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", @@ -2426,13 +2446,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]] @@ -2458,9 +2478,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" @@ -2485,9 +2505,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", @@ -2511,7 +2531,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2521,7 +2541,7 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20 0.8.2", "hex", "itertools 0.10.3", "miracl_core", @@ -2554,11 +2574,11 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "futures-core", "futures-sink", @@ -2568,7 +2588,7 @@ dependencies = [ "slab", "tokio", "tokio-util 0.7.3", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -2582,41 +2602,37 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hdrhistogram" -version = "7.5.0" +version = "7.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +checksum = "6ea9fe3952d32674a14e0975009a3547af9ea364995b5ec1add2e23c2ae523ab" dependencies = [ - "base64 0.13.0", "byteorder", - "crossbeam-channel", - "flate2", - "nom 7.1.1", "num-traits 0.2.15", ] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "headers-core", "http", "httpdate", "mime 0.3.16", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -2702,7 +2718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2723,7 +2739,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", ] @@ -2734,16 +2750,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "http", "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2772,11 +2788,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", @@ -2787,10 +2803,10 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.9", - "socket2 0.4.4", + "socket2 0.4.7", "tokio", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "want", ] @@ -2800,11 +2816,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "headers", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2821,7 +2837,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.19", + "hyper 0.14.20", "log 0.4.17", "rustls", "rustls-native-certs", @@ -2837,7 +2853,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", @@ -2849,19 +2865,33 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.19", + "bytes 1.2.1", + "hyper 0.14.20", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi 0.3.9", +] + [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "derive_more", "flex-error", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", @@ -2870,17 +2900,17 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.137", + "serde 1.0.144", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.5", "subtle-encoding", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time 0.3.9", - "tracing 0.1.35", + "time 0.3.14", + "tracing 0.1.36", ] [[package]] @@ -2888,7 +2918,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 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", @@ -2897,28 +2927,28 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.137", + "serde 1.0.144", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.5", "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)", - "time 0.3.9", - "tracing 0.1.35", + "time 0.3.14", + "tracing 0.1.36", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" 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.144", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tonic", ] @@ -2928,10 +2958,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.144", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tonic", ] @@ -2943,7 +2973,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", @@ -3008,7 +3038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-lite", "if-addrs", "ipnet", @@ -3041,7 +3071,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -3063,13 +3093,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.144", ] [[package]] @@ -3098,7 +3128,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]] @@ -3115,9 +3145,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" @@ -3166,9 +3196,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" @@ -3181,9 +3211,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", ] @@ -3195,7 +3225,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.144", "serde_json", ] @@ -3236,12 +3266,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" -[[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" @@ -3275,9 +3299,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.121" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libgit2-sys" @@ -3309,9 +3333,9 @@ version = "0.38.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", + "bytes 1.2.1", + "futures 0.3.24", + "lazy_static", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -3336,8 +3360,8 @@ dependencies = [ "libp2p-yamux", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.9.0", "wasm-timer", ] @@ -3351,23 +3375,23 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", - "lazy_static 1.4.0", + "lazy_static", "libsecp256k1 0.3.5", "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", "ring", "rw-stream-sink", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.9.0", "thiserror", "unsigned-varint 0.7.1", "void", @@ -3380,7 +3404,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "flate2", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", ] @@ -3390,10 +3414,10 @@ version = "0.28.1" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std-resolver", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.9.0", "trust-dns-resolver", ] @@ -3404,14 +3428,14 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", ] [[package]] @@ -3422,9 +3446,9 @@ dependencies = [ "asynchronous-codec", "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "fnv", - "futures 0.3.21", + "futures 0.3.24", "hex_fmt", "libp2p-core", "libp2p-swarm", @@ -3434,7 +3458,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", ] @@ -3444,13 +3468,13 @@ name = "libp2p-identify" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "smallvec 1.8.0", + "smallvec 1.9.0", "wasm-timer", ] @@ -3461,10 +3485,10 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "arrayvec 0.5.2", "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3472,7 +3496,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", @@ -3487,15 +3511,15 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.21", + "futures 0.3.24", "if-watch", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "libp2p-swarm", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", + "smallvec 1.9.0", + "socket2 0.4.7", "void", ] @@ -3505,14 +3529,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", ] @@ -3521,10 +3545,10 @@ name = "libp2p-noise" version = "0.31.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", + "futures 0.3.24", + "lazy_static", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3542,7 +3566,7 @@ name = "libp2p-ping" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3557,8 +3581,8 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3572,9 +3596,9 @@ name = "libp2p-pnet" version = "0.21.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "rand 0.7.3", "salsa20", "sha3 0.9.1", @@ -3586,17 +3610,17 @@ version = "0.2.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "futures-timer", "libp2p-core", "libp2p-swarm", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", "void", "wasm-timer", @@ -3608,15 +3632,15 @@ version = "0.11.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-trait", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "lru", "minicbor", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3627,11 +3651,11 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "void", "wasm-timer", ] @@ -3651,14 +3675,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", "if-watch", "ipnet", "libc", "libp2p-core", "log 0.4.17", - "socket2 0.4.4", + "socket2 0.4.7", ] [[package]] @@ -3667,7 +3691,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", ] @@ -3677,7 +3701,7 @@ name = "libp2p-wasm-ext" version = "0.28.2" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3691,7 +3715,7 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "futures-rustls", "libp2p-core", "log 0.4.17", @@ -3707,7 +3731,7 @@ name = "libp2p-yamux" version = "0.32.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "parking_lot 0.11.2", "thiserror", @@ -3758,7 +3782,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.144", "sha2 0.9.9", "typenum", ] @@ -3817,12 +3841,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.144", ] [[package]] @@ -3854,9 +3877,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3938,7 +3961,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.137", + "serde 1.0.144", "serde_derive", "serde_yaml", ] @@ -3988,9 +4011,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -4023,12 +4046,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.144", "strum", "tungstenite 0.16.0", "url 2.2.2", @@ -4087,9 +4110,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -4128,9 +4151,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", @@ -4199,7 +4222,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", @@ -4211,7 +4234,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.2.1", "proc-macro-error", "proc-macro2", "quote", @@ -4230,11 +4253,11 @@ name = "multistream-select" version = "0.10.3" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.9.0", "unsigned-varint 0.7.1", ] @@ -4278,7 +4301,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "serde 1.0.137", + "serde 1.0.144", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4291,8 +4314,8 @@ dependencies = [ "thiserror", "tiny-keccak", "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", @@ -4333,7 +4356,7 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.21", + "futures 0.3.24", "git2", "hex", "itertools 0.10.3", @@ -4361,7 +4384,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", "serde_json", "serde_regex", @@ -4389,9 +4412,9 @@ dependencies = [ "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-log", - "tracing-subscriber 0.3.11", + "tracing-subscriber 0.3.15", "web30", "websocket", "winapi 0.3.9", @@ -4403,7 +4426,7 @@ version = "0.7.1" dependencies = [ "borsh", "itertools 0.10.3", - "lazy_static 1.4.0", + "lazy_static", "madato", "namada", ] @@ -4455,8 +4478,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]] @@ -4464,7 +4487,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.5", ] [[package]] @@ -4482,7 +4505,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.5", ] [[package]] @@ -4491,7 +4514,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", @@ -4516,9 +4539,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", @@ -4529,9 +4552,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", @@ -4555,9 +4578,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", @@ -4639,7 +4662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.1", + "num-complex 0.4.2", "num-integer", "num-iter", "num-rational 0.4.1", @@ -4666,7 +4689,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -4681,9 +4704,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", ] @@ -4768,11 +4791,11 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.144", "serde_derive", ] @@ -4795,12 +4818,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" @@ -4813,11 +4830,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.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "opaque-debug" @@ -4833,9 +4859,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", @@ -4865,9 +4891,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", @@ -4883,7 +4909,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", ] @@ -4920,7 +4946,7 @@ dependencies = [ "data-encoding", "multihash", "percent-encoding 2.1.0", - "serde 1.0.137", + "serde 1.0.144", "static_assertions", "unsigned-varint 0.7.1", "url 2.2.2", @@ -4937,7 +4963,7 @@ dependencies = [ "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -4946,7 +4972,7 @@ version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.2.1", "proc-macro2", "quote", "syn", @@ -4988,7 +5014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.7", + "lock_api 0.4.8", "parking_lot_core 0.8.5", ] @@ -4998,7 +5024,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.7", + "lock_api 0.4.8", "parking_lot_core 0.9.3", ] @@ -5026,8 +5052,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", ] @@ -5039,16 +5065,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.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pathdiff" @@ -5126,33 +5152,71 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset 0.4.2", "indexmap", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" dependencies = [ - "pin-project-internal 0.4.29", + "pin-project-internal 0.4.30", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ - "pin-project-internal 1.0.10", + "pin-project-internal 1.0.12", ] [[package]] name = "pin-project-internal" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" dependencies = [ "proc-macro2", "quote", @@ -5161,9 +5225,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -5196,10 +5260,11 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", @@ -5213,7 +5278,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -5225,7 +5290,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -5299,10 +5364,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -5333,9 +5399,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", ] @@ -5348,7 +5414,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", @@ -5365,7 +5431,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", ] @@ -5375,7 +5441,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", ] @@ -5385,7 +5451,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", @@ -5403,10 +5469,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", @@ -5449,7 +5515,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", ] @@ -5459,7 +5525,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", ] @@ -5528,9 +5594,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", ] @@ -5555,7 +5621,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5571,6 +5637,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -5644,7 +5711,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]] @@ -5709,6 +5776,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" @@ -5747,7 +5823,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", "num_cpus", ] @@ -5768,30 +5844,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", ] @@ -5803,14 +5870,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", @@ -5828,9 +5895,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" @@ -5864,33 +5931,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.144", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5936,12 +6004,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", @@ -5950,9 +6018,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", @@ -5974,7 +6042,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "rustc-hex", ] @@ -6006,13 +6074,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.24.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -6057,7 +6125,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.10", + "semver 1.0.13", ] [[package]] @@ -6087,9 +6155,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" @@ -6109,16 +6177,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", + "futures 0.3.24", + "pin-project 0.4.30", "static_assertions", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -6197,7 +6265,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", ] @@ -6225,18 +6293,18 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "secp256k1" -version = "0.21.3" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" dependencies = [ "cc", ] @@ -6284,9 +6352,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "semver-parser" @@ -6311,9 +6379,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -6324,7 +6392,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", @@ -6339,23 +6407,23 @@ dependencies = [ "byteorder", "error", "num 0.2.1", - "serde 1.0.137", + "serde 1.0.144", ] [[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.144", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -6364,14 +6432,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -6381,29 +6449,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.137", + "serde 1.0.144", ] [[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" @@ -6413,7 +6472,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.144", ] [[package]] @@ -6424,7 +6483,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.137", + "serde 1.0.144", "yaml-rust", ] @@ -6448,7 +6507,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6460,7 +6519,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", + "digest 0.10.3", +] + +[[package]] +name = "sha1" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures 0.2.5", "digest 0.10.3", ] @@ -6484,19 +6554,19 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.10.3", ] @@ -6514,9 +6584,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +checksum = "eaedf34ed289ea47c2b741bb72e5357a209512d67bcd4bda44359e5bf0470f56" dependencies = [ "digest 0.10.3", "keccak", @@ -6528,7 +6598,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]] @@ -6564,9 +6634,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" [[package]] name = "simple-error" @@ -6574,11 +6644,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" @@ -6591,9 +6670,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" @@ -6626,9 +6705,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi 0.3.9", @@ -6643,7 +6722,7 @@ dependencies = [ "base64 0.12.3", "bytes 0.5.6", "flate2", - "futures 0.3.21", + "futures 0.3.24", "httparse", "log 0.4.17", "rand 0.7.3", @@ -6688,15 +6767,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]] @@ -6713,18 +6792,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.0" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -6736,7 +6815,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -6775,9 +6854,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", @@ -6842,7 +6921,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", ] @@ -6850,19 +6929,19 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.21", + "futures 0.3.24", "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", "serde_json", "serde_repr", @@ -6871,7 +6950,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time 0.3.9", + "time 0.3.14", "zeroize", ] @@ -6881,16 +6960,16 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.21", + "futures 0.3.24", "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", "serde_json", "serde_repr", @@ -6899,17 +6978,17 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "time 0.3.9", + "time 0.3.14", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "flex-error", - "serde 1.0.137", + "serde 1.0.144", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "toml", @@ -6922,7 +7001,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.144", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", @@ -6932,14 +7011,14 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "derive_more", "flex-error", - "serde 1.0.137", + "serde 1.0.144", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6949,27 +7028,27 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.137", + "serde 1.0.144", "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", + "time 0.3.14", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" 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.144", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6977,36 +7056,36 @@ 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.144", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", - "futures 0.3.21", - "getrandom 0.2.6", + "futures 0.3.24", + "getrandom 0.2.7", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", - "serde 1.0.137", + "pin-project 1.0.12", + "serde 1.0.144", "serde_bytes", "serde_json", "subtle-encoding", @@ -7014,9 +7093,9 @@ dependencies = [ "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "thiserror", - "time 0.3.9", + "time 0.3.14", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", "url 2.2.2", "uuid", "walkdir", @@ -7029,17 +7108,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", - "futures 0.3.21", - "getrandom 0.2.6", + "futures 0.3.24", + "getrandom 0.2.7", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", - "serde 1.0.137", + "pin-project 1.0.12", + "serde 1.0.144", "serde_bytes", "serde_json", "subtle-encoding", @@ -7047,9 +7126,9 @@ dependencies = [ "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)", "thiserror", - "time 0.3.9", + "time 0.3.14", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", "url 2.2.2", "uuid", "walkdir", @@ -7058,16 +7137,16 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.137", + "serde 1.0.144", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -7077,22 +7156,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.137", + "serde 1.0.144", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "time 0.3.9", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.9", + "time 0.3.14", ] [[package]] @@ -7105,15 +7174,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]] @@ -7124,9 +7194,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", @@ -7139,7 +7209,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] @@ -7172,16 +7241,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" @@ -7204,9 +7263,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa", "libc", @@ -7238,7 +7297,7 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.9", + "time 0.3.14", "url 2.2.2", ] @@ -7259,20 +7318,21 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" dependencies = [ - "bytes 1.1.0", + "autocfg 1.1.0", + "bytes 1.2.1", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.4.7", "tokio-macros", "winapi 0.3.9", ] @@ -7360,7 +7420,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", @@ -7424,7 +7484,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", @@ -7447,7 +7507,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", @@ -7461,12 +7521,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]] @@ -7475,7 +7535,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.144", ] [[package]] @@ -7487,16 +7547,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.12", "prost 0.9.0", "prost-derive 0.9.0", "tokio", @@ -7505,7 +7565,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)", ] @@ -7523,15 +7583,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.12", "pin-project-lite 0.2.9", "rand 0.8.5", "slab", @@ -7539,7 +7599,7 @@ dependencies = [ "tokio-util 0.7.3", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -7547,9 +7607,9 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus#21623a99bdca5b006d53752a1967849bef3b89ea" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "pin-project 1.0.10", + "bytes 1.2.1", + "futures 0.3.24", + "pin-project 1.0.12", "prost 0.9.0", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "tokio", @@ -7565,9 +7625,9 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "pin-project 1.0.10", + "bytes 1.2.1", + "futures 0.3.24", + "pin-project 1.0.12", "prost 0.9.0", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tokio", @@ -7595,9 +7655,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" @@ -7612,15 +7672,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]] @@ -7635,9 +7695,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", @@ -7649,14 +7709,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", @@ -7668,7 +7728,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", ] @@ -7678,8 +7738,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", - "tracing 0.1.35", + "pin-project 1.0.12", + "tracing 0.1.36", ] [[package]] @@ -7697,9 +7757,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]] @@ -7709,25 +7769,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", ] @@ -7736,7 +7796,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "pin-project-lite 0.2.9", "tower-layer", "tower-make", @@ -7766,10 +7826,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", @@ -7784,12 +7844,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", ] @@ -7808,7 +7868,7 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "input_buffer", @@ -7827,7 +7887,7 @@ checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "log 0.4.17", @@ -7852,9 +7912,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" @@ -7894,15 +7954,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", ] @@ -7931,19 +7991,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" @@ -7957,7 +8008,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", ] @@ -8009,7 +8060,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]] @@ -8058,26 +8109,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" @@ -8134,9 +8165,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", @@ -8144,13 +8175,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", @@ -8159,9 +8190,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", @@ -8171,9 +8202,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", @@ -8181,9 +8212,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", @@ -8194,15 +8225,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.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" dependencies = [ "leb128", ] @@ -8213,7 +8244,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -8269,9 +8300,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.144", "serde_bytes", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", "thiserror", "wasmer-types", @@ -8292,9 +8323,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", @@ -8309,11 +8340,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", @@ -8339,12 +8370,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.144", "serde_bytes", "target-lexicon", "thiserror", @@ -8365,11 +8396,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.4", "rkyv", - "serde 1.0.137", + "serde 1.0.144", "tempfile", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -8404,7 +8435,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", @@ -8419,7 +8450,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.144", "thiserror", ] @@ -8440,7 +8471,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.137", + "serde 1.0.144", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -8460,9 +8491,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "42.0.0" +version = "46.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" dependencies = [ "leb128", "memchr", @@ -8472,28 +8503,27 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.44" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" 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", @@ -8501,9 +8531,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", @@ -8517,12 +8547,12 @@ checksum = "41c0c0c928020760cc69884944d54e7fca43ff5d00933d0a63fd85155466fbed" dependencies = [ "awc", "clarity", - "futures 0.3.21", - "lazy_static 1.4.0", + "futures 0.3.24", + "lazy_static", "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.137", + "serde 1.0.144", "serde_derive", "serde_json", "tokio", @@ -8549,9 +8579,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", @@ -8570,9 +8600,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", @@ -8599,13 +8629,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static 1.4.0", "libc", + "once_cell", ] [[package]] @@ -8815,7 +8845,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", @@ -8825,9 +8855,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 80a532597a..407226fe37 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -187,7 +187,7 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { // reset all the Tendermint state, if any std::process::Command::new(tendermint_path) .args(&[ - "reset", + "reset-state", "unsafe-all", // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels // "--log-level=\"*debug\"", @@ -356,7 +356,6 @@ async fn update_tendermint_config( config.instrumentation.namespace = tendermint_config.instrumentation_namespace; - let mut file = OpenOptions::new() .write(true) .truncate(true) From 86b2301dc7a7d0319e7cbffef815861fa3deb9a5 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Sep 2022 15:58:50 +0200 Subject: [PATCH 0576/1995] Update shared/src/types/eth_bridge_pool.rs Co-authored-by: James --- shared/src/types/eth_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index c02c9fef82..18c6e7f1a9 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -63,7 +63,7 @@ pub struct PendingTransfer { BorshDeserialize, )] pub struct GasFee { - /// The amount of fess (in NAM) + /// The amount of fees (in NAM) pub amount: Amount, /// The account of fee payer. pub payer: Address, From e283672ebd7ffbc10706adb98f0bc2b0f84754ce Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Sep 2022 15:59:35 +0200 Subject: [PATCH 0577/1995] Update shared/src/ledger/eth_bridge/bridge_pool_vp.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 0b2b76a2dd..d354f9890b 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -101,7 +101,8 @@ where // check that the signed root is not modified let signed_root_key = get_signed_root_key(); - if keys_changed.contains(&signed_root_key) { +if keys_changed.contains(&signed_root_key) { + tracing::debug!("Rejecting transaction as it is attempting to change the signed root key"); return Ok(false); } // check that the pending transfer (and only that) was added to the pool From 76ed3ce4ea2b9df7f9bcc51bc30256a79cebc836 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Sep 2022 16:00:02 +0200 Subject: [PATCH 0578/1995] Update shared/src/ledger/eth_bridge/bridge_pool_vp.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index d354f9890b..a5fe987e82 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -118,6 +118,10 @@ if keys_changed.contains(&signed_root_key) { "The bridge pool transfers are missing from storage" ))?; if !pending_post.contains(&transfer) { + tracing::debug!( + "Rejecting transaction as the transfer wasn't added to the \ + pending transfers" + ); return Ok(false); } for item in pending_pre.symmetric_difference(&pending_post) { From f12120f8fb05504e4da8c4e65ed70b9f450fe286 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Sep 2022 16:00:24 +0200 Subject: [PATCH 0579/1995] Update shared/src/ledger/eth_bridge/bridge_pool_vp.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index a5fe987e82..7aef89d804 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -126,6 +126,11 @@ if keys_changed.contains(&signed_root_key) { } for item in pending_pre.symmetric_difference(&pending_post) { if item != &transfer { + tracing::debug!( + ?item, + "Rejecting transaction as an unrecognized item was added \ + to the pending transfers" + ); return Ok(false); } } From c8b5630c8f7eb407ae3bc430bd15f649fcc4a4da Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 6 Sep 2022 16:44:08 +0200 Subject: [PATCH 0580/1995] [fix]: Updated the lock file and fixed resulting issues. Tendermint currently won't start however --- apps/src/lib/client/tx.rs | 15 +++++++++++++-- apps/src/lib/node/ledger/events.rs | 6 ------ apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shims/abcipp_shim_types.rs | 2 +- apps/src/lib/node/ledger/tendermint_node.rs | 4 +--- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ddf8aedf18..28b78aa9cc 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1197,9 +1197,20 @@ pub async fn submit_tx( } => (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())?, - None, + Some(websocket_timeout), )?; // It is better to subscribe to the transaction before it is broadcast @@ -1215,7 +1226,7 @@ pub async fn submit_tx( let mut decrypted_tx_subscription = { let mut decrypted_tx_subscription = TendermintWebsocketClient::open( WebSocketAddress::try_from(address.clone())?, - None, + Some(websocket_timeout), )?; let query = Query::from(EventType::NewBlock) .and_eq(ACCEPTED_QUERY_KEY, decrypted_hash.as_str()); diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 486bf485d9..3adefc59d7 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -159,14 +159,8 @@ impl From for crate::facade::tendermint_proto::abci::Event { .attributes .into_iter() .map(|(key, value)| EventAttribute { - #[cfg(feature = "abcipp")] key, - #[cfg(not(feature = "abcipp"))] - key: key.into_bytes(), - #[cfg(feature = "abcipp")] value, - #[cfg(not(feature = "abcipp"))] - value: value.into_bytes(), index: true, }) .collect(), diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f41e611180..61a8786eeb 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -52,7 +52,7 @@ use super::protocol::ShellParams; use super::rpc; use crate::config::{genesis, TendermintMode}; #[cfg(not(feature = "abcipp"))] -use crate::facade::tendermint_proto::abci::ConsensusParams; +use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; 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 89bc78fd01..6b19486afa 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -258,7 +258,7 @@ pub mod shim { /// Custom types for response payloads pub mod response { #[cfg(not(feature = "abcipp"))] - use crate::facade::tendermint_proto::abci::ConsensusParams; + use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::abci::{ Event as TmEvent, ResponseProcessProposal, ValidatorUpdate, }; diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 407226fe37..d30eaa4043 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -126,9 +126,7 @@ pub async fn run( let mut tendermint_node = Command::new(&tendermint_path); tendermint_node.args(&[ "start", - "--mode", - &mode, - "--proxy-app", + "--proxy_app", &ledger_address, "--home", &home_dir_string, From 9c1d50c19bd76126795a31a1e58f10187f4aff82 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 7 Sep 2022 10:38:13 +0200 Subject: [PATCH 0581/1995] [fix]: Protected all storage under bridge pool from modification under than the pool itself. Added logging --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 23 +++++++++++++------ .../ledger/eth_bridge/storage/bridge_pool.rs | 12 ++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 7aef89d804..e031d59e61 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -15,7 +15,7 @@ use borsh::BorshDeserialize; use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BRIDGE_POOL_ADDRESS, + get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage::{DBIter, StorageHasher, DB}; @@ -96,13 +96,20 @@ where let transfer: PendingTransfer = match signed.data { Some(data) => BorshDeserialize::try_from_slice(data.as_slice()) .map_err(|e| Error(e.into()))?, - None => return Ok(false), + None => { + tracing::debug!( + "Rejecting transaction as there was no signed data" + ); + return Ok(false); + } }; - // check that the signed root is not modified - let signed_root_key = get_signed_root_key(); -if keys_changed.contains(&signed_root_key) { - tracing::debug!("Rejecting transaction as it is attempting to change the signed root key"); + // check that only the pending_key value is changed + if keys_changed.iter().any(is_protected_storage) { + tracing::debug!( + "Rejecting transaction as it is attempting to change the \ + bridge pool storage other than the pending transaction pool" + ); return Ok(false); } // check that the pending transfer (and only that) was added to the pool @@ -145,6 +152,7 @@ if keys_changed.contains(&signed_root_key) { return Ok(false); } } else { + tracing::debug!("The gas fee payers account was not debited."); return Ok(false); } // check that the correct amount was credited to escrow @@ -155,9 +163,9 @@ if keys_changed.contains(&signed_root_key) { return Ok(false); } } else { + tracing::debug!("The bridge pools escrow was not credited."); return Ok(false); } - // TODO: Verify nonce? Ok(true) } @@ -170,6 +178,7 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use super::*; + use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::write_log::WriteLog; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 73a5d0b382..b86266f124 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -32,3 +32,15 @@ pub fn get_signed_root_key() -> Key { ], } } + +/// Check if a key belongs to the bridge pools sub-storage +pub fn is_bridge_pool_key(key: &Key) -> bool { + matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) +} + +/// Check if a key belongs to the bridge pool but is not +/// the key for the pending transaction pool. Such keys +/// may not be modified via transactions. +pub fn is_protected_storage(key: &Key) -> bool { + is_bridge_pool_key(key) && *key != get_pending_key() +} From bb9aa7fc735f6ebef1233850c03c049cfcd807aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 7 Sep 2022 09:37:55 +0000 Subject: [PATCH 0582/1995] [ci skip] 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 5b6626ce1d..cfdcd29c71 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.a757ac9b673838596e83bf33165928ed72785c34646dd6f8a051e7f8722af55e.wasm", - "tx_ibc.wasm": "tx_ibc.95a796794a2e9ff7fd207d5ae44bb15dfbdad4dbd9af9bb9821df7cccad2ac0c.wasm", - "tx_init_account.wasm": "tx_init_account.9cf8a7edd916f9f525b409c0b5dd6cb6ab3f0c56fcbfdbcd12aa85c93f18fa27.wasm", - "tx_init_nft.wasm": "tx_init_nft.5fd33b12eeaeee04071a5b5992a7ade35264911b0f6e58f7f73d3abd3db6dd5c.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ec5fdcc9c1357d8b605d0a5332d2eeb0cf295ffbe01847beeeb1492caf877799.wasm", - "tx_init_validator.wasm": "tx_init_validator.1d3f64de435631ab8a22d8dda03335cc3ca985a9fef9321e5749e54042f70dd3.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.a7dc2a035ef126e37920373a334c6018b9f86a522af2271cf0cd520b01f7cd36.wasm", - "tx_transfer.wasm": "tx_transfer.dce689ac8924baaad0b214a76b4350768e24fafd83e36c0193f125a1b907c8b3.wasm", - "tx_unbond.wasm": "tx_unbond.97f20427841154ca5723fea364aa808cf169dce3cf7c3de81d5b8c539b06b5bf.wasm", - "tx_update_vp.wasm": "tx_update_vp.dd36fe5c547e7b6d4131b4160c71569259f4b4457b3ff78c737ff5fe9ba32f6d.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.b03ec390390b7242904aa00df28a8f35ffbd5d5e51ae0c17929f7498087e447f.wasm", - "tx_withdraw.wasm": "tx_withdraw.fc0b0a43640033a8e8576247cde5ee518c99a09a4da9e072286d8279df67d4b8.wasm", - "vp_nft.wasm": "vp_nft.b98d17a18cf98b43a84083870d1be0502260e58ff08db8ca98c102b46f5ec3df.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.45b4877bac5c6a1341751f4c1059d1b2ef2f9387f0cf381cbed564eafd4583e8.wasm", - "vp_token.wasm": "vp_token.a6f531a0d93cba4de43f38af271a439b3b3ad1ef1c5771519126052dadc68fd1.wasm", - "vp_user.wasm": "vp_user.d95fa274daebe8816b3dd508968d83e64ecaa1b1b1669c95769beea259d82e48.wasm" + "tx_from_intent.wasm": "tx_from_intent.56bee1c6b0d35b09adf023cf3cf0e5b9c4de6c23faba61afcc3e0bc8c426e778.wasm", + "tx_ibc.wasm": "tx_ibc.f82b5c0515e60c2d4cde106e9098ad3340250b4c003934e5b53a0c2806475f79.wasm", + "tx_init_account.wasm": "tx_init_account.970b1e7f79a8d43f60d58c4357aa45efc8cf4290feef5442e8365aab75ea863e.wasm", + "tx_init_nft.wasm": "tx_init_nft.d8994270e8815c22eb9c9d130ff5f36756540cfcebbf4feb2f95f317367ed481.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e5d15a9278e2d9355f0fc6ae707ad694e13f8d496b36ca367aa3883f0a6e621f.wasm", + "tx_init_validator.wasm": "tx_init_validator.4c45c31b1b494b6ce3a64122e9d87e2fed9757e5a6580d3f4d37e8a1600cdff7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.31d842637ec27da8010e184f2a2fdbcee141a3b62888dd432cb43c3113f787d5.wasm", + "tx_transfer.wasm": "tx_transfer.1c9ca5c5743669fc90ef3ed69cc6a7e463f264b238ae5b782d8d38aec9c40055.wasm", + "tx_unbond.wasm": "tx_unbond.5e928128233c6077099d53e11ae3161205c43347c3d7dd59554a4b2ef308428b.wasm", + "tx_update_vp.wasm": "tx_update_vp.f1cc672546b603b9d87f05dfe5f0ab32fba554a66715cf33b33b3e8bcc1cc543.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.2a43eb9af520bdc31affe43e2c31c0b5a1587e9a3a4659691077ab4cbf48e589.wasm", + "tx_withdraw.wasm": "tx_withdraw.d8ade8b8d41457fa4180e40784e7c0a6c1a61295f73f82a5c5226fcbc04c4038.wasm", + "vp_nft.wasm": "vp_nft.e3b467ba8f396c28abcdf54bc348c04d105b2f1c6249254b55b57bccd6646add.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.747aa4ee7f75462a96625a76d6a687cae2794b5fcd092c8ac1e94ae999efd7df.wasm", + "vp_token.wasm": "vp_token.d7a0d0a85fdcc5d87f07e4bea476156e21907ada630dacaa2f61a5dc7aa91597.wasm", + "vp_user.wasm": "vp_user.55a3302e0da13ca6f9ee2e8d072b4984fdfa0497141e4a96d63337419e2df84f.wasm" } \ No newline at end of file From 1b0040ae7bab0d108a22a5a0e16ccd8a29304062 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 13:21:25 +0100 Subject: [PATCH 0583/1995] ProcessProposal: accept genesis block without vote extensions --- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 32659cdee0..d1ccb29ff2 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -387,7 +387,8 @@ where /// Checks if we have found the correct number of Ethereum events /// vote extensions in [`DigestCounters`]. fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { - self.storage.last_height.0 > 0 && c.eth_ev_digest_num == 1 + self.storage.last_height.0 == 0 + || self.storage.last_height.0 > 0 && c.eth_ev_digest_num == 1 } /// Checks if we have found the correct number of validator set update @@ -397,8 +398,9 @@ where .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) { - // TODO: confirm if we need a height check here or not - self.storage.last_height.0 > 0 && c.valset_upd_digest_num == 1 + self.storage.last_height.0 == 0 + || self.storage.last_height.0 > 0 + && c.valset_upd_digest_num == 1 } else { true } From ffd9aed4810e8b1883e4aa1593be448f6ac2fd29 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 13:22:06 +0100 Subject: [PATCH 0584/1995] FinalizeBlock: accept ProtocolTxType::ValidatorSetUpdate --- apps/src/lib/node/ledger/protocol/mod.rs | 1 + apps/src/lib/node/ledger/shell/finalize_block.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 1bfe51ddc2..cf44333730 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -220,6 +220,7 @@ where } Ok(TxResult::default()) } + ProtocolTxType::ValidatorSetUpdate(_) => Ok(TxResult::default()), _ => { tracing::error!( "Attempt made to apply an unsupported protocol transaction! - \ diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index c75bce506e..19bf87ffdd 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -324,10 +324,14 @@ where } Event::new_tx_event(&tx_type, height.0) } - _ => { + ProtocolTxType::ValidatorSetUpdate(_) => { + Event::new_tx_event(&tx_type, height.0) + } + ref protocol_tx_type => { tracing::error!( + ?protocol_tx_type, "Internal logic error: FinalizeBlock received an \ - unsupported TxType::Protocol transaction" + unsupported ProtocolTxType transaction" ); continue; } From 510cca4999dc39a3eed83664b044835eb154310c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 7 Sep 2022 16:29:01 +0200 Subject: [PATCH 0585/1995] [fix]: Got tendermint working with the ledger again --- Cargo.lock | 43 ++++++++++----------- apps/src/lib/node/ledger/tendermint_node.rs | 7 +++- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1176a0aad1..7762cab3f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2494,7 +2494,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -3722,7 +3722,7 @@ dependencies = [ "quicksink", "rw-stream-sink", "soketto", - "url 2.2.2", + "url 2.3.0", "webpki-roots", ] @@ -4054,7 +4054,7 @@ dependencies = [ "serde 1.0.144", "strum", "tungstenite 0.16.0", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -4949,7 +4949,7 @@ dependencies = [ "serde 1.0.144", "static_assertions", "unsigned-varint 0.7.1", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -5959,7 +5959,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tower-service", - "url 2.2.2", + "url 2.3.0", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6929,7 +6929,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6985,14 +6985,14 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde 1.0.144", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "toml", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -7005,13 +7005,13 @@ dependencies = [ "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", - "url 2.2.2", + "url 2.3.0", ] [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -7037,7 +7037,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes 1.2.1", "flex-error", @@ -7071,7 +7071,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "async-tungstenite", @@ -7096,7 +7096,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.2.2", + "url 2.3.0", "uuid", "walkdir", ] @@ -7129,7 +7129,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.2.2", + "url 2.3.0", "uuid", "walkdir", ] @@ -7137,7 +7137,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#8cca4e1cb2824fedb5dff02e2734bf2e81a02a02" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7298,7 +7298,7 @@ dependencies = [ "chunked_transfer", "log 0.4.17", "time 0.3.14", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -7832,7 +7832,7 @@ dependencies = [ "smallvec 1.9.0", "thiserror", "tinyvec", - "url 2.2.2", + "url 2.3.0", ] [[package]] @@ -7875,7 +7875,7 @@ dependencies = [ "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.2.2", + "url 2.3.0", "utf-8", ] @@ -7894,7 +7894,7 @@ dependencies = [ "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.2.2", + "url 2.3.0", "utf-8", ] @@ -8032,13 +8032,12 @@ dependencies = [ [[package]] name = "url" -version = "2.2.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" dependencies = [ "form_urlencoded", "idna 0.2.3", - "matches", "percent-encoding 2.1.0", ] diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index d30eaa4043..1512bcdd87 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -321,8 +321,10 @@ async fn update_tendermint_config( let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("config.toml"); let mut config = - TendermintConfig::load_toml_file(&path).map_err(Error::LoadConfig)?; - + TendermintConfig::load_toml_file(&path).map_err(|e| { + tracing::debug!("Error: {:?}", e); + Error::LoadConfig(e) + })?; config.p2p.laddr = TendermintAddress::from_str(&tendermint_config.p2p_address.to_string()) .unwrap(); @@ -360,6 +362,7 @@ async fn update_tendermint_config( .open(path) .await .map_err(Error::OpenWriteConfig)?; + let config_str = toml::to_string(&config).map_err(Error::ConfigSerializeToml)?; file.write_all(config_str.as_bytes()) From 5a1be14a3fd287995710177fd410404cb351de06 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 8 Sep 2022 10:21:30 +0200 Subject: [PATCH 0586/1995] [fix]: Removed debugging log line --- apps/src/lib/node/ledger/tendermint_node.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 1512bcdd87..324fd8f6a4 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -321,10 +321,7 @@ async fn update_tendermint_config( let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("config.toml"); let mut config = - TendermintConfig::load_toml_file(&path).map_err(|e| { - tracing::debug!("Error: {:?}", e); - Error::LoadConfig(e) - })?; + TendermintConfig::load_toml_file(&path).map_err(Error::LoadConfig)?; config.p2p.laddr = TendermintAddress::from_str(&tendermint_config.p2p_address.to_string()) .unwrap(); From e27a219df13e0a1193a904e8ced907a983ad9c29 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 8 Sep 2022 10:40:49 +0200 Subject: [PATCH 0587/1995] [fix]: Fix the counting of vote extensions in the shim so the chain can make progress --- apps/src/lib/node/ledger/shell/process_proposal.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ed8916f551..c4dda62f77 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -378,7 +378,14 @@ where /// Checks if we have found the correct number of Ethereum events /// vote extensions in [`DigestCounters`]. fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { - self.storage.last_height.0 > 0 && c.eth_ev_digest_num == 1 + #[cfg(feature = "abcipp")] + { + self.storage.last_height.0 > 0 && c.eth_ev_digest_num == 1 + } + #[cfg(not(feature = "abcipp"))] + { + c.eth_ev_digest_num <= 1 + } } /// Checks if we have found the correct number of validator set update From 5e599adf909314eae224b39f12168a71b3a3aa32 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 8 Sep 2022 10:15:47 +0100 Subject: [PATCH 0588/1995] Remove redundant last height check --- apps/src/lib/node/ledger/shell/process_proposal.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d1ccb29ff2..c3a1fbd14b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -387,8 +387,7 @@ where /// Checks if we have found the correct number of Ethereum events /// vote extensions in [`DigestCounters`]. fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { - self.storage.last_height.0 == 0 - || self.storage.last_height.0 > 0 && c.eth_ev_digest_num == 1 + self.storage.last_height.0 == 0 || c.eth_ev_digest_num == 1 } /// Checks if we have found the correct number of validator set update @@ -398,9 +397,7 @@ where .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) { - self.storage.last_height.0 == 0 - || self.storage.last_height.0 > 0 - && c.valset_upd_digest_num == 1 + self.storage.last_height.0 == 0 || c.valset_upd_digest_num == 1 } else { true } From 832bfbea2a0dc0520ebdb9921b1544eae70c167b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 8 Sep 2022 10:46:56 +0100 Subject: [PATCH 0589/1995] Add Ethereum governance key addresses to validator set update vote extensions --- .../vote_extensions/validator_set_update.rs | 129 ++++++++++-------- 1 file changed, 74 insertions(+), 55 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b51fc83df1..a7e2ea7057 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -112,51 +112,69 @@ impl Vext { } } +/// Container type for both kinds of Ethereum bridge addresses: +/// +/// - An address derived from a hot key. +/// - An address derived from a cold key. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct EthAddrBook { + /// Ethereum address derived from a hot key. + pub hot_key_addr: EthAddress, + /// Ethereum address derived from a cold key. + pub cold_key_addr: EthAddress, +} + /// Provides a mapping between [`EthAddress`] and [`VotingPower`] instances. -pub type VotingPowersMap = HashMap; +pub type VotingPowersMap = HashMap; /// This trait contains additional methods for a [`HashMap`], related /// with validator set update vote extensions logic. pub trait VotingPowersMapExt { - /// Returns the keccak hash of this [`VotingPowersMap`] - /// to be signed by an Ethereum validator key. - fn get_bridge_hash(&self, block_height: BlockHeight) -> KeccakHash; - - /// Returns the keccak hash of this [`VotingPowersMap`] - /// to be signed by an Ethereum governance key. - fn get_governance_hash(&self, block_height: BlockHeight) -> KeccakHash; - - /// Returns the list of Ethereum validator addresses and their respective - /// voting power (in this order), with an Ethereum ABI compatible encoding. - fn get_abi_encoded(&self) -> (Vec, Vec); -} - -impl VotingPowersMapExt for VotingPowersMap { - #[inline] - fn get_bridge_hash(&self, block_height: BlockHeight) -> KeccakHash { - let (validators, voting_powers) = self.get_abi_encoded(); - - compute_hash( + /// Returns the list of Ethereum validator hot and cold addresses and their + /// respective voting power (in this order), with an Ethereum ABI + /// compatible encoding. + fn get_abi_encoded(&self) -> (Vec, Vec, Vec); + + /// Returns the keccak hashes of this [`VotingPowersMap`], + /// to be signed by an Ethereum hot and cold key, respectively. + fn get_bridge_and_gov_hashes( + &self, + block_height: BlockHeight, + ) -> (KeccakHash, KeccakHash) { + let (hot_key_addrs, cold_key_addrs, voting_powers) = + self.get_abi_encoded(); + + let bridge_hash = compute_hash( block_height, BRIDGE_CONTRACT_NAMESPACE, - validators, - voting_powers, - ) - } + hot_key_addrs, + voting_powers.clone(), + ); - #[inline] - fn get_governance_hash(&self, block_height: BlockHeight) -> KeccakHash { - compute_hash( + let governance_hash = compute_hash( block_height, GOVERNANCE_CONTRACT_NAMESPACE, - // TODO: get governance validators - vec![], - // TODO: get governance voting powers - vec![], - ) + cold_key_addrs, + voting_powers, + ); + + (bridge_hash, governance_hash) } +} - fn get_abi_encoded(&self) -> (Vec, Vec) { +impl VotingPowersMapExt for VotingPowersMap { + fn get_abi_encoded(&self) -> (Vec, Vec, Vec) { // get addresses and voting powers all into one vec let mut unsorted: Vec<_> = self.iter().collect(); @@ -171,10 +189,11 @@ impl VotingPowersMapExt for VotingPowersMap { .map(|&(_, &voting_power)| u64::from(voting_power)) .sum(); - // split the vec into two - sorted - .into_iter() - .map(|(&EthAddress(addr), &voting_power)| { + // split the vec into three portions + let init = (Vec::new(), Vec::new(), Vec::new()); + sorted.into_iter().fold( + init, + |accum, (ref addr_book, &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power @@ -186,12 +205,19 @@ impl VotingPowersMapExt for VotingPowersMap { let voting_power = voting_power.round().to_integer(); let voting_power: ethereum::U256 = voting_power.into(); - ( - Token::Address(ethereum::H160(addr)), - Token::Uint(voting_power), - ) - }) - .unzip() + let (mut fst, mut snd, mut thd) = accum; + let EthAddrBook { + hot_key_addr: EthAddress(hot_key_addr), + cold_key_addr: EthAddress(cold_key_addr), + } = addr_book; + + fst.push(Token::Address(ethereum::H160(*hot_key_addr))); + snd.push(Token::Address(ethereum::H160(*cold_key_addr))); + thd.push(Token::Uint(voting_power)); + + (fst, snd, thd) + }, + ) } } @@ -240,20 +266,13 @@ mod tag { type Output = [u8; 32]; fn serialize(ext: &Vext) -> Self::Output { + let (KeccakHash(bridge_hash), KeccakHash(gov_hash)) = ext + .voting_powers + .get_bridge_and_gov_hashes(ext.block_height); let KeccakHash(output) = AbiEncode::signed_keccak256(&[ Token::String("updateValidatorsSet".into()), - Token::FixedBytes( - ext.voting_powers - .get_bridge_hash(ext.block_height) - .0 - .to_vec(), - ), - Token::FixedBytes( - ext.voting_powers - .get_governance_hash(ext.block_height) - .0 - .to_vec(), - ), + Token::FixedBytes(bridge_hash.to_vec()), + Token::FixedBytes(gov_hash.to_vec()), bheight_to_token(ext.block_height), ]); output From acdca15ff7c4c7d4293ebbd3f0949cce7feee76c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 8 Sep 2022 14:26:38 +0200 Subject: [PATCH 0590/1995] [feat]: Fixed the websockets and parsing of events returned from tendermint --- Cargo.lock | 15 +++--- apps/src/lib/client/tendermint_rpc_types.rs | 52 ++----------------- apps/src/lib/client/tx.rs | 13 ++--- .../lib/node/ledger/shell/finalize_block.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 4 +- .../node/ledger/shims/abcipp_shim_types.rs | 4 +- 6 files changed, 20 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7762cab3f5..d39c411ac9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,11 +413,10 @@ dependencies = [ [[package]] name = "async-io" -version = "1.9.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ - "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -442,12 +441,11 @@ dependencies = [ [[package]] name = "async-process" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", - "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", @@ -5260,11 +5258,10 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index c8bca25cb5..32454c0440 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,14 +1,10 @@ -use std::convert::TryFrom; - use jsonpath_lib as jsonpath; use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; -use crate::node::ledger::events::{ - Attributes, Error, EventType as NamadaEventType, -}; +use crate::node::ledger::events::EventType as NamadaEventType; /// Data needed for broadcasting a tx and /// monitoring its progress on chain @@ -47,53 +43,11 @@ impl TxResponse { json: serde_json::Value, event_type: NamadaEventType, tx_hash: &str, - ) -> Result, Error> { - let mut selector = jsonpath::selector(&json); - let mut event = { - match selector(&format!("$.events.[?(@.type=='{}')]", event_type)) - .unwrap() - .pop() - { - Some(event) => { - let attrs = Attributes::try_from(event)?; - match attrs.get("hash") { - Some(hash) if hash == tx_hash => attrs, - _ => return Ok(None), - } - } - _ => return Ok(None), - } - }; - let info = event.take("info").unwrap(); - let log = event.take("log").unwrap(); - let height = event.take("height").unwrap(); - let hash = event.take("hash").unwrap(); - let code = event.take("code").unwrap(); - let gas_used = - event.take("gas_used").unwrap_or_else(|| String::from("0")); - let initialized_accounts = event.take("initialized_accounts"); - let initialized_accounts = match initialized_accounts { - Some(values) => serde_json::from_str(&values).unwrap(), - _ => vec![], - }; - Ok(Some(TxResponse { - info, - log, - height, - hash, - code, - gas_used, - initialized_accounts, - })) - } - - /// Find a tx with a given hash from the the websocket subscription - /// to Tendermint events. - pub fn find_tx(json: serde_json::Value, tx_hash: &str) -> Self { + ) -> Self { let tx_hash_json = serde_json::Value::String(tx_hash.to_string()); let mut selector = jsonpath::selector(&json); let mut index = 0; - let evt_key = "accepted"; + let evt_key = event_type.to_string(); // Find the tx with a matching hash let hash = loop { if let Ok(hash) = diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 28b78aa9cc..f759f06a37 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1208,6 +1208,7 @@ pub async fn submit_tx( } else { Duration::new(300, 0) }; + tracing::debug!("Tenderming address: {:?}", address); let mut wrapper_tx_subscription = TendermintWebsocketClient::open( WebSocketAddress::try_from(address.clone())?, Some(websocket_timeout), @@ -1218,7 +1219,7 @@ pub async fn submit_tx( // 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()); + .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); wrapper_tx_subscription.subscribe(query)?; // We also subscribe to the event emitted when the encrypted @@ -1229,7 +1230,7 @@ pub async fn submit_tx( Some(websocket_timeout), )?; let query = Query::from(EventType::NewBlock) - .and_eq(ACCEPTED_QUERY_KEY, decrypted_hash.as_str()); + .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); decrypted_tx_subscription.subscribe(query)?; decrypted_tx_subscription }; @@ -1242,9 +1243,7 @@ pub async fn submit_tx( wrapper_tx_subscription.receive_response()?, NamadaEventType::Accepted, wrapper_hash, - ) - .map_err(WsError::MalformedJson)? - .ok_or_else(|| WsError::MissingEvent(wrapper_hash.clone()))?; + ); println!( "Transaction accepted with result: {}", @@ -1257,9 +1256,7 @@ pub async fn submit_tx( decrypted_tx_subscription.receive_response()?, NamadaEventType::Applied, decrypted_hash.as_str(), - ) - .map_err(WsError::MalformedJson)? - .ok_or_else(|| WsError::MissingEvent(decrypted_hash.clone()))?; + ); println!( "Transaction applied with result: {}", serde_json::to_string_pretty(&parsed).unwrap() diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4433878e93..6070c3c7c7 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -327,7 +327,8 @@ where _ => { tracing::error!( "Internal logic error: FinalizeBlock received an \ - unsupported TxType::Protocol transaction" + unsupported TxType::Protocol transaction: {:?}", + protocol_tx ); continue; } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 61a8786eeb..d9c44552e6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -51,12 +51,12 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use super::protocol::ShellParams; use super::rpc; use crate::config::{genesis, TendermintMode}; -#[cfg(not(feature = "abcipp"))] -use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; +#[cfg(not(feature = "abcipp"))] +use crate::facade::tendermint_proto::types::ConsensusParams; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tower_abci::{request, response}; 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 6b19486afa..f5b9488033 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -257,11 +257,11 @@ pub mod shim { /// Custom types for response payloads pub mod response { - #[cfg(not(feature = "abcipp"))] - use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tendermint_proto::abci::{ Event as TmEvent, ResponseProcessProposal, ValidatorUpdate, }; + #[cfg(not(feature = "abcipp"))] + use crate::facade::tendermint_proto::types::ConsensusParams; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::{ abci::{ExecTxResult, ResponseFinalizeBlock}, From d2ce2018641777b47eaf6360698ed90dde1407c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 8 Sep 2022 16:42:00 +0100 Subject: [PATCH 0591/1995] Add Cargo.lock changes --- Cargo.lock | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d39c411ac9..fd762c657f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,6 +926,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.11.0" @@ -4246,6 +4256,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "quick-error 1.2.3", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "multistream-select" version = "0.10.3" @@ -4340,6 +4368,7 @@ dependencies = [ "borsh", "byte-unit", "byteorder", + "bytes 1.2.1", "cargo-watch", "clap 3.0.0-beta.2", "clarity", @@ -4413,6 +4442,7 @@ dependencies = [ "tracing 0.1.36", "tracing-log", "tracing-subscriber 0.3.15", + "warp", "web30", "websocket", "winapi 0.3.9", @@ -6266,6 +6296,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -7498,6 +7534,19 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log 0.4.17", + "pin-project 1.0.12", + "tokio", + "tungstenite 0.14.0", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -7876,6 +7925,25 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.9.8", + "thiserror", + "url 2.3.0", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.16.0" @@ -7895,6 +7963,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -8141,6 +8218,36 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes 1.2.1", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.20", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "multipart", + "percent-encoding 2.1.0", + "pin-project 1.0.12", + "scoped-tls", + "serde 1.0.144", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.10", + "tower-service", + "tracing 0.1.36", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" From b00863d06ae4d745f66c04c1fb9e2f40d8748d38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 8 Sep 2022 15:38:57 +0000 Subject: [PATCH 0592/1995] 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 cfdcd29c71..a1604a1a36 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.56bee1c6b0d35b09adf023cf3cf0e5b9c4de6c23faba61afcc3e0bc8c426e778.wasm", - "tx_ibc.wasm": "tx_ibc.f82b5c0515e60c2d4cde106e9098ad3340250b4c003934e5b53a0c2806475f79.wasm", - "tx_init_account.wasm": "tx_init_account.970b1e7f79a8d43f60d58c4357aa45efc8cf4290feef5442e8365aab75ea863e.wasm", - "tx_init_nft.wasm": "tx_init_nft.d8994270e8815c22eb9c9d130ff5f36756540cfcebbf4feb2f95f317367ed481.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e5d15a9278e2d9355f0fc6ae707ad694e13f8d496b36ca367aa3883f0a6e621f.wasm", - "tx_init_validator.wasm": "tx_init_validator.4c45c31b1b494b6ce3a64122e9d87e2fed9757e5a6580d3f4d37e8a1600cdff7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.31d842637ec27da8010e184f2a2fdbcee141a3b62888dd432cb43c3113f787d5.wasm", - "tx_transfer.wasm": "tx_transfer.1c9ca5c5743669fc90ef3ed69cc6a7e463f264b238ae5b782d8d38aec9c40055.wasm", - "tx_unbond.wasm": "tx_unbond.5e928128233c6077099d53e11ae3161205c43347c3d7dd59554a4b2ef308428b.wasm", - "tx_update_vp.wasm": "tx_update_vp.f1cc672546b603b9d87f05dfe5f0ab32fba554a66715cf33b33b3e8bcc1cc543.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.2a43eb9af520bdc31affe43e2c31c0b5a1587e9a3a4659691077ab4cbf48e589.wasm", - "tx_withdraw.wasm": "tx_withdraw.d8ade8b8d41457fa4180e40784e7c0a6c1a61295f73f82a5c5226fcbc04c4038.wasm", - "vp_nft.wasm": "vp_nft.e3b467ba8f396c28abcdf54bc348c04d105b2f1c6249254b55b57bccd6646add.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.747aa4ee7f75462a96625a76d6a687cae2794b5fcd092c8ac1e94ae999efd7df.wasm", - "vp_token.wasm": "vp_token.d7a0d0a85fdcc5d87f07e4bea476156e21907ada630dacaa2f61a5dc7aa91597.wasm", - "vp_user.wasm": "vp_user.55a3302e0da13ca6f9ee2e8d072b4984fdfa0497141e4a96d63337419e2df84f.wasm" + "tx_from_intent.wasm": "tx_from_intent.49b59fd9a3c08ae28c020dfb83e024b1055dd1fd3993bf8e5588db4436574df5.wasm", + "tx_ibc.wasm": "tx_ibc.c249dc37fcb85f0b14e1f064845b820b80e265e4d3d2b85b991a5cad7a895338.wasm", + "tx_init_account.wasm": "tx_init_account.2aafd544cdc7c93e29a76bf3687d1d9468a1fbd3a6f281613304cb3723c2a2c2.wasm", + "tx_init_nft.wasm": "tx_init_nft.1a7c09d5f089c52cbe0c42bd496014a1077c5283b12b9088bb2cf43ae4fbf113.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ca2d00d720fda4aef32f1ea99c2927f4e787d798d7172e7378c2c3ef45122b02.wasm", + "tx_init_validator.wasm": "tx_init_validator.abfcf0b77a390d381b4ad7dc232a13390ad6e6f2d6925b0936c69df87e32cf4c.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.afcd165729194f79e8c8ba4cab135024e948473388a49a4f70cd2a001572f2fb.wasm", + "tx_transfer.wasm": "tx_transfer.befd5f7d2354dfceb5791b6958955bf4322e8fa750ee6e3c6a2ab0c3849ba3b8.wasm", + "tx_unbond.wasm": "tx_unbond.8405ec2f697850e75afd34ddb4a0a06c2ae8b1cf37fe6f81b159cac9f9f69a7d.wasm", + "tx_update_vp.wasm": "tx_update_vp.11940df2f7a4794b7e941a54a9f714757419266ad7e4c204759c1ec7823f4590.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.085a34328aa2379c58fe01ebe973e500fc46174cfbad6569a470ce78f3896e48.wasm", + "tx_withdraw.wasm": "tx_withdraw.1a5171753c2c168f10f0cf34af36669a22051a409abd64fbef208dff583f8375.wasm", + "vp_nft.wasm": "vp_nft.32e7d12194a63951267b0e586ae2d671e11b88f557056bf134a2be9768916412.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.192c09498cb8b94368b64345468cae7922321d9a36d62b472bc9a903277c7000.wasm", + "vp_token.wasm": "vp_token.01a0d21d392dab91c76e37f835eaf9a2fd83c4c7245e8f5d2db233cde16303ce.wasm", + "vp_user.wasm": "vp_user.10dde4c00de0e671d352d58e22d3d0e4bee06eedb23ac30171581dbd3e84d27d.wasm" } \ No newline at end of file From 47c2e384b98530fb7c2e11ecd1acf4c9d8c65973 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 6 Sep 2022 16:01:28 +0100 Subject: [PATCH 0593/1995] Remove the eth-fullnode feature flag --- Makefile | 2 +- apps/Cargo.toml | 1 - apps/src/lib/config/ethereum.rs | 5 +- .../lib/node/ledger/ethereum_node/events.rs | 3 - apps/src/lib/node/ledger/ethereum_node/mod.rs | 69 ++++++++++++++++--- .../lib/node/ledger/ethereum_node/oracle.rs | 2 - .../node/ledger/ethereum_node/test_tools.rs | 7 +- apps/src/lib/node/ledger/mod.rs | 45 +++++------- 8 files changed, 84 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index 0aa9843a9d..3474f6c8e6 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ check: clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings clippy: - ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets --features eth-fullnode -- -D warnings && \ + 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 diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 94178b46b7..8fba95ce85 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -44,7 +44,6 @@ dev = ["namada/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies testing = ["dev"] -eth-fullnode = [] [dependencies] namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand", "secp256k1-sign-verify"]} diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index db48f8ef45..aab0cfce5b 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -8,6 +8,8 @@ pub const DEFAULT_ORACLE_RPC_ENDPOINT: &str = "http://127.0.0.1:8545"; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { + /// Whether a managed `geth` subprocess should be started or not. + pub geth_startup: bool, /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use /// to listen for events from the Ethereum bridge smart contracts pub oracle_rpc_endpoint: String, @@ -15,15 +17,14 @@ pub struct Config { /// events at a Ethereum JSON-RPC endpoint, an endpoint will be exposed by /// the ledger for submission of Borsh-serialized /// [`EthereumEvent`]s - #[cfg(not(feature = "eth-fullnode"))] pub oracle_event_endpoint: bool, } impl Default for Config { fn default() -> Self { Self { + geth_startup: true, oracle_rpc_endpoint: DEFAULT_ORACLE_RPC_ENDPOINT.to_owned(), - #[cfg(not(feature = "eth-fullnode"))] oracle_event_endpoint: false, } } diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 8294272eb3..14b8c04ee4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "eth-fullnode")] pub mod signatures { pub const TRANSFER_TO_NAMADA_SIG: &str = "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; @@ -38,7 +37,6 @@ pub mod signatures { } } -#[cfg(feature = "eth-fullnode")] pub mod eth_events { use std::convert::TryInto; use std::fmt::Debug; @@ -916,5 +914,4 @@ pub mod eth_events { } } -#[cfg(feature = "eth-fullnode")] pub use eth_events::*; diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index d95d320a21..0b422fc91c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -28,15 +28,51 @@ pub enum Error { pub type Result = std::result::Result; -/// Run the Ethereum fullnode. If it stops or an abort -/// signal is sent, this processes is halted. -pub async fn run( - mut ethereum_node: EthereumNode, +/// Monitor the Ethereum fullnode. If it stops or an abort +/// signal is sent, the subprocess is halted. +pub async fn monitor( + ethereum_node: Subprocess, + abort_recv: Receiver>, +) -> Result<()> { + match ethereum_node { + Subprocess::Mock(node) => monitor_mock(node, abort_recv).await, + Subprocess::Geth(node) => monitor_real(node, abort_recv).await, + } +} + +async fn monitor_real( + mut ethereum_node: eth_fullnode::EthereumNode, + abort_recv: Receiver>, +) -> Result<()> { + tokio::select! { + // run the ethereum fullnode + status = ethereum_node.wait() => status, + // wait for an abort signal + resp_sender = abort_recv => { + match resp_sender { + Ok(resp_sender) => { + tracing::info!("Shutting down Ethereum fullnode..."); + ethereum_node.kill().await; + resp_sender.send(()).unwrap(); + }, + Err(err) => { + tracing::error!("The Ethereum abort sender has unexpectedly dropped: {}", err); + tracing::info!("Shutting down Ethereum fullnode..."); + ethereum_node.kill().await; + } + } + Ok(()) + } + } +} + +async fn monitor_mock( + mut ethereum_node: test_tools::mock_eth_fullnode::EthereumNode, abort_recv: Receiver>, ) -> Result<()> { tokio::select! { // run the ethereum fullnode - status = ethereum_node.wait() => status, + status = ethereum_node.wait() => status, // wait for an abort signal resp_sender = abort_recv => { match resp_sender { @@ -56,7 +92,6 @@ pub async fn run( } } -#[cfg(feature = "eth-fullnode")] /// Tools for running a geth fullnode process pub mod eth_fullnode { use std::time::Duration; @@ -196,7 +231,21 @@ pub mod eth_fullnode { } } -#[cfg(feature = "eth-fullnode")] -pub use eth_fullnode::EthereumNode; -#[cfg(not(feature = "eth-fullnode"))] -pub use test_tools::mock_eth_fullnode::EthereumNode; +/// Starts an Ethereum fullnode in a subprocess and returns a handle for +/// monitoring it using [`monitor`], as well as a channel for halting it. +pub async fn start(url: &str, real: bool) -> Result<(Subprocess, Sender<()>)> { + if real { + let (node, sender) = eth_fullnode::EthereumNode::new(url).await?; + Ok((Subprocess::Geth(node), sender)) + } else { + let (node, sender) = + test_tools::mock_eth_fullnode::EthereumNode::new().await?; + Ok((Subprocess::Mock(node), sender)) + } +} + +/// Represents a subprocess running an Ethereum full node +pub enum Subprocess { + Mock(test_tools::mock_eth_fullnode::EthereumNode), + Geth(eth_fullnode::EthereumNode), +} diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 2ed1944e25..919829a4ff 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "eth-fullnode")] pub mod oracle_process { use std::ops::Deref; @@ -530,5 +529,4 @@ pub mod oracle_process { } } -#[cfg(feature = "eth-fullnode")] pub use oracle_process::*; diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 77dc163c4f..cf6a477c37 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -1,4 +1,3 @@ -#[cfg(not(feature = "eth-fullnode"))] /// tools for running a mock ethereum fullnode process pub mod mock_eth_fullnode { use tokio::sync::oneshot::{channel, Receiver, Sender}; @@ -11,7 +10,7 @@ pub mod mock_eth_fullnode { } impl EthereumNode { - pub async fn new(_: &str) -> Result<(EthereumNode, Sender<()>)> { + pub async fn new() -> Result<(EthereumNode, Sender<()>)> { let (abort_sender, receiver) = channel(); Ok((Self { receiver }, abort_sender)) } @@ -24,7 +23,6 @@ pub mod mock_eth_fullnode { } } -#[cfg(not(feature = "eth-fullnode"))] pub mod mock_oracle { use namada::types::ethereum_events::EthereumEvent; @@ -47,7 +45,6 @@ pub mod mock_oracle { } } -#[cfg(not(feature = "eth-fullnode"))] pub mod event_endpoint { use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; @@ -110,7 +107,7 @@ pub mod event_endpoint { } } -#[cfg(all(test, feature = "eth-fullnode"))] +#[cfg(test)] pub mod mock_web3_client { use std::cell::RefCell; use std::fmt::Debug; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 2f4385bbb2..439e789666 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -14,7 +14,6 @@ use std::path::PathBuf; use std::str::FromStr; use byte_unit::Byte; -use ethereum_node::EthereumNode; use futures::future::TryFutureExt; use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; @@ -306,16 +305,17 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::sync::mpsc::unbounded_channel(); let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); - let (ethereum_node, oracle, broadcaster) = if matches!( + let (eth_node, oracle, broadcaster) = if matches!( config.tendermint.tendermint_mode, TendermintMode::Validator ) { // boot up the ethereum node process and wait for it to finish syncing let (eth_sender, eth_receiver) = unbounded_channel(); let url = ethereum_url.clone(); - let (ethereum_node, abort_sender) = EthereumNode::new(&url) - .await - .expect("Unable to start the Ethereum fullnode"); + let (eth_node, abort_sender) = + ethereum_node::start(&url, config.ethereum.geth_startup) + .await + .expect("Unable to start the Ethereum fullnode"); // Start Ethereum fullnode // Channel for signalling shut down to Tendermint process @@ -323,7 +323,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::sync::oneshot::channel::>(); let abort_send_for_eth = abort_send.clone(); // run geth in the background - let ethereum_node = tokio::spawn(async move { + let eth_node = tokio::spawn(async move { // On panic or exit, the `Drop` of `AbortSender` will send abort // message let aborter = Aborter { @@ -331,7 +331,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { who: "Ethereum", }; - let res = ethereum_node::run(ethereum_node, eth_abort_recv) + let res = ethereum_node::monitor(eth_node, eth_abort_recv) .map_err(Error::Ethereum) .await; tracing::info!("Ethereum fullnode is no longer running."); @@ -341,11 +341,16 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }); let oracle = { - #[cfg(not(feature = "eth-fullnode"))] if config.ethereum.oracle_event_endpoint { ethereum_node::test_tools::event_endpoint::start_oracle( eth_sender, ) + } else if config.ethereum.geth_startup { + ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ) } else { ethereum_node::test_tools::mock_oracle::run_oracle( ethereum_url, @@ -353,16 +358,10 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { abort_sender, ) } - #[cfg(feature = "eth-fullnode")] - ethereum_node::oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ) }; - // Shutdown ethereum_node via a message to ensure that the child process - // is properly cleaned-up. + // Shutdown the Ethereum node subprocess via a message to ensure that + // the child process is properly cleaned-up. let (eth_abort_resp_send, eth_abort_resp_recv) = tokio::sync::oneshot::channel::<()>(); let abort_send_for_broadcaster = abort_send.clone(); @@ -373,7 +372,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { ( Some(( - ethereum_node, + eth_node, eth_abort_send, eth_abort_resp_send, eth_abort_resp_recv, @@ -511,14 +510,14 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let res = if let ( Some(( - ethereum_node, + eth_node, eth_abort_send, eth_abort_resp_send, eth_abort_resp_recv, )), Some(oracle), Some((broadcaster, bc_abort_send)), - ) = (ethereum_node, oracle_proc, broadcaster) + ) = (eth_node, oracle_proc, broadcaster) { // Ask to shutdown tendermint node cleanly. Ignore error, which can // happen if the tendermint_node task has already finished. @@ -536,13 +535,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // request the broadcaster shutdown let _ = bc_abort_send.send(()); - tokio::try_join!( - tendermint_node, - ethereum_node, - oracle, - abci, - broadcaster - ) + tokio::try_join!(tendermint_node, eth_node, oracle, abci, broadcaster) } else { // if we are not a validator, the broadcaster service and Ethereum // fullnode are not active. Thus, we fill in their return values From bfdcb78d979aae3a867a35cabdd795007a2ccc86 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 6 Sep 2022 16:37:55 +0100 Subject: [PATCH 0594/1995] Unnest oracle.rs --- .../lib/node/ledger/ethereum_node/oracle.rs | 954 +++++++++--------- 1 file changed, 471 insertions(+), 483 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 919829a4ff..28fd09ccb5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,532 +1,520 @@ -pub mod oracle_process { - use std::ops::Deref; - - use clarity::Address; - use namada::types::ethereum_events::{EthAddress, EthereumEvent}; - use num256::Uint256; - use tokio::sync::mpsc::UnboundedSender; - use tokio::sync::oneshot::Sender; - use tokio::task::LocalSet; - #[cfg(not(test))] - use web30::client::Web3; - - use super::super::events::{signatures, PendingEvent}; - #[cfg(test)] - use super::super::test_tools::mock_web3_client::Web3; - - /// Minimum number of confirmations needed to trust an Ethereum branch - pub(crate) const MIN_CONFIRMATIONS: u64 = 50; - - /// Dummy addresses for smart contracts - const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); - const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); - - /// A client that can talk to geth and parse - /// and relay events relevant to Anoma to the - /// ledger process - pub struct Oracle { - /// The client that talks to the Ethereum fullnode - client: Web3, - /// A channel for sending processed and confirmed - /// events to the ledger process - sender: UnboundedSender, - /// A channel to signal that the ledger should shut down - /// because the Oracle has stopped - abort: Option>, - } +use std::ops::Deref; + +use clarity::Address; +use namada::types::ethereum_events::{EthAddress, EthereumEvent}; +use num256::Uint256; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::oneshot::Sender; +use tokio::task::LocalSet; +#[cfg(not(test))] +use web30::client::Web3; + +use super::events::{signatures, PendingEvent}; +#[cfg(test)] +use super::test_tools::mock_web3_client::Web3; + +/// Minimum number of confirmations needed to trust an Ethereum branch +pub(crate) const MIN_CONFIRMATIONS: u64 = 50; + +/// Dummy addresses for smart contracts +const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); +const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); + +/// A client that can talk to geth and parse +/// and relay events relevant to Anoma to the +/// ledger process +pub struct Oracle { + /// The client that talks to the Ethereum fullnode + client: Web3, + /// A channel for sending processed and confirmed + /// events to the ledger process + sender: UnboundedSender, + /// A channel to signal that the ledger should shut down + /// because the Oracle has stopped + abort: Option>, +} - impl Deref for Oracle { - type Target = Web3; +impl Deref for Oracle { + type Target = Web3; - fn deref(&self) -> &Self::Target { - &self.client - } + fn deref(&self) -> &Self::Target { + &self.client } +} - impl Drop for Oracle { - fn drop(&mut self) { - // send an abort signal to shut down the - // rest of the ledger gracefully - let abort = self.abort.take().unwrap(); - let _ = abort.send(()); - } +impl Drop for Oracle { + fn drop(&mut self) { + // send an abort signal to shut down the + // rest of the ledger gracefully + let abort = self.abort.take().unwrap(); + let _ = abort.send(()); } +} - impl Oracle { - /// Initialize a new [`Oracle`] - pub fn new( - url: &str, - sender: UnboundedSender, - abort: Sender<()>, - ) -> Self { - Self { - client: Web3::new(url, std::time::Duration::from_secs(30)), - sender, - abort: Some(abort), - } - } - - /// Send a series of [`EthereumEvent`]s to the Anoma - /// ledger. Returns a boolean indicating that all sent - /// successfully. If false is returned, the receiver - /// has hung up. - fn send(&self, events: Vec) -> bool { - events - .into_iter() - .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) - && !self.sender.is_closed() +impl Oracle { + /// Initialize a new [`Oracle`] + pub fn new( + url: &str, + sender: UnboundedSender, + abort: Sender<()>, + ) -> Self { + Self { + client: Web3::new(url, std::time::Duration::from_secs(30)), + sender, + abort: Some(abort), } + } - /// Check if the receiver in the ledger has hung up. - /// Used to help determine when to stop the oracle - fn connected(&self) -> bool { - !self.sender.is_closed() - } + /// Send a series of [`EthereumEvent`]s to the Anoma + /// ledger. Returns a boolean indicating that all sent + /// successfully. If false is returned, the receiver + /// has hung up. + fn send(&self, events: Vec) -> bool { + events + .into_iter() + .map(|event| self.sender.send(event)) + .all(|res| res.is_ok()) + && !self.sender.is_closed() } - /// Set up an Oracle and run the process where the Oracle - /// processes and forwards Ethereum events to the ledger - pub fn run_oracle( - url: impl AsRef, - sender: UnboundedSender, - abort_sender: Sender<()>, - ) -> tokio::task::JoinHandle<()> { - let url = url.as_ref().to_owned(); - // we have to run the oracle in a [`LocalSet`] due to the web30 - // crate - tokio::task::spawn_blocking(move || { - let rt = tokio::runtime::Handle::current(); - rt.block_on(async move { - LocalSet::new() - .run_until(async move { - tracing::info!( - ?url, - "Ethereum event oracle is starting" - ); - - let oracle = Oracle::new(&url, sender, abort_sender); - run_oracle_aux(oracle).await; - - tracing::info!( - ?url, - "Ethereum event oracle is no longer running" - ); - }) - .await - }); - }) + /// Check if the receiver in the ledger has hung up. + /// Used to help determine when to stop the oracle + fn connected(&self) -> bool { + !self.sender.is_closed() } +} + +/// Set up an Oracle and run the process where the Oracle +/// processes and forwards Ethereum events to the ledger +pub fn run_oracle( + url: impl AsRef, + sender: UnboundedSender, + abort_sender: Sender<()>, +) -> tokio::task::JoinHandle<()> { + let url = url.as_ref().to_owned(); + // we have to run the oracle in a [`LocalSet`] due to the web30 + // crate + tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async move { + LocalSet::new() + .run_until(async move { + tracing::info!(?url, "Ethereum event oracle is starting"); + + let oracle = Oracle::new(&url, sender, abort_sender); + run_oracle_aux(oracle).await; - /// Given an oracle, watch for new Ethereum events, processing - /// them into Anoma native types. - /// - /// It also checks that once the specified number of confirmations - /// is reached, an event is forwarded to the ledger process - async fn run_oracle_aux(oracle: Oracle) { - // Initialize our local state. This includes - // the latest block height seen and a queue of events - // awaiting a certain number of confirmations - let mut latest_block; - let mut pending: Vec = Vec::new(); - loop { - // update the latest block height - latest_block = loop { - if let Ok(height) = oracle.eth_block_number().await { - break height; - } - if !oracle.connected() { tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" + ?url, + "Ethereum event oracle is no longer running" ); - return; - } + }) + .await + }); + }) +} + +/// Given an oracle, watch for new Ethereum events, processing +/// them into Anoma native types. +/// +/// It also checks that once the specified number of confirmations +/// is reached, an event is forwarded to the ledger process +async fn run_oracle_aux(oracle: Oracle) { + // Initialize our local state. This includes + // the latest block height seen and a queue of events + // awaiting a certain number of confirmations + let mut latest_block; + let mut pending: Vec = Vec::new(); + loop { + // update the latest block height + latest_block = loop { + if let Ok(height) = oracle.eth_block_number().await { + break height; + } + if !oracle.connected() { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return; + } + }; + // No blocks in existence yet with enough confirmations + if Uint256::from(MIN_CONFIRMATIONS) > latest_block { + if !oracle.connected() { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return; + } + continue; + } + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` + // confirmations. + for sig in signatures::SIGNATURES { + let addr: Address = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => MINT_CONTRACT.0.into(), + signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" - ); - return; + // fetch the events for matching the given signature + let mut events = loop { + if let Ok(pending) = oracle + .check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) + .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) + .ok() + }) + .collect::>() + }) + { + break pending; } - continue; - } - let block_to_check = - latest_block.clone() - MIN_CONFIRMATIONS.into(); - // check for events with at least `[MIN_CONFIRMATIONS]` - // confirmations. - for sig in signatures::SIGNATURES { - let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => { - GOVERNANCE_CONTRACT.0.into() - } - }; - // fetch the events for matching the given signature - let mut events = loop { - if let Ok(pending) = oracle - .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), - vec![addr], - vec![sig], - ) - .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) - { - break pending; - } - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the \ - ledger; the receiver has hung up. Shutting down" - ); - return; - } - }; - pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)) { + if !oracle.connected() { tracing::info!( "Ethereum oracle could not send events to the ledger; \ the receiver has hung up. Shutting down" ); return; } + }; + pending.append(&mut events); + if !oracle.send(process_queue(&latest_block, &mut pending)) { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return; } } } +} - /// Check which events in the queue have reached their - /// required number of confirmations and remove them - /// from the queue of pending events - fn process_queue( - latest_block: &Uint256, - pending: &mut Vec, - ) -> Vec { - let mut pending_tmp: Vec = - Vec::with_capacity(pending.len()); - std::mem::swap(&mut pending_tmp, pending); - let mut confirmed = vec![]; - for item in pending_tmp.into_iter() { - if item.is_confirmed(latest_block) { - confirmed.push(item.event); - } else { - pending.push(item); - } +/// Check which events in the queue have reached their +/// required number of confirmations and remove them +/// from the queue of pending events +fn process_queue( + latest_block: &Uint256, + pending: &mut Vec, +) -> Vec { + let mut pending_tmp: Vec = Vec::with_capacity(pending.len()); + std::mem::swap(&mut pending_tmp, pending); + let mut confirmed = vec![]; + for item in pending_tmp.into_iter() { + if item.is_confirmed(latest_block) { + confirmed.push(item.event); + } else { + pending.push(item); } - confirmed } + confirmed +} - #[cfg(test)] - mod test_oracle { - use namada::types::ethereum_events::TransferToEthereum; - use tokio::sync::oneshot::{channel, Receiver}; +#[cfg(test)] +mod test_oracle { + use namada::types::ethereum_events::TransferToEthereum; + use tokio::sync::oneshot::{channel, Receiver}; + + use super::*; + use crate::node::ledger::ethereum_node::events::{ + ChangedContract, RawTransfersToEthereum, + }; + use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::{ + MockEventType, TestCmd, Web3, + }; + + /// The data returned from setting up a test + struct TestPackage { + oracle: Oracle, + admin_channel: tokio::sync::mpsc::UnboundedSender, + eth_recv: tokio::sync::mpsc::UnboundedReceiver, + abort_recv: Receiver<()>, + } - use super::*; - use crate::node::ledger::ethereum_node::events::{ - ChangedContract, RawTransfersToEthereum, - }; - use crate::node::ledger::ethereum_node::test_tools::mock_web3_client::{MockEventType, TestCmd, Web3}; - - /// The data returned from setting up a test - struct TestPackage { - oracle: Oracle, - admin_channel: tokio::sync::mpsc::UnboundedSender, - eth_recv: tokio::sync::mpsc::UnboundedReceiver, - abort_recv: Receiver<()>, + /// Set up an oracle with a mock web3 client that we can contr + fn setup() -> TestPackage { + let (admin_channel, client) = Web3::setup(); + let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (abort, abort_recv) = channel(); + TestPackage { + oracle: Oracle { + client, + sender: eth_sender, + abort: Some(abort), + }, + admin_channel, + eth_recv: eth_receiver, + abort_recv, } + } - /// Set up an oracle with a mock web3 client that we can contr - fn setup() -> TestPackage { - let (admin_channel, client) = Web3::setup(); - let (eth_sender, eth_receiver) = - tokio::sync::mpsc::unbounded_channel(); - let (abort, abort_recv) = channel(); - TestPackage { - oracle: Oracle { - client, - sender: eth_sender, - abort: Some(abort), - }, - admin_channel, - eth_recv: eth_receiver, - abort_recv, - } - } + /// Test that if the oracle shuts down, it + /// sends a message to the fullnode to stop + #[test] + fn test_abort_send() { + let TestPackage { + oracle, + mut abort_recv, + .. + } = setup(); + drop(oracle); + assert!(abort_recv.try_recv().is_ok()) + } - /// Test that if the oracle shuts down, it - /// sends a message to the fullnode to stop - #[test] - fn test_abort_send() { - let TestPackage { - oracle, - mut abort_recv, - .. - } = setup(); - drop(oracle); - assert!(abort_recv.try_recv().is_ok()) - } + /// Test that if the fullnode stops, the oracle + /// shuts down, even if the web3 client is unresponsive + #[test] + fn test_shutdown() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); + drop(eth_recv); + oracle.join().expect("Test failed"); + } - /// Test that if the fullnode stops, the oracle - /// shuts down, even if the web3 client is unresponsive - #[test] - fn test_shutdown() { - let TestPackage { - oracle, - eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); - drop(eth_recv); - oracle.join().expect("Test failed"); + /// Test that if no logs are received from the web3 + /// client, no events are sent out + #[test] + fn test_no_logs_no_op() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(100u32))) + .expect("Test failed"); + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); } + drop(eth_recv); + oracle.join().expect("Test failed"); + } - /// Test that if no logs are received from the web3 - /// client, no events are sent out - #[test] - fn test_no_logs_no_op() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - admin_channel - .send(TestCmd::NewHeight(Uint256::from(100u32))) - .expect("Test failed"); - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); - } - drop(eth_recv); - oracle.join().expect("Test failed"); + /// Test that if a new block height doesn't increase, + /// no events are sent out even if there are + /// some in the logs. + #[test] + fn test_cant_get_new_height() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: new_event, + height: 51, + }) + .expect("Test failed"); + // since height is not updating, we should not receive events + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); } + drop(eth_recv); + oracle.join().expect("Test failed"); + } - /// Test that if a new block height doesn't increase, - /// no events are sent out even if there are - /// some in the logs. - #[test] - fn test_cant_get_new_height() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - let new_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: new_event, - height: 51, - }) - .expect("Test failed"); - // since height is not updating, we should not receive events - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); - } - drop(eth_recv); - oracle.join().expect("Test failed"); + /// Test that the oracle waits until new logs + /// are received before sending them on. + #[test] + fn test_wait_on_new_logs() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + let new_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: new_event, + height: 100, + }) + .expect("Test failed"); + + // we should not receive events even though the height is large + // enough + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); + admin_channel + .send(TestCmd::NewHeight(Uint256::from(101u32))) + .expect("Test failed"); + + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } + // check that when web3 becomes responsive, oracle sends event + admin_channel.send(TestCmd::Normal).expect("Test failed"); + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract { name, address } = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address.0, [0; 20]); + } else { + panic!("Test failed"); } + drop(eth_recv); + oracle.join().expect("Test failed"); + } - /// Test that the oracle waits until new logs - /// are received before sending them on. - #[test] - fn test_wait_on_new_logs() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - let new_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: new_event, - height: 100, - }) - .expect("Test failed"); - - // we should not receive events even though the height is large - // enough - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); - admin_channel - .send(TestCmd::NewHeight(Uint256::from(101u32))) - .expect("Test failed"); - - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); - } - // check that when web3 becomes responsive, oracle sends event - admin_channel.send(TestCmd::Normal).expect("Test failed"); - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract { name, address } = event { - assert_eq!(name.as_str(), "Test"); - assert_eq!(address.0, [0; 20]); - } else { - panic!("Test failed"); - } - drop(eth_recv); - oracle.join().expect("Test failed"); + /// Test that events are only sent when they + /// reach the required number of confirmations + #[test] + fn test_finality_gadget() { + let TestPackage { + oracle, + mut eth_recv, + admin_channel, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + // Increase height above [`MIN_CONFIRMATIONS`] + admin_channel + .send(TestCmd::NewHeight(50u32.into())) + .expect("Test failed"); + + // confirmed after 50 blocks + let first_event = ChangedContract { + name: "Test".to_string(), + address: EthAddress([0; 20]), + } + .encode(); + + // confirmed after 75 blocks + let second_event = RawTransfersToEthereum { + transfers: vec![TransferToEthereum { + amount: Default::default(), + asset: EthAddress([0; 20]), + receiver: EthAddress([1; 20]), + }], + nonce: 1.into(), + confirmations: 75, + } + .encode(); + + // send in the events to the logs + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::TransferToEthereum, + data: second_event, + height: 125, + }) + .expect("Test failed"); + admin_channel + .send(TestCmd::NewEvent { + event_type: MockEventType::NewContract, + data: first_event, + height: 100, + }) + .expect("Test failed"); + + // increase block height so first event is confirmed but second is + // not. + admin_channel + .send(TestCmd::NewHeight(Uint256::from(102u32))) + .expect("Test failed"); + // check the correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::NewContract { name, address } = event { + assert_eq!(name.as_str(), "Test"); + assert_eq!(address, EthAddress([0; 20])); + } else { + panic!("Test failed, {:?}", event); } - /// Test that events are only sent when they - /// reach the required number of confirmations - #[test] - fn test_finality_gadget() { - let TestPackage { - oracle, - mut eth_recv, - admin_channel, - .. - } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); - // Increase height above [`MIN_CONFIRMATIONS`] - admin_channel - .send(TestCmd::NewHeight(50u32.into())) - .expect("Test failed"); - - // confirmed after 50 blocks - let first_event = ChangedContract { - name: "Test".to_string(), - address: EthAddress([0; 20]), - } - .encode(); + // check no other events are received + let mut time = std::time::Duration::from_secs(1); + while time > std::time::Duration::from_millis(10) { + assert!(eth_recv.try_recv().is_err()); + time -= std::time::Duration::from_millis(10); + } - // confirmed after 75 blocks - let second_event = RawTransfersToEthereum { - transfers: vec![TransferToEthereum { + // increase block height so second event is confirmed + admin_channel + .send(TestCmd::NewHeight(Uint256::from(130u32))) + .expect("Test failed"); + // check correct event is received + let event = eth_recv.blocking_recv().expect("Test failed"); + if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event + { + assert_eq!(transfers.len(), 1); + let transfer = transfers.remove(0); + assert_eq!( + transfer, + TransferToEthereum { amount: Default::default(), asset: EthAddress([0; 20]), receiver: EthAddress([1; 20]), - }], - nonce: 1.into(), - confirmations: 75, - } - .encode(); - - // send in the events to the logs - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::TransferToEthereum, - data: second_event, - height: 125, - }) - .expect("Test failed"); - admin_channel - .send(TestCmd::NewEvent { - event_type: MockEventType::NewContract, - data: first_event, - height: 100, - }) - .expect("Test failed"); - - // increase block height so first event is confirmed but second is - // not. - admin_channel - .send(TestCmd::NewHeight(Uint256::from(102u32))) - .expect("Test failed"); - // check the correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract { name, address } = event { - assert_eq!(name.as_str(), "Test"); - assert_eq!(address, EthAddress([0; 20])); - } else { - panic!("Test failed, {:?}", event); - } - - // check no other events are received - let mut time = std::time::Duration::from_secs(1); - while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); - time -= std::time::Duration::from_millis(10); - } - - // increase block height so second event is confirmed - admin_channel - .send(TestCmd::NewHeight(Uint256::from(130u32))) - .expect("Test failed"); - // check correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::TransfersToEthereum { - mut transfers, .. - } = event - { - assert_eq!(transfers.len(), 1); - let transfer = transfers.remove(0); - assert_eq!( - transfer, - TransferToEthereum { - amount: Default::default(), - asset: EthAddress([0; 20]), - receiver: EthAddress([1; 20]), - } - ); - } else { - panic!("Test failed"); - } - - drop(eth_recv); - oracle.join().expect("Test failed"); + } + ); + } else { + panic!("Test failed"); } + + drop(eth_recv); + oracle.join().expect("Test failed"); } } - -pub use oracle_process::*; From 8cccbbfce5715ae855caf30fa310f28a63d02069 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 6 Sep 2022 17:07:06 +0100 Subject: [PATCH 0595/1995] `geth_startup` takes precedence over `oracle_event_endpoint` --- apps/src/lib/node/ledger/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 439e789666..45963dd708 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -341,16 +341,16 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }); let oracle = { - if config.ethereum.oracle_event_endpoint { - ethereum_node::test_tools::event_endpoint::start_oracle( - eth_sender, - ) - } else if config.ethereum.geth_startup { + if config.ethereum.geth_startup { ethereum_node::oracle::run_oracle( ethereum_url, eth_sender, abort_sender, ) + } else if config.ethereum.oracle_event_endpoint { + ethereum_node::test_tools::event_endpoint::start_oracle( + eth_sender, + ) } else { ethereum_node::test_tools::mock_oracle::run_oracle( ethereum_url, From 2cc3567de24603310b67ce3da442c1c15aa2ba4e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 12:06:22 +0100 Subject: [PATCH 0596/1995] Add Monitorable async trait --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 92 ++++++++----------- .../node/ledger/ethereum_node/test_tools.rs | 9 +- 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 0b422fc91c..1eb3c03025 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -3,6 +3,7 @@ pub mod oracle; pub mod test_tools; use std::ffi::OsString; +use async_trait::async_trait; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; @@ -28,6 +29,25 @@ pub enum Error { pub type Result = std::result::Result; +/// Represents a subprocess running an Ethereum full node +pub enum Subprocess { + Mock(test_tools::mock_eth_fullnode::EthereumNode), + Geth(eth_fullnode::EthereumNode), +} + +/// Starts an Ethereum fullnode in a subprocess and returns a handle for +/// monitoring it using [`monitor`], as well as a channel for halting it. +pub async fn start(url: &str, real: bool) -> Result<(Subprocess, Sender<()>)> { + if real { + let (node, sender) = eth_fullnode::EthereumNode::new(url).await?; + Ok((Subprocess::Geth(node), sender)) + } else { + let (node, sender) = + test_tools::mock_eth_fullnode::EthereumNode::new().await?; + Ok((Subprocess::Mock(node), sender)) + } +} + /// Monitor the Ethereum fullnode. If it stops or an abort /// signal is sent, the subprocess is halted. pub async fn monitor( @@ -35,56 +55,37 @@ pub async fn monitor( abort_recv: Receiver>, ) -> Result<()> { match ethereum_node { - Subprocess::Mock(node) => monitor_mock(node, abort_recv).await, - Subprocess::Geth(node) => monitor_real(node, abort_recv).await, + Subprocess::Mock(node) => monitor_node(node, abort_recv).await, + Subprocess::Geth(node) => monitor_node(node, abort_recv).await, } } -async fn monitor_real( - mut ethereum_node: eth_fullnode::EthereumNode, - abort_recv: Receiver>, -) -> Result<()> { - tokio::select! { - // run the ethereum fullnode - status = ethereum_node.wait() => status, - // wait for an abort signal - resp_sender = abort_recv => { - match resp_sender { - Ok(resp_sender) => { - tracing::info!("Shutting down Ethereum fullnode..."); - ethereum_node.kill().await; - resp_sender.send(()).unwrap(); - }, - Err(err) => { - tracing::error!("The Ethereum abort sender has unexpectedly dropped: {}", err); - tracing::info!("Shutting down Ethereum fullnode..."); - ethereum_node.kill().await; - } - } - Ok(()) - } - } +/// A handle on an Ethereum full node subprocess for monitoring it +#[async_trait] +pub trait Monitorable { + async fn wait(&mut self) -> Result<()>; + async fn kill(&mut self); } -async fn monitor_mock( - mut ethereum_node: test_tools::mock_eth_fullnode::EthereumNode, +async fn monitor_node( + mut node: impl Monitorable, abort_recv: Receiver>, ) -> Result<()> { tokio::select! { // run the ethereum fullnode - status = ethereum_node.wait() => status, + status = node.wait() => status, // wait for an abort signal resp_sender = abort_recv => { match resp_sender { Ok(resp_sender) => { tracing::info!("Shutting down Ethereum fullnode..."); - ethereum_node.kill().await; + node.kill().await; resp_sender.send(()).unwrap(); }, Err(err) => { tracing::error!("The Ethereum abort sender has unexpectedly dropped: {}", err); tracing::info!("Shutting down Ethereum fullnode..."); - ethereum_node.kill().await; + node.kill().await; } } Ok(()) @@ -96,13 +97,14 @@ async fn monitor_mock( pub mod eth_fullnode { use std::time::Duration; + use async_trait::async_trait; use tokio::process::{Child, Command}; use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::task::LocalSet; use web30::client::Web3; - use super::{Error, Result}; + use super::{Error, Monitorable, Result}; /// A handle to a running geth process and a channel /// that indicates it should shut down if the oracle @@ -199,11 +201,14 @@ pub mod eth_fullnode { }) .await } + } + #[async_trait] + impl Monitorable for EthereumNode { /// Wait for the process to finish or an abort message was /// received from the Oracle process. If either, return the /// status. - pub async fn wait(&mut self) -> Result<()> { + async fn wait(&mut self) -> Result<()> { loop { match self.process.try_wait() { Ok(Some(status)) => { @@ -225,27 +230,8 @@ pub mod eth_fullnode { } /// Stop the geth process - pub async fn kill(&mut self) { + async fn kill(&mut self) { self.process.kill().await.unwrap(); } } } - -/// Starts an Ethereum fullnode in a subprocess and returns a handle for -/// monitoring it using [`monitor`], as well as a channel for halting it. -pub async fn start(url: &str, real: bool) -> Result<(Subprocess, Sender<()>)> { - if real { - let (node, sender) = eth_fullnode::EthereumNode::new(url).await?; - Ok((Subprocess::Geth(node), sender)) - } else { - let (node, sender) = - test_tools::mock_eth_fullnode::EthereumNode::new().await?; - Ok((Subprocess::Mock(node), sender)) - } -} - -/// Represents a subprocess running an Ethereum full node -pub enum Subprocess { - Mock(test_tools::mock_eth_fullnode::EthereumNode), - Geth(eth_fullnode::EthereumNode), -} diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index cf6a477c37..c6184bda99 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -1,8 +1,10 @@ /// tools for running a mock ethereum fullnode process pub mod mock_eth_fullnode { + use async_trait::async_trait; use tokio::sync::oneshot::{channel, Receiver, Sender}; use super::super::Result; + use crate::node::ledger::ethereum_node::Monitorable; pub struct EthereumNode { #[allow(dead_code)] @@ -14,12 +16,15 @@ pub mod mock_eth_fullnode { let (abort_sender, receiver) = channel(); Ok((Self { receiver }, abort_sender)) } + } - pub async fn wait(&mut self) -> Result<()> { + #[async_trait] + impl Monitorable for EthereumNode { + async fn wait(&mut self) -> Result<()> { std::future::pending().await } - pub async fn kill(&mut self) {} + async fn kill(&mut self) {} } } From 8c7728b76b924e4d3855c75bcfdb9ee32eaed659 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 12:54:59 +0100 Subject: [PATCH 0597/1995] Add ledger.ethereum.mode config option --- apps/src/lib/config/ethereum.rs | 29 ++++++++++++++++++++--------- apps/src/lib/node/ledger/mod.rs | 24 +++++++++++++----------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index aab0cfce5b..52d757c67b 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -6,26 +6,37 @@ use serde::{Deserialize, Serialize}; /// Default [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) endpoint used by the oracle pub const DEFAULT_ORACLE_RPC_ENDPOINT: &str = "http://127.0.0.1:8545"; +/// The mode in which to run the Ethereum bridge. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Mode { + /// Run `geth` in a subprocess, exposing an Ethereum + /// JSON-RPC endpoint at [`DEFAULT_ORACLE_RPC_ENDPOINT`]. By default, the + /// oracle is configured to listen for events from the Ethereum bridge + /// smart contracts using this endpoint. + Managed, + /// Do not start a managed `geth` subprocess. Instead of the oracle + /// listening for events using a Ethereum JSON-RPC endpoint, an endpoint + /// will be exposed by the ledger itself for submission of Borsh- + /// serialized [`EthereumEvent`]s. Mostly useful for testing purposes. + EventsEndpoint, + /// Do not run any components of the Ethereum bridge + Off, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { - /// Whether a managed `geth` subprocess should be started or not. - pub geth_startup: bool, + /// The mode in which to run the Ethereum bridge + pub mode: Mode, /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use /// to listen for events from the Ethereum bridge smart contracts pub oracle_rpc_endpoint: String, - /// If this is set to `true`, then instead of the oracle listening for - /// events at a Ethereum JSON-RPC endpoint, an endpoint will be exposed by - /// the ledger for submission of Borsh-serialized - /// [`EthereumEvent`]s - pub oracle_event_endpoint: bool, } impl Default for Config { fn default() -> Self { Self { - geth_startup: true, + mode: Mode::Managed, oracle_rpc_endpoint: DEFAULT_ORACLE_RPC_ENDPOINT.to_owned(), - oracle_event_endpoint: false, } } } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 45963dd708..25af7b52cc 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -26,7 +26,7 @@ use tower_abci::{response, split, Server}; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; -use crate::config::TendermintMode; +use crate::config::{ethereum, TendermintMode}; use crate::node::ledger::broadcaster::Broadcaster; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; @@ -312,8 +312,10 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // boot up the ethereum node process and wait for it to finish syncing let (eth_sender, eth_receiver) = unbounded_channel(); let url = ethereum_url.clone(); + let start_managed_eth_node = + matches!(config.ethereum.mode, ethereum::Mode::Managed); let (eth_node, abort_sender) = - ethereum_node::start(&url, config.ethereum.geth_startup) + ethereum_node::start(&url, start_managed_eth_node) .await .expect("Unable to start the Ethereum fullnode"); @@ -340,18 +342,18 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { res }); - let oracle = { - if config.ethereum.geth_startup { - ethereum_node::oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ) - } else if config.ethereum.oracle_event_endpoint { + let oracle = match config.ethereum.mode { + ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ), + ethereum::Mode::EventsEndpoint => { ethereum_node::test_tools::event_endpoint::start_oracle( eth_sender, ) - } else { + } + ethereum::Mode::Off => { ethereum_node::test_tools::mock_oracle::run_oracle( ethereum_url, eth_sender, From d57ecfe41f2644b8c32dde5c3c68c2def27a6530 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 9 Sep 2022 10:29:53 +0200 Subject: [PATCH 0598/1995] [feat]: Merged in the arse merkle trees --- Cargo.lock | 24 +- 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 | 628 ++++++++++++++++---- shared/src/ledger/storage/mockdb.rs | 12 +- shared/src/ledger/storage/mod.rs | 2 + shared/src/types/hash.rs | 64 +- shared/src/types/storage.rs | 95 ++- wasm/wasm_source/Cargo.lock | 2 +- 11 files changed, 686 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1be76213bb..1641f7587e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3009,7 +3009,7 @@ dependencies = [ "derive_more", "flex-error", "ibc-proto", - "ics23", + "ics23 0.6.7", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -3056,6 +3056,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "ics23" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" +dependencies = [ + "anyhow", + "bytes 1.1.0", + "hex", + "prost 0.9.0", + "ripemd160", + "sha2 0.9.9", + "sha3 0.9.1", + "sp-std", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -4396,7 +4412,7 @@ dependencies = [ "hex", "ibc", "ibc-proto", - "ics23", + "ics23 0.6.7", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -6821,12 +6837,12 @@ 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#04ad1eeb28901b57a7599bbe433b3822965dabe8" dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23", + "ics23 0.7.0", "sha2 0.9.9", ] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 8fba95ce85..0196e640d0 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -47,6 +47,7 @@ testing = ["dev"] [dependencies] namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand", "secp256k1-sign-verify"]} +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "bat/arse-merkle-tree", features = ["std", "borsh"]} ark-serialize = "0.3.0" ark-std = "0.3.0" async-std = {version = "1.9.0", features = ["unstable"]} @@ -103,7 +104,6 @@ serde_json = {version = "1.0.62", features = ["raw_value"]} serde_regex = "1.1.0" sha2 = "0.9.3" signal-hook = "0.3.9" -sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", features = ["borsh"]} # sysinfo with disabled multithread feature sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 25929ba17e..2876236bca 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -5,11 +5,11 @@ mod rocksdb; use std::fmt; +use arse_merkle_tree::blake2b::Blake2bHasher; +use arse_merkle_tree::traits::Hasher; +use arse_merkle_tree::H256; use blake2b_rs::{Blake2b, Blake2bBuilder}; use namada::ledger::storage::{Storage, StorageHasher}; -use sparse_merkle_tree::blake2b::Blake2bHasher; -use sparse_merkle_tree::traits::Hasher; -use sparse_merkle_tree::H256; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); @@ -19,8 +19,8 @@ pub type PersistentDB = rocksdb::RocksDB; pub type PersistentStorage = Storage; impl Hasher for PersistentStorageHasher { - fn write_h256(&mut self, h: &H256) { - self.0.write_h256(h) + fn write_bytes(&mut self, h: &[u8]) { + self.0.write_bytes(h) } fn finish(self) -> H256 { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..6af0011dcc 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -348,11 +348,8 @@ impl DB for RocksDB { types::decode(bytes) .map_err(Error::CodingError)?, ), - Some(&"store") => merkle_tree_stores.set_store( - &st, - types::decode(bytes) - .map_err(Error::CodingError)?, - ), + Some(&"store") => merkle_tree_stores + .set_store(st.decode_store(bytes)?), _ => unknown_key_error(path)?, } } @@ -484,7 +481,7 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; batch.put( store_key.to_string(), - types::encode(merkle_tree_stores.store(st)), + merkle_tree_stores.store(st).encode(), ); } } @@ -593,8 +590,7 @@ impl DB for RocksDB { .map_err(|e| Error::DBError(e.into_string()))?; match bytes { Some(b) => { - let store = types::decode(b).map_err(Error::CodingError)?; - merkle_tree_stores.set_store(st, store); + merkle_tree_stores.set_store(st.decode_store(b)?); } None => return Ok(None), } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ff22c0911b..0f3c469b6d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -52,6 +52,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" @@ -87,7 +89,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", features = ["secp256k1"]} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index fa0f4a011f..c7fec10126 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -1,44 +1,63 @@ //! 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, Value}; +use arse_merkle_tree::{ + Hash as SmtHash, Key as TreeKey, 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 super::IBC_KEY_LIMIT; 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, StringKey, TreeBytes, +}; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Invalid key: {0}")] InvalidKey(StorageError), + #[error("Invalid key for merkle tree: {0}")] + 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), + #[error("Non-existence proofs not supported for store type: {0}")] + NonExistenceProof(String), } /// Result for functions that may fail 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 Smt = ArseMerkleTree; +pub type Amt = + ArseMerkleTree; + /// Store types for the merkle tree #[derive( Clone, @@ -62,6 +81,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 +170,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 +222,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 +240,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 +253,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: Either, ) -> 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.unwrap_left()) + .map_err(Error::MerkleTree)?, + StoreType::Ibc => self + .ibc + .update(key.try_into()?, value.unwrap_right()) + .map_err(Error::MerkleTree)?, + StoreType::PoS => self + .pos + .update(key.try_into()?, value.unwrap_left()) + .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 +296,55 @@ 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()))?; - Ok(!value.is_zero()) + let value = match self.tree(&store_type) { + Either::Left(smt) => { + 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)?.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 (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; + 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 (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; + 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 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 +355,40 @@ 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 = + 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 + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + leaf: Some(self.ibc_leaf_spec()), + ..ep + })), + }, + _ => unreachable!(), + } + } }; self.get_proof(key, sub_proof) } @@ -265,22 +396,35 @@ 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(_) => { + return Err(Error::NonExistenceProof(store_type.to_string())); + } + Either::Right(amt) => { + let key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = amt.non_membership_proof(&key)?; + // 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(self.ibc_leaf_spec()); + } + _ => unreachable!(), + } + } + nep + } }; + // Get a proof of the sub tree self.get_proof(key, sub_proof) } @@ -304,7 +448,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 +480,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() @@ -348,6 +492,20 @@ 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 { @@ -372,6 +530,20 @@ impl MerkleTree { 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(), + length: LengthOp::NoPrefix.into(), + prefix: H256::zero().as_slice().to_vec(), + } + } } /// The root hash of the merkle tree as bytes @@ -383,24 +555,70 @@ 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 | StoreType::Account | StoreType::PoS => { + MerkleKey::Sha256(key, PhantomData) + } + StoreType::Ibc => MerkleKey::Raw(key), + } + } +} + +impl TryFrom> for SmtHash { + type Error = Error; + + fn try_from(value: MerkleKey) -> Result { + match value { + MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), + _ => Err(Error::InvalidMerkleKey( + "This key is for a sparse merkle tree".into(), + )), + } + } +} + +impl TryFrom> for StringKey { + type Error = Error; + + fn try_from(value: MerkleKey) -> Result { + match value { + MerkleKey::Raw(key) => { + Self::try_from_bytes(key.to_string().as_bytes()) + } + _ => Err(Error::InvalidMerkleKey( + "This is not an key for the IBC subtree".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,47 +628,82 @@ 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), + } + } +} + +impl TreeKey for StringKey { + type Error = Error; + + fn as_slice(&self) -> &[u8] { + &self.original.as_slice()[..self.length] + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + 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 { + return Err(Error::InvalidMerkleKey( + "Input IBC key is too large".into(), + )); + } + original[i] = *byte; + tree_key[i] = byte.wrapping_add(1); + length += 1; } + Ok(Self { + original, + tree_key: tree_key.into(), + length, + }) + } +} + +impl Value for TreeBytes { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + TreeBytes::zero() } } @@ -462,19 +715,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 +761,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 +816,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()); @@ -567,7 +825,7 @@ mod test { } #[test] - fn test_proof() { + fn test_ibc_existence_proof() { let mut tree = MerkleTree::::default(); let key_prefix: Key = @@ -582,7 +840,7 @@ 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(); @@ -615,4 +873,118 @@ mod test { // 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_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_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 nep_verification_res = ics23::verify_non_membership( + &nep_commitment_proof, + &specs[0], + &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/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..99260d8333 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -105,11 +105,8 @@ impl DB for MockDB { types::decode(bytes) .map_err(Error::CodingError)?, ), - Some(&"store") => merkle_tree_stores.set_store( - &st, - types::decode(bytes) - .map_err(Error::CodingError)?, - ), + Some(&"store") => merkle_tree_stores + .set_store(st.decode_store(bytes)?), _ => unknown_key_error(path)?, } } @@ -218,7 +215,7 @@ impl DB for MockDB { .map_err(Error::KeyError)?; self.0.borrow_mut().insert( store_key.to_string(), - types::encode(merkle_tree_stores.store(st)), + merkle_tree_stores.store(st).encode(), ); } } @@ -322,8 +319,7 @@ impl DB for MockDB { let bytes = self.0.borrow().get(&store_key.to_string()).cloned(); match bytes { Some(b) => { - let store = types::decode(b).map_err(Error::CodingError)?; - merkle_tree_stores.set_store(st, store); + merkle_tree_stores.set_store(st.decode_store(b)?); } None => return Ok(None), } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index f3e561616c..8b19a8a16a 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 5455c7a823..0e960ec01f 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::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -50,16 +52,16 @@ impl Display for Hash { } } -impl Deref for Hash { - type Target = [u8; 32]; - - fn deref(&self) -> &Self::Target { +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { &self.0 } } -impl AsRef<[u8]> for Hash { - fn as_ref(&self) -> &[u8] { +impl Deref for Hash { + type Target = [u8; 32]; + + fn deref(&self) -> &Self::Target { &self.0 } } @@ -89,16 +91,56 @@ impl From for transaction::Hash { } } +impl Hash { + /// Compute sha256 of some bytes + pub fn sha256(data: impl AsRef<[u8]>) -> Self { + 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 { fn from(hash: Hash) -> Self { TmHash::Sha256(hash.0) } } -impl Hash { - /// Compute sha256 of some bytes - pub fn sha256(data: impl AsRef<[u8]>) -> Self { - let digest = Sha256::digest(data.as_ref()); - Self(*digest.as_ref()) +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.0.into() + } +} + +impl From for TreeHash { + fn from(hash: Hash) -> Self { + Self::from(hash.0) + } +} + +impl Value for Hash { + 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 8bae491aac..b7e2005bb7 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -1,10 +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, Div, Mul, Rem, Sub}; +use std::ops::{Add, Deref, Div, Mul, Rem, Sub}; use std::str::FromStr; +use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -12,6 +15,7 @@ use thiserror::Error; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; +use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -229,6 +233,95 @@ 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), +} + +/// Storage keys that are utf8 encoded strings +#[derive(Eq, PartialEq, Copy, Clone, Hash)] +pub struct StringKey { + /// 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 = InternalKey; + + fn deref(&self) -> &Self::Target { + &self.tree_key + } +} + +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, + }) + } +} + +/// 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 { diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 7238da664b..853e14c3d2 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -2718,7 +2718,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 acffe83ce0136c2a05b01f6911f2ab8efb7ccddf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 10:18:07 +0100 Subject: [PATCH 0599/1995] Fix namada encoding spec checks --- .../types/vote_extensions/ethereum_events.rs | 25 ++++++++++++++-- .../vote_extensions/validator_set_update.rs | 29 +++++++++++++++---- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 9086be0658..0196df3094 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -61,9 +61,7 @@ pub struct MultiSignedEthEvent { /// Compresses a set of signed [`Vext`] instances, to save /// space on a block. -#[derive( - Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, -)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct VextDigest { /// The signatures and signing address of each [`Vext`] pub signatures: HashMap, @@ -71,6 +69,27 @@ pub struct VextDigest { pub events: Vec, } +impl BorshSchema for VextDigest { + fn add_definitions_recursively( + definitions: &mut HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + HashMap::::declaration(), + Vec::::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "ethereum_events::VextDigest".into() + } +} + impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, last_height: BlockHeight) -> Vec> { diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b51fc83df1..43a2c1c253 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -25,9 +25,7 @@ const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. -#[derive( - Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, -)] +#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct VextDigest { /// A mapping from a validator address to a [`Signature`]. pub signatures: HashMap, @@ -36,6 +34,27 @@ pub struct VextDigest { pub voting_powers: VotingPowersMap, } +impl BorshSchema for VextDigest { + fn add_definitions_recursively( + definitions: &mut HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + HashMap::::declaration(), + VotingPowersMap::declaration() + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "validator_set_update::VextDigest".into() + } +} + impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, block_height: BlockHeight) -> Vec { @@ -70,9 +89,7 @@ impl VextDigest { pub type SignedVext = Signed; /// Represents a validator set update, for some new [`Epoch`]. -#[derive( - Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, -)] +#[derive(Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Vext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. From 021e519f4af302f6e7348e0570ab3ae7c6e5845b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Sep 2022 09:40:57 +0000 Subject: [PATCH 0600/1995] 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 cfdcd29c71..72d9f18209 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.56bee1c6b0d35b09adf023cf3cf0e5b9c4de6c23faba61afcc3e0bc8c426e778.wasm", - "tx_ibc.wasm": "tx_ibc.f82b5c0515e60c2d4cde106e9098ad3340250b4c003934e5b53a0c2806475f79.wasm", - "tx_init_account.wasm": "tx_init_account.970b1e7f79a8d43f60d58c4357aa45efc8cf4290feef5442e8365aab75ea863e.wasm", - "tx_init_nft.wasm": "tx_init_nft.d8994270e8815c22eb9c9d130ff5f36756540cfcebbf4feb2f95f317367ed481.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e5d15a9278e2d9355f0fc6ae707ad694e13f8d496b36ca367aa3883f0a6e621f.wasm", - "tx_init_validator.wasm": "tx_init_validator.4c45c31b1b494b6ce3a64122e9d87e2fed9757e5a6580d3f4d37e8a1600cdff7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.31d842637ec27da8010e184f2a2fdbcee141a3b62888dd432cb43c3113f787d5.wasm", - "tx_transfer.wasm": "tx_transfer.1c9ca5c5743669fc90ef3ed69cc6a7e463f264b238ae5b782d8d38aec9c40055.wasm", - "tx_unbond.wasm": "tx_unbond.5e928128233c6077099d53e11ae3161205c43347c3d7dd59554a4b2ef308428b.wasm", - "tx_update_vp.wasm": "tx_update_vp.f1cc672546b603b9d87f05dfe5f0ab32fba554a66715cf33b33b3e8bcc1cc543.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.2a43eb9af520bdc31affe43e2c31c0b5a1587e9a3a4659691077ab4cbf48e589.wasm", - "tx_withdraw.wasm": "tx_withdraw.d8ade8b8d41457fa4180e40784e7c0a6c1a61295f73f82a5c5226fcbc04c4038.wasm", - "vp_nft.wasm": "vp_nft.e3b467ba8f396c28abcdf54bc348c04d105b2f1c6249254b55b57bccd6646add.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.747aa4ee7f75462a96625a76d6a687cae2794b5fcd092c8ac1e94ae999efd7df.wasm", - "vp_token.wasm": "vp_token.d7a0d0a85fdcc5d87f07e4bea476156e21907ada630dacaa2f61a5dc7aa91597.wasm", - "vp_user.wasm": "vp_user.55a3302e0da13ca6f9ee2e8d072b4984fdfa0497141e4a96d63337419e2df84f.wasm" + "tx_from_intent.wasm": "tx_from_intent.2c4bc21a64a08951fc769e538464e083bd89a6746884543f7bd952870d3c2012.wasm", + "tx_ibc.wasm": "tx_ibc.6fc2372356d5d6a91c6e25d256dbb97ae982533607a2a37ccec847b2cf14cd86.wasm", + "tx_init_account.wasm": "tx_init_account.65e3dbbfccafeac121719f014bd11d9f862f1fe997e0288dafceb5dbfa20d84d.wasm", + "tx_init_nft.wasm": "tx_init_nft.a9dabebdaa245b24333c2cf02616503d95262dbb84a98fc7526ed9cb74ecd680.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.56f6908ac84792d8e5e2b2bdefcc53543b778ce98f37161b4dc8c37b1336e7ad.wasm", + "tx_init_validator.wasm": "tx_init_validator.9f9708e69ee9c0e8e838cea605ec0ca1461dd4670826955ecfb28e5e121e5a01.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.8712711ce9aa6724f6e8f5a0dadf3492e7290af03a7ad31457dd89139112cc98.wasm", + "tx_transfer.wasm": "tx_transfer.15136b3948093ae8c8057cc32a31e713c9630af7373b1845fdc0468dafe4a429.wasm", + "tx_unbond.wasm": "tx_unbond.3d1b33b558f21f9dce656abc0e45f392ea8e3d3b1e528d1fe7e457b7cef46895.wasm", + "tx_update_vp.wasm": "tx_update_vp.ca5ca7dd52da99f96b58d9117dcf5fc76746f8dce4dac5ff621273ff701d44a2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.475f7eb15b635bf5946b2b3e63ca3f9356d51a95ca353d16613e1f0e0e6b0d5e.wasm", + "tx_withdraw.wasm": "tx_withdraw.af58146ad8c427cb0dcc27ebbab82e9dbe93ab0d2dcf32a77047126cc14caf5b.wasm", + "vp_nft.wasm": "vp_nft.aad85ef02c4d4a4ef32c70f3cbca2ce365cbbfe428307a2ac12a892e9bb943d7.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c1d80dab398aa7c564c15dcd49d349f2af6f44a04d744cf7f6c0186bdd45e1fe.wasm", + "vp_token.wasm": "vp_token.a6f531a0d93cba4de43f38af271a439b3b3ad1ef1c5771519126052dadc68fd1.wasm", + "vp_user.wasm": "vp_user.963676b150df6663ee9f1255071da29cf90022157337faeb3e8a378e72e98e61.wasm" } \ No newline at end of file From 19d0cc2c9f72b70d3d8730d974c259f42dbbaf5b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 12:43:40 +0100 Subject: [PATCH 0601/1995] Remove ref from EthAddrBook pattern match --- .../types/vote_extensions/validator_set_update.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index a7e2ea7057..b48fba5d4f 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -191,9 +191,9 @@ impl VotingPowersMapExt for VotingPowersMap { // split the vec into three portions let init = (Vec::new(), Vec::new(), Vec::new()); - sorted.into_iter().fold( - init, - |accum, (ref addr_book, &voting_power)| { + sorted + .into_iter() + .fold(init, |accum, (addr_book, &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power @@ -206,18 +206,17 @@ impl VotingPowersMapExt for VotingPowersMap { let voting_power: ethereum::U256 = voting_power.into(); let (mut fst, mut snd, mut thd) = accum; - let EthAddrBook { + let &EthAddrBook { hot_key_addr: EthAddress(hot_key_addr), cold_key_addr: EthAddress(cold_key_addr), } = addr_book; - fst.push(Token::Address(ethereum::H160(*hot_key_addr))); - snd.push(Token::Address(ethereum::H160(*cold_key_addr))); + fst.push(Token::Address(ethereum::H160(hot_key_addr))); + snd.push(Token::Address(ethereum::H160(cold_key_addr))); thd.push(Token::Uint(voting_power)); (fst, snd, thd) - }, - ) + }) } } From de3586da44cae4bf1852a6390f529b5338fb310a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 12:46:57 +0100 Subject: [PATCH 0602/1995] Improve readability of fold() identifiers --- .../vote_extensions/validator_set_update.rs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b48fba5d4f..0f06408eb6 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -190,10 +190,9 @@ impl VotingPowersMapExt for VotingPowersMap { .sum(); // split the vec into three portions - let init = (Vec::new(), Vec::new(), Vec::new()); - sorted - .into_iter() - .fold(init, |accum, (addr_book, &voting_power)| { + sorted.into_iter().fold( + Default::default(), + |accum, (addr_book, &voting_power)| { let voting_power: u64 = voting_power.into(); // normalize the voting power @@ -205,18 +204,22 @@ impl VotingPowersMapExt for VotingPowersMap { let voting_power = voting_power.round().to_integer(); let voting_power: ethereum::U256 = voting_power.into(); - let (mut fst, mut snd, mut thd) = accum; + let (mut hot_key_addrs, mut cold_key_addrs, mut voting_powers) = + accum; let &EthAddrBook { hot_key_addr: EthAddress(hot_key_addr), cold_key_addr: EthAddress(cold_key_addr), } = addr_book; - fst.push(Token::Address(ethereum::H160(hot_key_addr))); - snd.push(Token::Address(ethereum::H160(cold_key_addr))); - thd.push(Token::Uint(voting_power)); + hot_key_addrs + .push(Token::Address(ethereum::H160(hot_key_addr))); + cold_key_addrs + .push(Token::Address(ethereum::H160(cold_key_addr))); + voting_powers.push(Token::Uint(voting_power)); - (fst, snd, thd) - }) + (hot_key_addrs, cold_key_addrs, voting_powers) + }, + ) } } From 2bb453af9f3c3d8faaef5138c79b84209cea36b9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 13:09:30 +0100 Subject: [PATCH 0603/1995] Switch to Github permalink in comment --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 0f06408eb6..e0f5c054e5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -196,7 +196,7 @@ impl VotingPowersMapExt for VotingPowersMap { let voting_power: u64 = voting_power.into(); // normalize the voting power - // https://github.com/anoma/ethereum-bridge/blob/main/test/utils/utilities.js#L29 + // https://github.com/anoma/ethereum-bridge/blob/fe93d2e95ddb193a759811a79c8464ad4d709c12/test/utils/utilities.js#L29 const NORMALIZED_VOTING_POWER: u64 = 1 << 32; let voting_power = Ratio::new(voting_power, total_voting_power) From 684da89e687f4917023c7e148b88b0fd6e820877 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 0604/1995] make build-doc: only build rustdoc in this command --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 3474f6c8e6..6bcb62c75f 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 72d5ca2e1d19c4ce45c8074da767007cb6cdf142 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 10 Sep 2022 12:55:34 +0000 Subject: [PATCH 0605/1995] 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 7f8c105ac7..dba7e696b2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.49b59fd9a3c08ae28c020dfb83e024b1055dd1fd3993bf8e5588db4436574df5.wasm", - "tx_ibc.wasm": "tx_ibc.c249dc37fcb85f0b14e1f064845b820b80e265e4d3d2b85b991a5cad7a895338.wasm", - "tx_init_account.wasm": "tx_init_account.2aafd544cdc7c93e29a76bf3687d1d9468a1fbd3a6f281613304cb3723c2a2c2.wasm", - "tx_init_nft.wasm": "tx_init_nft.1a7c09d5f089c52cbe0c42bd496014a1077c5283b12b9088bb2cf43ae4fbf113.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ca2d00d720fda4aef32f1ea99c2927f4e787d798d7172e7378c2c3ef45122b02.wasm", - "tx_init_validator.wasm": "tx_init_validator.abfcf0b77a390d381b4ad7dc232a13390ad6e6f2d6925b0936c69df87e32cf4c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.afcd165729194f79e8c8ba4cab135024e948473388a49a4f70cd2a001572f2fb.wasm", - "tx_transfer.wasm": "tx_transfer.befd5f7d2354dfceb5791b6958955bf4322e8fa750ee6e3c6a2ab0c3849ba3b8.wasm", - "tx_unbond.wasm": "tx_unbond.8405ec2f697850e75afd34ddb4a0a06c2ae8b1cf37fe6f81b159cac9f9f69a7d.wasm", - "tx_update_vp.wasm": "tx_update_vp.11940df2f7a4794b7e941a54a9f714757419266ad7e4c204759c1ec7823f4590.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.085a34328aa2379c58fe01ebe973e500fc46174cfbad6569a470ce78f3896e48.wasm", - "tx_withdraw.wasm": "tx_withdraw.1a5171753c2c168f10f0cf34af36669a22051a409abd64fbef208dff583f8375.wasm", - "vp_nft.wasm": "vp_nft.32e7d12194a63951267b0e586ae2d671e11b88f557056bf134a2be9768916412.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.192c09498cb8b94368b64345468cae7922321d9a36d62b472bc9a903277c7000.wasm", - "vp_token.wasm": "vp_token.01a0d21d392dab91c76e37f835eaf9a2fd83c4c7245e8f5d2db233cde16303ce.wasm", - "vp_user.wasm": "vp_user.10dde4c00de0e671d352d58e22d3d0e4bee06eedb23ac30171581dbd3e84d27d.wasm" -} + "tx_from_intent.wasm": "tx_from_intent.5873123abda899833648cbaec31f0596750fb683767c821712adc6b37b8f90b7.wasm", + "tx_ibc.wasm": "tx_ibc.b64374d501fb27b8200a496f2c50f2fd151c86b480b14910a1c678b9a18be255.wasm", + "tx_init_account.wasm": "tx_init_account.5753889fee02137cf35c2abf2b9992b624b66eb4e136a4ff345a7783ade4b0c8.wasm", + "tx_init_nft.wasm": "tx_init_nft.6a77790d623a80ce095117779b6c73e08650543e3552fcb50b76e7c78954a2ed.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d3a307253fbfd916fe5f78788d8f384e4040291198720aaff57df41b32b53e83.wasm", + "tx_init_validator.wasm": "tx_init_validator.ac9f6a6521312806b9aa73357780a7e3a4b6d4b454ff278aca11bd3ed36e10e2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.537ac560ebd83b67faaef4f61835c685b4cff662dd19d18e6a7e5d5c291d20c8.wasm", + "tx_transfer.wasm": "tx_transfer.a45e9e7f4ce046f0fbb1a1c111a83647009b597c9a3eb9de256642fd7a4ee9e1.wasm", + "tx_unbond.wasm": "tx_unbond.6506f23d8a5214ce7087dfc47db49430321f611869db3a2854c84112fea58862.wasm", + "tx_update_vp.wasm": "tx_update_vp.15772fa9ca0bc32600ec40e787541a1973fba7e3321b5f1f360a72683473e168.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e7d5aa0d70b0ece804af38cf225e13181f37dce92c687dde4455438ac828aebe.wasm", + "tx_withdraw.wasm": "tx_withdraw.fdd9d50f9a9834584c590ef02e9294dad3f38ae721e79cbb27eee471a6d83679.wasm", + "vp_nft.wasm": "vp_nft.7c26f1d2ba12740b1b40edfed74eeba9c36173e968f82e82dbea2ac40d8d548f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.d46022e48d1d20c4c62b2dd8c979bbfeb18f8e56d941b977d61f83e871ab5d11.wasm", + "vp_token.wasm": "vp_token.093ededf7f0163edb6498fcd6cd022902f461a1f1e110332b313fe8b92b1abb9.wasm", + "vp_user.wasm": "vp_user.6aa18329e89c9f3752a53ebc7a165f3a619563ee7dbaf65006518c4f4bccb76b.wasm" +} \ No newline at end of file From 091852937e298ff5304e6c11fbbf2c3b58c4df2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 17:14:05 +0200 Subject: [PATCH 0606/1995] fixup! pos: add validator eth cold and hot keys --- proof_of_stake/src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a137eb8a91..89d7981445 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -571,6 +571,17 @@ pub trait PosBase { fn read_validator_set(&self) -> ValidatorSets; /// Read PoS total voting power of all validators (active and inactive). fn read_total_voting_power(&self) -> TotalVotingPowers; + /// Read PoS validator's Eth bridge governance key + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option; + + /// Read PoS validator's Eth validator set update signing key + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option; /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); @@ -624,6 +635,18 @@ pub trait PosBase { fn write_validator_set(&mut self, value: &ValidatorSets); /// Read PoS total voting power of all validators (active and inactive). fn write_total_voting_power(&mut self, value: &TotalVotingPowers); + /// Write PoS validator's Eth bridge governance key + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: &ValidatorEthKey, + ); + /// Write PoS validator's Eth validator set update signing key + fn write_validator_eth_hot_key( + &mut self, + address: &Self::Address, + value: &ValidatorEthKey, + ); /// Initialize staking reward account with the given public key. fn init_staking_reward_account( &mut self, From ebbc8fccbde82883ed73a740daccea85b2eaab3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 17:14:33 +0200 Subject: [PATCH 0607/1995] shared: add eth keys for validators --- shared/src/ledger/pos/storage.rs | 87 +++++++++++++++++++++++++++++ shared/src/ledger/pos/vp.rs | 26 +++++++-- shared/src/types/transaction/mod.rs | 5 ++ vm_env/src/proof_of_stake.rs | 36 ++++++++++++ 4 files changed, 150 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index cfe1126b88..1443c27811 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -22,6 +22,8 @@ const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = "staking_reward_address"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; +const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; +const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; @@ -142,6 +144,56 @@ pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { } } +/// Storage key for validator's eth cold key. +pub fn validator_eth_cold_key_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_ETH_COLD_KEY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's eth cold key? +pub fn is_validator_eth_cold_key_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_COLD_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's eth hot key. +pub fn validator_eth_hot_key_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_ETH_HOT_KEY_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's eth hot key? +pub fn is_validator_eth_hot_key_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_ETH_HOT_KEY_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + /// Storage key for validator's state. pub fn validator_state_key(validator: &Address) -> Key { validator_prefix(validator) @@ -450,6 +502,23 @@ where decode(value.unwrap()).unwrap() } + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option { + let (value, _gas) = + self.read(&validator_eth_cold_key_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()) + } + + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option { + let (value, _gas) = self.read(&validator_eth_hot_key_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()) + } + fn write_pos_params(&mut self, params: &PosParams) { self.write(¶ms_key(), encode(params)).unwrap(); } @@ -529,6 +598,24 @@ where .unwrap(); } + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: &types::ValidatorEthKey, + ) { + self.write(&validator_eth_cold_key_key(address), encode(value)) + .unwrap(); + } + + fn write_validator_eth_hot_key( + &mut self, + address: &Self::Address, + value: &types::ValidatorEthKey, + ) { + self.write(&validator_eth_hot_key_key(address), encode(value)) + .unwrap(); + } + fn init_staking_reward_account( &mut self, address: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..7d2b7680f1 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -21,10 +21,11 @@ use super::{ is_validator_staking_reward_address_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_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, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, + validator_eth_cold_key_key, validator_eth_hot_key_key, validator_set_key, + validator_slashes_key, validator_staking_reward_address_key, + validator_state_key, validator_total_deltas_key, + validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, + ValidatorSets, ValidatorTotalDeltas, }; use crate::ledger::governance::vp::is_proposal_accepted; use crate::ledger::native_vp::{self, Ctx, NativeVp}; @@ -415,6 +416,23 @@ where .unwrap(); decode(value).unwrap() } + + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option { + let value = + self.ctx.read_pre(&validator_eth_cold_key_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()) + } + + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option { + let value = self.ctx.read_pre(&validator_eth_hot_key_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()) + } } impl From for Error { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index a7d5ee864b..b9fabcfd6b 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -187,6 +187,11 @@ pub struct InitValidator { pub account_key: common::PublicKey, /// A key to be used for signing blocks and votes on blocks. pub consensus_key: common::PublicKey, + /// An Eth bridge governance public key + pub eth_cold_key: secp256k1::PublicKey, + /// An Eth bridge hot signing public key used for validator set updates and + /// cross-chain transactions + pub eth_hot_key: secp256k1::PublicKey, /// Public key to be written into the staking reward account's storage. /// This can be used for signature verification of transactions for the /// newly created account. diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index afabd85ed7..53e2ab9470 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -62,6 +62,8 @@ pub fn init_validator( InitValidator { account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_account_key, protocol_key, dkg_key, @@ -84,10 +86,14 @@ pub fn init_validator( let pk_key = key::pk_key(&rewards_address); tx::write(&pk_key.to_string(), &rewards_account_key); + let eth_cold_key = key::common::PublicKey::Secp256k1(eth_cold_key); + let eth_hot_key = key::common::PublicKey::Secp256k1(eth_hot_key); PoS.become_validator( &validator_address, &rewards_address, &consensus_key, + ð_cold_key, + ð_hot_key, current_epoch, )?; Ok((validator_address, rewards_address)) @@ -167,6 +173,20 @@ impl namada_proof_of_stake::PosReadOnly for PoS { fn read_total_voting_power(&self) -> TotalVotingPowers { tx::read(total_voting_power_key().to_string()).unwrap() } + + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option { + tx::read(validator_eth_cold_key_key(key).to_string()) + } + + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option { + tx::read(validator_eth_hot_key_key(key).to_string()) + } } impl namada_proof_of_stake::PosActions for PoS { @@ -258,4 +278,20 @@ impl namada_proof_of_stake::PosActions for PoS { ) { crate::token::tx::transfer(src, dest, token, None, amount) } + + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) { + tx::write(validator_eth_cold_key_key(address).to_string(), &value) + } + + fn write_validator_eth_hot_key( + &self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) { + tx::write(validator_eth_hot_key_key(address).to_string(), &value) + } } From 250ee2e7c4065241ee164c7959888074df71e957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 17:14:59 +0200 Subject: [PATCH 0608/1995] client: add eth keys for validators --- apps/src/lib/cli.rs | 22 +++++++++++++++- apps/src/lib/client/tx.rs | 54 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 6eb6454bc4..f271433b9e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1482,6 +1482,9 @@ pub mod args { arg_opt("account-key"); const VALIDATOR_CONSENSUS_KEY: ArgOpt = arg_opt("consensus-key"); + const VALIDATOR_ETH_COLD_KEY: ArgOpt = + arg_opt("eth-cold-key"); + const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); const VALIDATOR_CODE_PATH: ArgOpt = arg_opt("validator-code-path"); const VALUE: ArgOpt = arg_opt("value"); const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); @@ -1700,6 +1703,8 @@ pub mod args { pub scheme: SchemeType, pub account_key: Option, pub consensus_key: Option, + pub eth_cold_key: Option, + pub eth_hot_key: Option, pub rewards_account_key: Option, pub protocol_key: Option, pub validator_vp_code_path: Option, @@ -1714,6 +1719,8 @@ pub mod args { let scheme = SCHEME.parse(matches); let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); + let eth_cold_key = VALIDATOR_ETH_COLD_KEY.parse(matches); + let eth_hot_key = VALIDATOR_ETH_HOT_KEY.parse(matches); let rewards_account_key = REWARDS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); @@ -1725,6 +1732,8 @@ pub mod args { scheme, account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_account_key, protocol_key, validator_vp_code_path, @@ -1748,7 +1757,18 @@ pub mod args { )) .arg(VALIDATOR_CONSENSUS_KEY.def().about( "A consensus key for the validator account. A new one \ - will be generated if none given.", + will be generated if none given. Note that this must be \ + ed25519.", + )) + .arg(VALIDATOR_ETH_COLD_KEY.def().about( + "An Eth cold key for the validator account. A new one \ + will be generated if none given. Note that this must be \ + secp256k1.", + )) + .arg(VALIDATOR_ETH_COLD_KEY.def().about( + "An Eth cold key for the validator account. A new one \ + will be generated if none given. Note that this must be \ + secp256k1.", )) .arg(REWARDS_KEY.def().about( "A public key for the staking reward account. A new one \ diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 43eeb49c68..a1aabfe532 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -14,7 +14,7 @@ use namada::types::address::{xan as m1t, Address}; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; -use namada::types::key::*; +use namada::types::key::{self, *}; use namada::types::nft::{self, Nft, NftToken}; use namada::types::storage::{Epoch, Key}; use namada::types::token::Amount; @@ -159,6 +159,8 @@ pub async fn submit_init_validator( scheme, account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_account_key, protocol_key, validator_vp_code_path, @@ -208,6 +210,48 @@ pub async fn submit_init_validator( .1 }); + let eth_cold_key = ctx + .get_opt_cached(ð_cold_key) + .map(|key| match *key { + common::SecretKey::Secp256k1(_) => key, + common::SecretKey::Ed25519(_) => { + eprintln!("Eth cold key can only be secp256k1"); + safe_exit(1) + } + }) + .unwrap_or_else(|| { + println!("Generating Eth cold key..."); + ctx.wallet + .gen_key( + // Note that ETH only allows secp256k1 + SchemeType::Secp256k1, + Some(consensus_key_alias.clone()), + unsafe_dont_encrypt, + ) + .1 + }); + + let eth_hot_key = ctx + .get_opt_cached(ð_hot_key) + .map(|key| match *key { + common::SecretKey::Secp256k1(_) => key, + common::SecretKey::Ed25519(_) => { + eprintln!("Eth hot key can only be secp256k1"); + safe_exit(1) + } + }) + .unwrap_or_else(|| { + println!("Generating Eth hot key..."); + ctx.wallet + .gen_key( + // Note that ETH only allows secp256k1 + SchemeType::Secp256k1, + Some(consensus_key_alias.clone()), + unsafe_dont_encrypt, + ) + .1 + }); + let rewards_account_key = ctx.get_opt_cached(&rewards_account_key).unwrap_or_else(|| { println!("Generating staking reward account key..."); @@ -269,6 +313,14 @@ pub async fn submit_init_validator( let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), + eth_cold_key: key::secp256k1::PublicKey::try_from_pk( + ð_cold_key.ref_to(), + ) + .unwrap(), + eth_hot_key: key::secp256k1::PublicKey::try_from_pk( + ð_hot_key.ref_to(), + ) + .unwrap(), rewards_account_key, protocol_key, dkg_key, From 6893c20adf934156e3e7a528f6166bfd06e4d9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 17:15:17 +0200 Subject: [PATCH 0609/1995] TODO: update genesis --- apps/src/lib/config/genesis.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..66e4b0caf4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -160,6 +160,7 @@ pub mod genesis_config { pub validator: HashMap, } + // TODO add eth keys here #[derive(Clone, Default, Debug, Deserialize, Serialize)] pub struct ValidatorConfig { // Public key for consensus. (default: generate) @@ -298,6 +299,7 @@ pub mod genesis_config { let reward_vp_config = wasm.get(reward_vp_name).unwrap(); Validator { + // TODO add eth keys here pos_data: GenesisValidator { address: Address::decode(&config.address.as_ref().unwrap()) .unwrap(), From 92a0e224358599c74998f2d30843be90f831deb5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 21 Aug 2022 17:46:45 +0200 Subject: [PATCH 0610/1995] config: add eth keys to genesis --- apps/src/lib/config/genesis.rs | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 66e4b0caf4..ca6b43fa95 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -165,6 +165,10 @@ pub mod genesis_config { pub struct ValidatorConfig { // Public key for consensus. (default: generate) pub consensus_public_key: Option, + // Public key (cold) for eth governance. (default: generate) + pub eth_cold_key: Option, + // Public key (hot) for eth bridge. (default: generate) + pub eth_hot_key: Option, // Public key for validator account. (default: generate) pub account_public_key: Option, // Public key for staking reward account. (default: generate) @@ -320,6 +324,18 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), + eth_cold_key: config + .staking_reward_public_key + .as_ref() + .unwrap() + .to_public_key() + .unwrap(), + eth_hot_key: config + .staking_reward_public_key + .as_ref() + .unwrap() + .to_public_key() + .unwrap(), }, account_key: config .account_public_key @@ -752,8 +768,27 @@ pub fn genesis() -> Genesis { 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, ]) .unwrap(); + + // TODO: should these bytes be different from the ed above? Should I + // generate them from somewhere? For now I'm just randomly + // adjusting some bytes, assuming that is fine. + let secp_eth_cold_keypair = secp256k1::SecretKey::try_from_slice(&[ + 64, 198, 87, 204, 44, 94, 234, 228, 217, 72, 245, 27, 40, 2, 151, 174, + 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 65, 162, 243, 125, 240, 206, + ]) + .unwrap(); + let secp_eth_hot_keypair = secp256k1::SecretKey::try_from_slice(&[ + 58, 198, 87, 204, 44, 94, 122, 228, 217, 72, 245, 27, 40, 2, 151, 174, + 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, + ]) + .unwrap(); + let staking_reward_keypair = common::SecretKey::try_from_sk(&ed_staking_reward_keypair).unwrap(); + let eth_cold_keypair = + common::SecretKey::try_from_sk(&secp_eth_cold_keypair).unwrap(); + let eth_hot_keypair = + common::SecretKey::try_from_sk(&secp_eth_hot_keypair).unwrap(); let address = wallet::defaults::validator_address(); let staking_reward_address = Address::decode("atest1v4ehgw36xcersvee8qerxd35x9prsw2xg5erxv6pxfpygd2x89z5xsf5xvmnysejgv6rwd2rnj2avt").unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); @@ -764,6 +799,8 @@ pub fn genesis() -> Genesis { tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), staking_reward_key: staking_reward_keypair.ref_to(), + eth_cold_key: eth_cold_keypair.ref_to(), + eth_hot_key: eth_hot_keypair.ref_to(), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), From fd925416a2a7eb60dd3ea9c934fa72cd7ee71c35 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 21 Aug 2022 17:59:39 +0200 Subject: [PATCH 0611/1995] client: add eth keys to pre_genesis --- apps/src/lib/client/utils.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 9043a14db7..ca0a3d7347 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1089,6 +1089,12 @@ pub fn init_genesis_validator( consensus_public_key: Some(HexString( pre_genesis.consensus_key.ref_to().to_string(), )), + eth_cold_key: Some(HexString( + pre_genesis.eth_cold_key.ref_to().to_string(), + )), + eth_hot_key: Some(HexString( + pre_genesis.eth_hot_key.ref_to().to_string(), + )), account_public_key: Some(HexString( pre_genesis.account_key.ref_to().to_string(), )), From 93179b7427516a5b945900d0218b6a87be225dad Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 21 Aug 2022 18:00:30 +0200 Subject: [PATCH 0612/1995] wallet: add eth keys to ValidatorWallet --- apps/src/lib/wallet/pre_genesis.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 72f719d1e4..bc5bb84106 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -40,6 +40,10 @@ pub struct ValidatorWallet { pub account_key: Rc, /// Cryptographic keypair for consensus key pub consensus_key: Rc, + /// Cryptographic keypair for eth cold key + pub eth_cold_key: Rc, + /// Cryptographic keypair for eth hot key + pub eth_hot_key: Rc, /// Cryptographic keypair for rewards key pub rewards_key: Rc, /// Cryptographic keypair for Tendermint node key @@ -54,6 +58,10 @@ pub struct ValidatorStore { pub account_key: wallet::StoredKeypair, /// Cryptographic keypair for consensus key pub consensus_key: wallet::StoredKeypair, + /// Cryptographic keypair for eth cold key + pub eth_cold_key: wallet::StoredKeypair, + /// Cryptographic keypair for eth hot key + pub eth_hot_key: wallet::StoredKeypair, /// Cryptographic keypair for rewards key pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key @@ -119,6 +127,11 @@ impl ValidatorWallet { store.account_key.get(true, password.clone())?; let consensus_key = store.consensus_key.get(true, password.clone())?; + let eth_cold_key = + store.eth_cold_key.get(true, password.clone())?; + let eth_hot_key = + store.eth_hot_key.get(true, password.clone())?; + let rewards_key = store.rewards_key.get(true, password.clone())?; let tendermint_node_key = @@ -128,6 +141,8 @@ impl ValidatorWallet { store, account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_key, tendermint_node_key, }) @@ -149,6 +164,11 @@ impl ValidatorWallet { SchemeType::Ed25519, &password, ); + let (eth_cold_key, eth_cold_sk) = + gen_key_to_store(SchemeType::Secp256k1, &password); + let (eth_hot_key, eth_hot_sk) = + gen_key_to_store(SchemeType::Secp256k1, &password); + let (rewards_key, rewards_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 @@ -159,6 +179,8 @@ impl ValidatorWallet { let store = ValidatorStore { account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_key, tendermint_node_key, validator_keys, @@ -167,6 +189,8 @@ impl ValidatorWallet { store, account_key: account_sk, consensus_key: consensus_sk, + eth_cold_key: eth_cold_sk, + eth_hot_key: eth_hot_sk, rewards_key: rewards_sk, tendermint_node_key: tendermint_node_sk, } From 87f70e4299f29e8cd6ee0babbbf195d76f4b906a Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 24 Aug 2022 12:26:10 +0300 Subject: [PATCH 0613/1995] proper hard-coded secp keys, remove outdated comments --- apps/src/lib/config/genesis.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index ca6b43fa95..b0393cab5e 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -303,7 +303,6 @@ pub mod genesis_config { let reward_vp_config = wasm.get(reward_vp_name).unwrap(); Validator { - // TODO add eth keys here pos_data: GenesisValidator { address: Address::decode(&config.address.as_ref().unwrap()) .unwrap(), @@ -769,17 +768,14 @@ pub fn genesis() -> Genesis { ]) .unwrap(); - // TODO: should these bytes be different from the ed above? Should I - // generate them from somewhere? For now I'm just randomly - // adjusting some bytes, assuming that is fine. let secp_eth_cold_keypair = secp256k1::SecretKey::try_from_slice(&[ - 64, 198, 87, 204, 44, 94, 234, 228, 217, 72, 245, 27, 40, 2, 151, 174, - 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 65, 162, 243, 125, 240, 206, + 90, 83, 107, 155, 193, 251, 120, 27, 76, 1, 188, 8, 116, 121, 90, 99, + 65, 17, 187, 6, 238, 141, 63, 188, 76, 38, 102, 7, 47, 185, 28, 52, ]) .unwrap(); let secp_eth_hot_keypair = secp256k1::SecretKey::try_from_slice(&[ - 58, 198, 87, 204, 44, 94, 122, 228, 217, 72, 245, 27, 40, 2, 151, 174, - 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, + 117, 93, 118, 129, 202, 67, 51, 62, 202, 196, 130, 244, 5, 44, 88, 200, + 121, 169, 11, 227, 79, 223, 74, 88, 49, 132, 213, 59, 64, 20, 13, 82, ]) .unwrap(); From be2a29546fd0bbcc50290802d8fa8b9110cbd084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Sep 2022 15:55:12 +0200 Subject: [PATCH 0614/1995] shared: add conversion from secp256k1 pk to eth address --- shared/Cargo.toml | 1 + shared/src/types/key/secp256k1.rs | 47 +++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ff22c0911b..b35b85bd36 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,6 +92,7 @@ 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", features = ["secp256k1"]} tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tiny-keccak = {version = "2.0.2", features = ["keccak"]} thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 99bcbb3f67..198cb8d74b 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -22,6 +22,10 @@ use super::{ #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PublicKey(pub libsecp256k1::PublicKey); +/// Eth address derived from secp256k1 key +#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct EthAddress([u8; 20]); + impl super::PublicKey for PublicKey { const TYPE: SchemeType = SigScheme::TYPE; @@ -133,6 +137,23 @@ impl From for PublicKey { } } +impl From<&PublicKey> for EthAddress { + fn from(pk: &PublicKey) -> Self { + use tiny_keccak::Hasher; + + let mut hasher = tiny_keccak::Keccak::v256(); + // We're removing the first byte with + // `libsecp256k1::util::TAG_PUBKEY_FULL` + let pk_bytes = &pk.0.serialize()[1..]; + hasher.update(pk_bytes); + let mut output = [0_u8; 32]; + hasher.finalize(&mut output); + let mut addr = [0; 20]; + addr.copy_from_slice(&output[12..]); + EthAddress(addr) + } +} + /// Secp256k1 secret key #[derive(Debug, Clone)] pub struct SecretKey(pub Box); @@ -501,3 +522,29 @@ impl super::SigScheme for SigScheme { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_eth_address_from_secp() { + // test vector from https://bitcoin.stackexchange.com/a/89848 + let sk_hex = + "c2c72dfbff11dfb4e9d5b0a20c620c58b15bb7552753601f043db91331b0db15"; + let expected_pk_hex = "a225bf565ff4ea039bccba3e26456e910cd74e4616d67ee0a166e26da6e5e55a08d0fa1659b4b547ba7139ca531f62907b9c2e72b80712f1c81ece43c33f4b8b"; + let expected_eth_addr_hex = "6ea27154616a29708dce7650b475dd6b82eba6a3"; + + let sk_bytes = hex::decode(sk_hex).unwrap(); + let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); + let pk: PublicKey = sk.ref_to(); + // We're removing the first byte with + // `libsecp256k1::util::TAG_PUBKEY_FULL` + let pk_hex = hex::encode(&pk.0.serialize()[1..]); + assert_eq!(expected_pk_hex, pk_hex); + + let eth_addr: EthAddress = (&pk).into(); + let eth_addr_hex = hex::encode(eth_addr.0); + assert_eq!(expected_eth_addr_hex, eth_addr_hex); + } +} From de84663ac9343b884804f3d57e45f5bafe79264c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Sep 2022 17:25:45 +0200 Subject: [PATCH 0615/1995] WIP: add maps for eth address reverse lookup to native address --- proof_of_stake/src/lib.rs | 87 +++++++++++++++++++++++++++++++- proof_of_stake/src/types.rs | 16 +++++- shared/src/ledger/pos/storage.rs | 34 +++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 89d7981445..34cfb60378 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -33,8 +33,9 @@ use epoched::{ use parameters::PosParams; use thiserror::Error; use types::{ - ActiveValidator, Bonds, Epoch, GenesisValidator, Slash, SlashType, Slashes, - TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, + ActiveValidator, Bonds, Epoch, EthAddress, EthKeyAddresses, + GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, + Unbonds, ValidatorConsensusKeys, ValidatorEthKey, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; @@ -146,6 +147,44 @@ pub trait PosReadOnly { fn read_validator_set(&self) -> ValidatorSets; /// Read PoS total voting power of all validators (active and inactive). fn read_total_voting_power(&self) -> TotalVotingPowers; + + /// Read PoS validator's Eth bridge governance key + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option; + + /// Read PoS validator's Eth validator set update signing key + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option; + + /// Read PoS map from eth address derived from cold keys to native addresses + fn read_eth_cold_key_addresses(&self) -> EthKeyAddresses; + + /// Read PoS map from eth address derived from hot keys to native addresses + fn read_eth_hot_key_addresses(&self) -> EthKeyAddresses; + + /// Try to find a native address associated with the given eth address + /// derived from a eth cold key + fn find_address_from_eth_cold_key_address( + &self, + eth_addr: &EthAddress, + ) -> Option { + let addresses = self.read_eth_cold_key_addresses(); + addresses.get(eth_addr).cloned() + } + + /// Try to find a native address associated with the given eth address + /// derived from a eth hot key + fn find_address_from_eth_hot_key_address( + &self, + eth_addr: &EthAddress, + ) -> Option { + let addresses = self.read_eth_hot_key_addresses(); + addresses.get(eth_addr).cloned() + } } /// PoS system trait to be implemented in integration that can read and write @@ -204,6 +243,19 @@ pub trait PosActions: PosReadOnly { fn write_validator_set(&mut self, value: ValidatorSets); /// Write PoS total voting power of all validators (active and inactive). fn write_total_voting_power(&mut self, value: TotalVotingPowers); + /// Write PoS validator's Eth bridge governance key + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: ValidatorEthKey, + ); + + /// Write PoS validator's Eth validator set update signing key + fn write_validator_eth_hot_key( + &self, + address: &Self::Address, + value: ValidatorEthKey, + ); /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond(&mut self, key: &BondId); @@ -226,6 +278,8 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, staking_reward_address: &Self::Address, consensus_key: &Self::PublicKey, + eth_cold_key: &Self::PublicKey, + eth_hot_key: &Self::PublicKey, current_epoch: impl Into, ) -> Result<(), BecomeValidatorError> { let current_epoch = current_epoch.into(); @@ -245,6 +299,8 @@ pub trait PosActions: PosReadOnly { } let BecomeValidatorData { consensus_key, + eth_cold_key, + eth_hot_key, state, total_deltas, voting_power, @@ -252,6 +308,8 @@ pub trait PosActions: PosReadOnly { ¶ms, address, consensus_key, + eth_cold_key, + eth_hot_key, &mut validator_set, current_epoch, ); @@ -260,6 +318,8 @@ pub trait PosActions: PosReadOnly { staking_reward_address.clone(), ); self.write_validator_consensus_key(address, consensus_key); + self.write_validator_eth_cold_key(address, eth_cold_key); + self.write_validator_eth_hot_key(address, eth_hot_key); self.write_validator_state(address, state); self.write_validator_set(validator_set); self.write_validator_address_raw_hash(address); @@ -707,6 +767,8 @@ pub trait PosBase { total_deltas, voting_power, bond: (bond_id, bond), + eth_cold_key, + eth_hot_key, } = res?; self.write_validator_address_raw_hash(address); self.write_validator_staking_reward_address( @@ -714,6 +776,8 @@ pub trait PosBase { &staking_reward_address, ); self.write_validator_consensus_key(address, &consensus_key); + self.write_validator_eth_cold_key(address, ð_cold_key); + self.write_validator_eth_hot_key(address, ð_hot_key); self.write_validator_state(address, &state); self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); @@ -1079,6 +1143,8 @@ where total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, bond: (BondId
, Bonds), + eth_cold_key: ValidatorEthKey, + eth_hot_key: ValidatorEthKey, } /// A function that returns genesis data created from the initial validator set. @@ -1178,9 +1244,15 @@ where tokens, consensus_key, staking_reward_key, + eth_cold_key, + eth_hot_key, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); + let eth_cold_key = + Epoched::init_at_genesis(eth_cold_key.clone(), current_epoch); + let eth_hot_key = + Epoched::init_at_genesis(eth_hot_key.clone(), current_epoch); let state = Epoched::init_at_genesis( ValidatorState::Candidate, current_epoch, @@ -1210,6 +1282,8 @@ where total_deltas, voting_power, bond: (bond_id, bond), + eth_cold_key, + eth_hot_key, }) }, ); @@ -1320,6 +1394,8 @@ where + BorshSchema, { consensus_key: ValidatorConsensusKeys, + eth_cold_key: ValidatorEthKey, + eth_hot_key: ValidatorEthKey, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1330,6 +1406,8 @@ fn become_validator( params: &PosParams, address: &Address, consensus_key: &PK, + eth_cold_key: &PK, + eth_hot_key: &PK, validator_set: &mut ValidatorSets
, current_epoch: Epoch, ) -> BecomeValidatorData @@ -1353,6 +1431,9 @@ where { let consensus_key = Epoched::init(consensus_key.clone(), current_epoch, params); + let eth_cold_key = + Epoched::init(eth_cold_key.clone(), current_epoch, params); + let eth_hot_key = Epoched::init(eth_hot_key.clone(), current_epoch, params); let mut state = Epoched::init_at_genesis(ValidatorState::Pending, current_epoch); @@ -1394,6 +1475,8 @@ where state, total_deltas, voting_power, + eth_cold_key, + eth_hot_key, } } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 45342ef277..c421903197 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -36,8 +36,15 @@ pub type Unbonds = pub type ValidatorSets
= Epoched, OffsetUnboundingLen>; /// Epoched total voting power. -pub type TotalVotingPowers = - EpochedDelta; +pub type TotalVotingPowers = EpochedDelta; +/// Epoched validator's eth key. +pub type ValidatorEthKey = Epoched; +/// Map from eth addresses back to native addresses. +pub type EthKeyAddresses
= HashMap; + +/// Eth address derived from secp256k1 key +#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct EthAddress([u8; 20]); /// Epoch identifier. Epochs are identified by consecutive natural numbers. /// @@ -118,6 +125,11 @@ pub struct GenesisValidator { pub consensus_key: PK, /// An public key associated with the staking reward address pub staking_reward_key: PK, + /// An Eth bridge governance public key + pub eth_cold_key: PK, + /// An Eth bridge hot signing public key used for validator set updates and + /// cross-chain transactions + pub eth_hot_key: PK, } /// An update of the active and inactive validator set. diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 1443c27811..8e92ad32b9 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -24,6 +24,8 @@ const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; +const ETH_COLD_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_cold_key_addresses"; +const ETH_HOT_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_hot_key_addresses"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; @@ -418,6 +420,38 @@ pub fn get_validator_address_from_bond(key: &Key) -> Option
{ } } +/// Storage key for look-up from validator's eth cold key addresses to native +/// address. +pub fn eth_cold_key_addresses_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(Ð_COLD_KEY_ADDRESSES_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validators' eth cold key addresses? +pub fn is_eth_cold_key_addresses_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(suffix)] + if addr == &ADDRESS + && suffix == ETH_COLD_KEY_ADDRESSES_STORAGE_KEY) +} + +/// Storage key for look-up from validator's eth hot key addresses to native +/// address. +pub fn eth_hot_key_addresses_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(Ð_HOT_KEY_ADDRESSES_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validators' eth hot key addresses? +pub fn is_eth_hot_key_addresses_key(key: &Key) -> bool { + matches!(&key.segments[..], + [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(suffix)] + if addr == &ADDRESS + && suffix == ETH_HOT_KEY_ADDRESSES_STORAGE_KEY) +} + impl PosBase for Storage where D: storage::DB + for<'iter> storage::DBIter<'iter>, From f083429b20e626a37f3d43cefdcf880f22cebd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Sep 2022 17:26:20 +0200 Subject: [PATCH 0616/1995] TODO: add eth hot key to validator keys in the wallet store --- apps/src/lib/wallet/store.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 76a2053419..356b7add53 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -30,6 +30,7 @@ pub struct ValidatorKeys { /// Special session keypair needed by validators for participating /// in the DKG protocol pub dkg_keypair: Option, + // TODO add eth hot key } impl ValidatorKeys { From 6b9de30b4a57d6788359349a99b5cbd7f8a02477 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 8 Sep 2022 12:12:54 +0100 Subject: [PATCH 0617/1995] WIP: Merge eth hot and cold keys into single storage sub-key space --- proof_of_stake/src/lib.rs | 75 ++++++++++++++++++++++++-------- shared/src/ledger/pos/storage.rs | 31 +++---------- 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 34cfb60378..581560513a 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -91,6 +91,7 @@ pub trait PosReadOnly { /// Cryptographic public key type type PublicKey: Debug + Clone + + TryInto + BorshDeserialize + BorshSerialize + BorshSchema; @@ -160,29 +161,17 @@ pub trait PosReadOnly { key: &Self::Address, ) -> Option; - /// Read PoS map from eth address derived from cold keys to native addresses - fn read_eth_cold_key_addresses(&self) -> EthKeyAddresses; + /// Read PoS map from eth address derived from cold or hot keys to native + /// addresses + fn read_eth_key_addresses(&self) -> EthKeyAddresses; - /// Read PoS map from eth address derived from hot keys to native addresses - fn read_eth_hot_key_addresses(&self) -> EthKeyAddresses; - - /// Try to find a native address associated with the given eth address - /// derived from a eth cold key - fn find_address_from_eth_cold_key_address( + /// Try to find a native address associated with the given Ethereum address + /// derived from an Ethereum cold or hot key + fn find_address_from_eth_key_address( &self, eth_addr: &EthAddress, ) -> Option { - let addresses = self.read_eth_cold_key_addresses(); - addresses.get(eth_addr).cloned() - } - - /// Try to find a native address associated with the given eth address - /// derived from a eth hot key - fn find_address_from_eth_hot_key_address( - &self, - eth_addr: &EthAddress, - ) -> Option { - let addresses = self.read_eth_hot_key_addresses(); + let addresses = self.read_eth_key_addresses(); addresses.get(eth_addr).cloned() } } @@ -257,6 +246,10 @@ pub trait PosActions: PosReadOnly { value: ValidatorEthKey, ); + /// Write PoS map from eth address derived from cold or hot keys to native + /// addresses + fn write_eth_key_addresses(&self, value: EthKeyAddresses); + /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond(&mut self, key: &BondId); /// Delete an emptied PoS unbond (unbonded tokens from validator self-bond @@ -297,6 +290,18 @@ pub trait PosActions: PosReadOnly { ), ); } + let convert_key_to_addr = |k| { + k.try_into() + .map_err(|_| BecomeValidatorError::SecpKeyConversion) + }; + let eth_addresses_map = self.read_eth_key_addresses(); + let have_duped_key = eth_key_reverse_map + .contains_key(&convert_key_to_addr(eth_cold_key)) + || eth_key_reverse_map + .contains_key(&convert_key_to_addr(eth_hot_key)); + if have_duped_key { + return Err(BecomeValidatorError::DupedEthKeyFound); + } let BecomeValidatorData { consensus_key, eth_cold_key, @@ -584,6 +589,7 @@ pub trait PosBase { type PublicKey: 'static + Debug + Clone + + TryInto + BorshDeserialize + BorshSerialize + BorshSchema; @@ -707,6 +713,12 @@ pub trait PosBase { address: &Self::Address, value: &ValidatorEthKey, ); + /// Write PoS map from eth address derived from cold or hot keys to native + /// addresses + fn write_eth_key_addresses( + &mut self, + value: &EthKeyAddresses, + ); /// Initialize staking reward account with the given public key. fn init_staking_reward_account( &mut self, @@ -757,6 +769,8 @@ pub trait PosBase { total_bonded_balance, } = init_genesis(params, validators, current_epoch)?; + let mut eth_key_reverse_map = HashMap::default(); + for res in validators { let GenesisValidatorData { ref address, @@ -786,7 +800,22 @@ pub trait PosBase { &staking_reward_address, &staking_reward_key, ); + let convert_key_to_addr = + |k| k.try_into().map_err(|_| GenesisError::SecpKeyConversion); + if eth_key_reverse_map + .insert(convert_key_to_addr(ð_cold_key)?, address) + .is_some() + { + return Err(GenesisError::DupedEthKeyFound); + } + if eth_key_reverse_map + .insert(convert_key_to_addr(ð_hot_key)?, address) + .is_some() + { + return Err(GenesisError::DupedEthKeyFound); + } } + self.write_eth_key_addresses(eth_key_reverse_map); self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); // Credit the bonded tokens to the PoS account @@ -980,6 +1009,10 @@ pub trait PosBase { pub enum GenesisError { #[error("Voting power overflow: {0}")] VotingPowerOverflow(TryFromIntError), + #[error("Ethereum address can only be of secp kind")] + SecpKeyConversion, + #[error("Duplicate Ethereum key found")] + DupedEthKeyFound, } #[allow(missing_docs)] @@ -992,6 +1025,10 @@ pub enum BecomeValidatorError { address {0}" )] StakingRewardAddressEqValidatorAddress(Address), + #[error("Ethereum address can only be of secp kind")] + SecpKeyConversion, + #[error("Duplicate Ethereum key found")] + DupedEthKeyFound, } #[allow(missing_docs)] diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 8e92ad32b9..92c84446c4 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -24,8 +24,7 @@ const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; -const ETH_COLD_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_cold_key_addresses"; -const ETH_HOT_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_hot_key_addresses"; +const ETH_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_key_addresses"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; @@ -420,36 +419,20 @@ pub fn get_validator_address_from_bond(key: &Key) -> Option
{ } } -/// Storage key for look-up from validator's eth cold key addresses to native +/// Storage key for look-up from validator's eth key addresses to native /// address. -pub fn eth_cold_key_addresses_key() -> Key { +pub fn eth_key_addresses_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(Ð_COLD_KEY_ADDRESSES_STORAGE_KEY.to_owned()) + .push(Ð_KEY_ADDRESSES_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } -/// Is storage key for validators' eth cold key addresses? -pub fn is_eth_cold_key_addresses_key(key: &Key) -> bool { +/// Is storage key for validators' eth key addresses? +pub fn is_eth_key_addresses_key(key: &Key) -> bool { matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(suffix)] if addr == &ADDRESS - && suffix == ETH_COLD_KEY_ADDRESSES_STORAGE_KEY) -} - -/// Storage key for look-up from validator's eth hot key addresses to native -/// address. -pub fn eth_hot_key_addresses_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(Ð_HOT_KEY_ADDRESSES_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validators' eth hot key addresses? -pub fn is_eth_hot_key_addresses_key(key: &Key) -> bool { - matches!(&key.segments[..], - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(suffix)] - if addr == &ADDRESS - && suffix == ETH_HOT_KEY_ADDRESSES_STORAGE_KEY) + && suffix == ETH_KEY_ADDRESSES_STORAGE_KEY) } impl PosBase for Storage From e2838199fcda239a4fb8835565d77b00e9a5dda0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 9 Sep 2022 02:04:00 +0200 Subject: [PATCH 0618/1995] add eth keys to gen_genesis_validator test --- apps/src/lib/config/genesis.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index b0393cab5e..57987a8619 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -160,7 +160,6 @@ pub mod genesis_config { pub validator: HashMap, } - // TODO add eth keys here #[derive(Clone, Default, Debug, Deserialize, Serialize)] pub struct ValidatorConfig { // Public key for consensus. (default: generate) @@ -923,11 +922,18 @@ pub mod tests { let srkp_arr = staking_reward_keypair.try_to_vec().unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); + + // TODO: derive validator eth address from an eth keypair + let eth_cold_gov_keypair: common::SecretKey = secp256k1::SigScheme::generate(&mut rng).try_to_sk().unwrap(); + let eth_hot_bridge_keypair: common::SecretKey = secp256k1::SigScheme::generate(&mut rng).try_to_sk().unwrap(); + println!("address: {}", address); println!("staking_reward_address: {}", staking_reward_address); println!("keypair: {:?}", kp_arr); println!("staking_reward_keypair: {:?}", srkp_arr); println!("protocol_keypair: {:?}", protocol_keypair); println!("dkg_keypair: {:?}", dkg_keypair.try_to_vec().unwrap()); + println!("eth_cold_gov_keypair: {:?}", eth_cold_gov_keypair.try_to_vec().unwrap()); + println!("eth_hot_bridge_keypair: {:?}", eth_hot_bridge_keypair.try_to_vec().unwrap()); } } From 231a6645b8c513ea3af3777d041d8bac9c3f149c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 13:14:36 +0100 Subject: [PATCH 0619/1995] WIP: adding secp key as a ValidatorKeys field --- apps/src/lib/config/genesis.rs | 20 ++++++++++++++++---- apps/src/lib/wallet/store.rs | 23 +++++++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 57987a8619..fc916b9caa 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -924,8 +924,14 @@ pub mod tests { wallet::defaults::validator_keys(); // TODO: derive validator eth address from an eth keypair - let eth_cold_gov_keypair: common::SecretKey = secp256k1::SigScheme::generate(&mut rng).try_to_sk().unwrap(); - let eth_hot_bridge_keypair: common::SecretKey = secp256k1::SigScheme::generate(&mut rng).try_to_sk().unwrap(); + let eth_cold_gov_keypair: common::SecretKey = + secp256k1::SigScheme::generate(&mut rng) + .try_to_sk() + .unwrap(); + let eth_hot_bridge_keypair: common::SecretKey = + secp256k1::SigScheme::generate(&mut rng) + .try_to_sk() + .unwrap(); println!("address: {}", address); println!("staking_reward_address: {}", staking_reward_address); @@ -933,7 +939,13 @@ pub mod tests { println!("staking_reward_keypair: {:?}", srkp_arr); println!("protocol_keypair: {:?}", protocol_keypair); println!("dkg_keypair: {:?}", dkg_keypair.try_to_vec().unwrap()); - println!("eth_cold_gov_keypair: {:?}", eth_cold_gov_keypair.try_to_vec().unwrap()); - println!("eth_hot_bridge_keypair: {:?}", eth_hot_bridge_keypair.try_to_vec().unwrap()); + println!( + "eth_cold_gov_keypair: {:?}", + eth_cold_gov_keypair.try_to_vec().unwrap() + ); + println!( + "eth_hot_bridge_keypair: {:?}", + eth_hot_bridge_keypair.try_to_vec().unwrap() + ); } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 356b7add53..2f16aa7fee 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 either::*; use file_lock::{FileLock, FileOptions}; use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::dkg_session_keys::DkgKeypair; @@ -288,16 +289,26 @@ impl Store { /// /// Note that this removes the validator data. pub fn gen_validator_keys( - protocol_keypair: Option, - scheme: SchemeType, + ethereum_bridge_key: Option, + protocol_keypair: Either, ) -> ValidatorKeys { - let protocol_keypair = - protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); + let ethereum_bridge_key = ethereum_bridge_key + .map(|k| { + if !matches!(&k, common::SecretKey::Secp256k1(_)) { + panic!( + "Ethereum bridge keys can only be of kind Secp256k1" + ); + } + k + }) + .unwrap_or_else(|| gen_sk(SchemeType::Secp256k1)); + let protocol_keypair = protocol_keypair.map_left(gen_sk).into_inner(); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); ValidatorKeys { protocol_keypair, + ethereum_bridge_key, dkg_keypair: Some(dkg_keypair.into()), } } @@ -528,7 +539,7 @@ mod test_wallet { fn test_toml_roundtrip_ed25519() { let mut store = Store::new(); let validator_keys = - Store::gen_validator_keys(None, SchemeType::Ed25519); + Store::gen_validator_keys(None, Left(SchemeType::Ed25519)); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -541,7 +552,7 @@ mod test_wallet { fn test_toml_roundtrip_secp256k1() { let mut store = Store::new(); let validator_keys = - Store::gen_validator_keys(None, SchemeType::Secp256k1); + Store::gen_validator_keys(None, Left(SchemeType::Secp256k1)); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys From 8e27bba534080197606d6c13284e15b44bd74adc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 13:26:34 +0100 Subject: [PATCH 0620/1995] WIP: add missing ValidatorKeys field --- apps/src/lib/wallet/store.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 2f16aa7fee..ac015c2ef5 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -28,6 +28,8 @@ use crate::config::genesis::genesis_config::GenesisConfig; pub struct ValidatorKeys { /// Special keypair for signing protocol txs pub protocol_keypair: common::SecretKey, + /// Special keypair for signing Ethereum bridge txs + pub eth_bridge_keypair: common::SecretKey, /// Special session keypair needed by validators for participating /// in the DKG protocol pub dkg_keypair: Option, @@ -289,10 +291,10 @@ impl Store { /// /// Note that this removes the validator data. pub fn gen_validator_keys( - ethereum_bridge_key: Option, + eth_bridge_keypair: Option, protocol_keypair: Either, ) -> ValidatorKeys { - let ethereum_bridge_key = ethereum_bridge_key + let eth_bridge_keypair = eth_bridge_keypair .map(|k| { if !matches!(&k, common::SecretKey::Secp256k1(_)) { panic!( @@ -308,7 +310,7 @@ impl Store { ); ValidatorKeys { protocol_keypair, - ethereum_bridge_key, + eth_bridge_keypair, dkg_keypair: Some(dkg_keypair.into()), } } From ade2415e96fda91a76c22dd521bd401e0b814ad5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 9 Sep 2022 15:35:00 +0100 Subject: [PATCH 0621/1995] WIP: gen eth keys --- apps/src/lib/wallet/mod.rs | 58 ++++++++++++++++++++++++------------ apps/src/lib/wallet/store.rs | 6 ++-- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index b3048ef97d..1feb8108c1 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -118,27 +118,47 @@ impl Wallet { /// we should re-use a keypair already in the wallet pub fn gen_validator_keys( &mut self, + eth_bridge_pk: Option, protocol_pk: Option, - scheme: SchemeType, + protocol_key_scheme: SchemeType, ) -> Result { - let protocol_keypair = protocol_pk.map(|pk| { - self.find_key_by_pkh(&PublicKeyHash::from(&pk)) - .ok() - .or_else(|| { - self.store - .validator_data - .take() - .map(|data| Rc::new(data.keys.protocol_keypair)) - }) - .ok_or(FindKeyError::KeyNotFound) - }); - match protocol_keypair { - Some(Err(err)) => Err(err), - other => Ok(Store::gen_validator_keys( - other.map(|res| res.unwrap().as_ref().clone()), - scheme, - )), - } + let protocol_keypair = self.find_secret_key(protocol_pk, |data| { + Rc::new(data.keys.protocol_keypair) + })?; + let eth_bridge_keypair = self + .find_secret_key(eth_bridge_pk, |data| { + Rc::new(data.keys.eth_bridge_keypair) + })?; + Ok(Store::gen_validator_keys( + eth_bridge_keypair, + protocol_keypair, + protocol_key_scheme, + )) + } + + /// Find a corresponding [`common::SecretKey`] in [`Store`], for some + /// [`common::PublicKey`]. + /// + /// If a key was provided in `maybe_pk`, and it's found in [`Store`], we use + /// `extract_key` to retrieve it from [`ValidatorData`]. + fn find_secret_key( + &mut self, + maybe_pk: Option, + extract_key: F, + ) -> Result>, FindKeyError> + where + F: Fn(ValidatorData) -> Rc, + { + maybe_pk + .map(|pk| { + self.find_key_by_pkh(&PublicKeyHash::from(&pk)) + .ok() + .or_else(|| { + self.store.validator_data.take().map(extract_key) + }) + .ok_or(FindKeyError::KeyNotFound) + }) + .transpose() } /// Add validator data to the store diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index ac015c2ef5..36bb6b4b0a 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -292,7 +292,8 @@ impl Store { /// Note that this removes the validator data. pub fn gen_validator_keys( eth_bridge_keypair: Option, - protocol_keypair: Either, + protocol_keypair: Option, + protocol_keypair_scheme: SchemeType, ) -> ValidatorKeys { let eth_bridge_keypair = eth_bridge_keypair .map(|k| { @@ -304,7 +305,8 @@ impl Store { k }) .unwrap_or_else(|| gen_sk(SchemeType::Secp256k1)); - let protocol_keypair = protocol_keypair.map_left(gen_sk).into_inner(); + let protocol_keypair = + protocol_keypair.unwrap_or_else(|| gen_sk(protocol_keypair_scheme)); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); From aa7e9970e38b915af62b3f4a9e7c2dc859c23601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 17:31:17 +0200 Subject: [PATCH 0622/1995] fixup! WIP: gen eth keys --- apps/src/lib/wallet/store.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 36bb6b4b0a..d296137572 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -28,12 +28,11 @@ use crate::config::genesis::genesis_config::GenesisConfig; pub struct ValidatorKeys { /// Special keypair for signing protocol txs pub protocol_keypair: common::SecretKey, - /// Special keypair for signing Ethereum bridge txs + /// Special hot keypair for signing Ethereum bridge txs pub eth_bridge_keypair: common::SecretKey, /// Special session keypair needed by validators for participating /// in the DKG protocol pub dkg_keypair: Option, - // TODO add eth hot key } impl ValidatorKeys { From 5ba47813e1a75b294ec758afd3e2780d65438c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 17:33:57 +0200 Subject: [PATCH 0623/1995] fixup! WIP: add maps for eth address reverse lookup to native address --- proof_of_stake/src/lib.rs | 97 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 581560513a..b5822e55be 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -20,7 +20,7 @@ pub mod validation; use core::fmt::Debug; use std::collections::{BTreeSet, HashMap}; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use std::hash::Hash; use std::num::TryFromIntError; @@ -91,7 +91,6 @@ pub trait PosReadOnly { /// Cryptographic public key type type PublicKey: Debug + Clone - + TryInto + BorshDeserialize + BorshSerialize + BorshSchema; @@ -274,7 +273,10 @@ pub trait PosActions: PosReadOnly { eth_cold_key: &Self::PublicKey, eth_hot_key: &Self::PublicKey, current_epoch: impl Into, - ) -> Result<(), BecomeValidatorError> { + ) -> Result<(), BecomeValidatorError> + where + for<'a> &'a Self::PublicKey: TryInto, + { let current_epoch = current_epoch.into(); let params = self.read_pos_params(); let mut validator_set = self.read_validator_set(); @@ -290,22 +292,13 @@ pub trait PosActions: PosReadOnly { ), ); } - let convert_key_to_addr = |k| { - k.try_into() - .map_err(|_| BecomeValidatorError::SecpKeyConversion) - }; - let eth_addresses_map = self.read_eth_key_addresses(); - let have_duped_key = eth_key_reverse_map - .contains_key(&convert_key_to_addr(eth_cold_key)) - || eth_key_reverse_map - .contains_key(&convert_key_to_addr(eth_hot_key)); - if have_duped_key { - return Err(BecomeValidatorError::DupedEthKeyFound); - } let BecomeValidatorData { consensus_key, eth_cold_key, eth_hot_key, + eth_cold_key_addr, + eth_hot_key_addr, + state, total_deltas, voting_power, @@ -317,7 +310,20 @@ pub trait PosActions: PosReadOnly { eth_hot_key, &mut validator_set, current_epoch, - ); + )?; + let mut eth_addresses_map = self.read_eth_key_addresses(); + if eth_addresses_map + .insert(eth_cold_key_addr, address.clone()) + .is_some() + { + return Err(BecomeValidatorError::DupedEthKeyFound); + } + if eth_addresses_map + .insert(eth_hot_key_addr, address.clone()) + .is_some() + { + return Err(BecomeValidatorError::DupedEthKeyFound); + } self.write_validator_staking_reward_address( address, staking_reward_address.clone(), @@ -325,6 +331,7 @@ pub trait PosActions: PosReadOnly { self.write_validator_consensus_key(address, consensus_key); self.write_validator_eth_cold_key(address, eth_cold_key); self.write_validator_eth_hot_key(address, eth_hot_key); + self.write_eth_key_addresses(eth_addresses_map); self.write_validator_state(address, state); self.write_validator_set(validator_set); self.write_validator_address_raw_hash(address); @@ -589,7 +596,6 @@ pub trait PosBase { type PublicKey: 'static + Debug + Clone - + TryInto + BorshDeserialize + BorshSerialize + BorshSchema; @@ -758,7 +764,10 @@ pub trait PosBase { > + Clone + 'a, current_epoch: impl Into, - ) -> Result<(), GenesisError> { + ) -> Result<(), GenesisError> + where + &'a Self::PublicKey: TryInto, + { let current_epoch = current_epoch.into(); self.write_pos_params(params); @@ -769,7 +778,7 @@ pub trait PosBase { total_bonded_balance, } = init_genesis(params, validators, current_epoch)?; - let mut eth_key_reverse_map = HashMap::default(); + let mut eth_addresses_map = HashMap::default(); for res in validators { let GenesisValidatorData { @@ -783,6 +792,8 @@ pub trait PosBase { bond: (bond_id, bond), eth_cold_key, eth_hot_key, + eth_cold_key_addr, + eth_hot_key_addr, } = res?; self.write_validator_address_raw_hash(address); self.write_validator_staking_reward_address( @@ -800,22 +811,20 @@ pub trait PosBase { &staking_reward_address, &staking_reward_key, ); - let convert_key_to_addr = - |k| k.try_into().map_err(|_| GenesisError::SecpKeyConversion); - if eth_key_reverse_map - .insert(convert_key_to_addr(ð_cold_key)?, address) + if eth_addresses_map + .insert(eth_cold_key_addr, address.clone()) .is_some() { return Err(GenesisError::DupedEthKeyFound); } - if eth_key_reverse_map - .insert(convert_key_to_addr(ð_hot_key)?, address) + if eth_addresses_map + .insert(eth_hot_key_addr, address.clone()) .is_some() { return Err(GenesisError::DupedEthKeyFound); } } - self.write_eth_key_addresses(eth_key_reverse_map); + self.write_eth_key_addresses(ð_addresses_map); self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); // Credit the bonded tokens to the PoS account @@ -1182,6 +1191,8 @@ where bond: (BondId
, Bonds), eth_cold_key: ValidatorEthKey, eth_hot_key: ValidatorEthKey, + eth_cold_key_addr: EthAddress, + eth_hot_key_addr: EthAddress, } /// A function that returns genesis data created from the initial validator set. @@ -1236,6 +1247,7 @@ where + BorshSerialize + BorshSchema, PK: 'a + Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, + &'a PK: TryInto, { // Accumulate the validator set and total voting power let mut active: BTreeSet> = BTreeSet::default(); @@ -1284,6 +1296,11 @@ where eth_cold_key, eth_hot_key, }| { + let convert_key_to_addr = |k: &'a PK| { + k.try_into().map_err(|_| GenesisError::SecpKeyConversion) + }; + let eth_cold_key_addr = convert_key_to_addr(ð_cold_key)?; + let eth_hot_key_addr = convert_key_to_addr(ð_hot_key)?; let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); let eth_cold_key = @@ -1321,6 +1338,8 @@ where bond: (bond_id, bond), eth_cold_key, eth_hot_key, + eth_cold_key_addr, + eth_hot_key_addr, }) }, ); @@ -1433,23 +1452,26 @@ where consensus_key: ValidatorConsensusKeys, eth_cold_key: ValidatorEthKey, eth_hot_key: ValidatorEthKey, + eth_cold_key_addr: EthAddress, + eth_hot_key_addr: EthAddress, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, } /// A function that initialized data for a new validator. -fn become_validator( +fn become_validator<'a, Address, PK, TokenChange>( params: &PosParams, address: &Address, consensus_key: &PK, - eth_cold_key: &PK, - eth_hot_key: &PK, + eth_cold_key: &'a PK, + eth_hot_key: &'a PK, validator_set: &mut ValidatorSets
, current_epoch: Epoch, -) -> BecomeValidatorData +) -> Result, BecomeValidatorError
> where - Address: Debug + Address: Display + + Debug + Clone + Ord + Hash @@ -1457,6 +1479,7 @@ where + BorshSerialize + BorshSchema, PK: Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, + &'a PK: TryInto, TokenChange: Default + Debug + Clone @@ -1466,6 +1489,12 @@ where + BorshSerialize + BorshSchema, { + let convert_key_to_addr = |k: &'a PK| { + k.try_into() + .map_err(|_| BecomeValidatorError::SecpKeyConversion) + }; + let eth_cold_key_addr = convert_key_to_addr(ð_cold_key)?; + let eth_hot_key_addr = convert_key_to_addr(ð_hot_key)?; let consensus_key = Epoched::init(consensus_key.clone(), current_epoch, params); let eth_cold_key = @@ -1507,14 +1536,16 @@ where params, ); - BecomeValidatorData { + Ok(BecomeValidatorData { consensus_key, state, total_deltas, voting_power, eth_cold_key, eth_hot_key, - } + eth_cold_key_addr, + eth_hot_key_addr, + }) } struct BondData From d3143f30bfd19a9c7f4e9b74030824c67b4e5342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 9 Sep 2022 18:42:52 +0200 Subject: [PATCH 0624/1995] write eth keys and addresses for genesis and on-chain created validators --- proof_of_stake/src/lib.rs | 39 ++++++++++++++++++----------- proof_of_stake/src/types.rs | 23 +++++++++++++++-- shared/src/ledger/pos/mod.rs | 41 +++++++++++++++++++++++++++---- shared/src/ledger/pos/storage.rs | 7 ++++++ shared/src/ledger/pos/vp.rs | 13 ++++++++-- shared/src/types/key/common.rs | 21 ++++++++++++++++ shared/src/types/key/secp256k1.rs | 2 +- vm_env/src/proof_of_stake.rs | 11 +++++++++ 8 files changed, 132 insertions(+), 25 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index b5822e55be..8c7a39c628 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -20,7 +20,7 @@ pub mod validation; use core::fmt::Debug; use std::collections::{BTreeSet, HashMap}; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; use std::num::TryFromIntError; @@ -34,8 +34,8 @@ use parameters::PosParams; use thiserror::Error; use types::{ ActiveValidator, Bonds, Epoch, EthAddress, EthKeyAddresses, - GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, - Unbonds, ValidatorConsensusKeys, ValidatorEthKey, ValidatorSet, + GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, TryRefTo, + Unbond, Unbonds, ValidatorConsensusKeys, ValidatorEthKey, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; @@ -275,7 +275,7 @@ pub trait PosActions: PosReadOnly { current_epoch: impl Into, ) -> Result<(), BecomeValidatorError> where - for<'a> &'a Self::PublicKey: TryInto, + Self::PublicKey: TryRefTo, { let current_epoch = current_epoch.into(); let params = self.read_pos_params(); @@ -766,7 +766,7 @@ pub trait PosBase { current_epoch: impl Into, ) -> Result<(), GenesisError> where - &'a Self::PublicKey: TryInto, + Self::PublicKey: TryRefTo, { let current_epoch = current_epoch.into(); self.write_pos_params(params); @@ -1246,8 +1246,13 @@ where + BorshDeserialize + BorshSerialize + BorshSchema, - PK: 'a + Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, - &'a PK: TryInto, + PK: 'a + + Debug + + Clone + + BorshDeserialize + + BorshSerialize + + BorshSchema + + TryRefTo, { // Accumulate the validator set and total voting power let mut active: BTreeSet> = BTreeSet::default(); @@ -1297,10 +1302,10 @@ where eth_hot_key, }| { let convert_key_to_addr = |k: &'a PK| { - k.try_into().map_err(|_| GenesisError::SecpKeyConversion) + k.try_ref_to().map_err(|_| GenesisError::SecpKeyConversion) }; - let eth_cold_key_addr = convert_key_to_addr(ð_cold_key)?; - let eth_hot_key_addr = convert_key_to_addr(ð_hot_key)?; + let eth_cold_key_addr = convert_key_to_addr(eth_cold_key)?; + let eth_hot_key_addr = convert_key_to_addr(eth_hot_key)?; let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); let eth_cold_key = @@ -1478,8 +1483,12 @@ where + BorshDeserialize + BorshSerialize + BorshSchema, - PK: Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, - &'a PK: TryInto, + PK: Debug + + Clone + + BorshDeserialize + + BorshSerialize + + BorshSchema + + TryRefTo, TokenChange: Default + Debug + Clone @@ -1490,11 +1499,11 @@ where + BorshSchema, { let convert_key_to_addr = |k: &'a PK| { - k.try_into() + k.try_ref_to() .map_err(|_| BecomeValidatorError::SecpKeyConversion) }; - let eth_cold_key_addr = convert_key_to_addr(ð_cold_key)?; - let eth_hot_key_addr = convert_key_to_addr(ð_hot_key)?; + let eth_cold_key_addr = convert_key_to_addr(eth_cold_key)?; + let eth_hot_key_addr = convert_key_to_addr(eth_hot_key)?; let consensus_key = Epoched::init(consensus_key.clone(), current_epoch, params); let eth_cold_key = diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index c421903197..a559b67fa9 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -43,8 +43,27 @@ pub type ValidatorEthKey = Epoched; pub type EthKeyAddresses
= HashMap; /// Eth address derived from secp256k1 key -#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct EthAddress([u8; 20]); +#[derive( + Debug, + Eq, + PartialEq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct EthAddress(pub [u8; 20]); + +/// A ref-to-value conversion that may fail + +pub trait TryRefTo { + /// The error + type Error; + /// Try to perform the conversion. + fn try_ref_to(&self) -> Result; +} /// Epoch identifier. Epochs are identified by consecutive natural numbers. /// diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index c980da81da..7bfdd3cee6 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -3,6 +3,8 @@ mod storage; pub mod vp; +use std::convert::{TryFrom, TryInto}; + pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::types::{ @@ -15,8 +17,10 @@ pub use vp::PosVP; use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{self, Address, InternalAddress}; +use crate::types::key::common; +use crate::types::key::secp256k1::EthAddress; use crate::types::storage::Epoch; -use crate::types::{key, token}; +use crate::types::token; /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); @@ -47,9 +51,7 @@ pub fn init_genesis_storage<'a, DB, H>( /// Alias for a PoS type with the same name with concrete type parameters pub type ValidatorConsensusKeys = - namada_proof_of_stake::types::ValidatorConsensusKeys< - key::common::PublicKey, - >; + namada_proof_of_stake::types::ValidatorConsensusKeys; /// Alias for a PoS type with the same name with concrete type parameters pub type ValidatorTotalDeltas = @@ -71,7 +73,7 @@ pub type BondId = namada_proof_of_stake::types::BondId
; pub type GenesisValidator = namada_proof_of_stake::types::GenesisValidator< Address, token::Amount, - key::common::PublicKey, + common::PublicKey, >; impl From for namada_proof_of_stake::types::Epoch { @@ -87,3 +89,32 @@ impl From for Epoch { Epoch(epoch) } } + +impl From for namada_proof_of_stake::types::EthAddress { + fn from(EthAddress(addr): EthAddress) -> Self { + namada_proof_of_stake::types::EthAddress(addr) + } +} + +impl TryFrom<&common::PublicKey> for namada_proof_of_stake::types::EthAddress { + type Error = common::EthAddressConvError; + + fn try_from(value: &common::PublicKey) -> Result { + let addr = EthAddress::try_from(value)?; + Ok(addr.into()) + } +} + +impl + namada_proof_of_stake::types::TryRefTo< + namada_proof_of_stake::types::EthAddress, + > for common::PublicKey +{ + type Error = common::EthAddressConvError; + + fn try_ref_to( + &self, + ) -> Result { + self.try_into() + } +} diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 92c84446c4..3d613926ce 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -633,6 +633,13 @@ where .unwrap(); } + fn write_eth_key_addresses( + &mut self, + value: &types::EthKeyAddresses, + ) { + self.write(ð_key_addresses_key(), encode(value)).unwrap(); + } + fn init_staking_reward_account( &mut self, address: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 7d2b7680f1..66a348fbf9 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -16,8 +16,8 @@ use namada_proof_of_stake::{validation, PosReadOnly}; use thiserror::Error; use super::{ - bond_key, is_bond_key, is_params_key, is_total_voting_power_key, - is_unbond_key, is_validator_set_key, + bond_key, eth_key_addresses_key, is_bond_key, is_params_key, + is_total_voting_power_key, is_unbond_key, is_validator_set_key, is_validator_staking_reward_address_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_key, validator_consensus_key_key, @@ -433,6 +433,15 @@ where let value = self.ctx.read_pre(&validator_eth_hot_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } + + fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { + let value = self + .ctx + .read_pre(ð_key_addresses_key()) + .unwrap() + .unwrap(); + decode(value).unwrap() + } } impl From for Error { diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 3cdec73bb9..861eac5b53 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -1,5 +1,6 @@ //! Cryptographic keys +use std::convert::TryFrom; use std::fmt::Display; use std::str::FromStr; @@ -7,7 +8,9 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; +use thiserror::Error; +use super::secp256k1::EthAddress; use super::{ ed25519, secp256k1, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, @@ -81,6 +84,24 @@ impl FromStr for PublicKey { } } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum EthAddressConvError { + #[error("Eth key cannot be ed25519, only secp256k1")] + CannotBeEd25519, +} + +impl TryFrom<&PublicKey> for EthAddress { + type Error = EthAddressConvError; + + fn try_from(value: &PublicKey) -> Result { + match value { + PublicKey::Ed25519(_) => Err(EthAddressConvError::CannotBeEd25519), + PublicKey::Secp256k1(pk) => Ok(EthAddress::from(pk).into()), + } + } +} + /// Secret key #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] #[allow(clippy::large_enum_variant)] diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 198cb8d74b..d3b037c0e3 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -24,7 +24,7 @@ pub struct PublicKey(pub libsecp256k1::PublicKey); /// Eth address derived from secp256k1 key #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct EthAddress([u8; 20]); +pub struct EthAddress(pub [u8; 20]); impl super::PublicKey for PublicKey { const TYPE: SchemeType = SigScheme::TYPE; diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 53e2ab9470..835d7d6ab1 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -187,6 +187,10 @@ impl namada_proof_of_stake::PosReadOnly for PoS { ) -> Option { tx::read(validator_eth_hot_key_key(key).to_string()) } + + fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { + tx::read(eth_key_addresses_key().to_string()).unwrap() + } } impl namada_proof_of_stake::PosActions for PoS { @@ -294,4 +298,11 @@ impl namada_proof_of_stake::PosActions for PoS { ) { tx::write(validator_eth_hot_key_key(address).to_string(), &value) } + + fn write_eth_key_addresses( + &self, + value: types::EthKeyAddresses, + ) { + tx::write(eth_key_addresses_key().to_string(), &value) + } } From dc0573392510a9e35a69b0cbbbfc21838ebbdc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 12 Sep 2022 16:16:38 +0200 Subject: [PATCH 0625/1995] fixup! WIP: gen eth keys --- apps/src/lib/client/tx.rs | 13 +++++++--- apps/src/lib/client/utils.rs | 35 +++++++++++++++++++++++++++ apps/src/lib/config/genesis.rs | 14 +++-------- apps/src/lib/node/ledger/shell/mod.rs | 8 +++--- apps/src/lib/wallet/defaults.rs | 16 ++++++++++-- apps/src/lib/wallet/mod.rs | 10 ++++++-- apps/src/lib/wallet/pre_genesis.rs | 15 +++++------- apps/src/lib/wallet/store.rs | 5 ++-- shared/src/types/key/common.rs | 2 +- 9 files changed, 84 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a1aabfe532..370780b0c1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -177,6 +177,8 @@ pub async fn submit_init_validator( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); let rewards_key_alias = format!("{}-rewards-key", alias); + let eth_hot_key_alias = format!("{}-eth-hot-key", alias); + let eth_cold_key_alias = format!("{}-eth-cold-key", alias); let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet @@ -225,7 +227,7 @@ pub async fn submit_init_validator( .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, - Some(consensus_key_alias.clone()), + Some(eth_cold_key_alias.clone()), unsafe_dont_encrypt, ) .1 @@ -246,7 +248,7 @@ pub async fn submit_init_validator( .gen_key( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, - Some(consensus_key_alias.clone()), + Some(eth_hot_key_alias.clone()), unsafe_dont_encrypt, ) .1 @@ -269,9 +271,12 @@ pub async fn submit_init_validator( if protocol_key.is_none() { println!("Generating protocol signing key..."); } + let eth_hot_pk = eth_hot_key.ref_to(); // Generate the validator keys - let validator_keys = - ctx.wallet.gen_validator_keys(protocol_key, scheme).unwrap(); + let validator_keys = ctx + .wallet + .gen_validator_keys(Some(eth_hot_pk), 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 ca0a3d7347..0c06851684 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -590,6 +590,36 @@ pub fn init_network( keypair.ref_to() }); + let eth_hot_pk = try_parse_public_key( + format!("validator {name} eth hot key"), + &config.eth_hot_key, + ) + .unwrap_or_else(|| { + let alias = format!("{}-eth-hot-key", name); + println!("Generating validator {} eth hot key...", name); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Secp256k1, + Some(alias), + unsafe_dont_encrypt, + ); + keypair.ref_to() + }); + + let eth_cold_pk = try_parse_public_key( + format!("validator {name} eth cold key"), + &config.eth_cold_key, + ) + .unwrap_or_else(|| { + let alias = format!("{}-eth-cold-key", name); + println!("Generating validator {} eth cold key...", name); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Secp256k1, + Some(alias), + unsafe_dont_encrypt, + ); + keypair.ref_to() + }); + let dkg_pk = &config .dkg_public_key .as_ref() @@ -608,6 +638,7 @@ pub fn init_network( let validator_keys = wallet .gen_validator_keys( + Some(eth_hot_pk.clone()), Some(protocol_pk.clone()), SchemeType::Ed25519, ) @@ -624,6 +655,10 @@ pub fn init_network( Some(genesis_config::HexString(account_pk.to_string())); config.staking_reward_public_key = Some(genesis_config::HexString(staking_reward_pk.to_string())); + config.eth_cold_key = + Some(genesis_config::HexString(eth_cold_pk.to_string())); + config.eth_hot_key = + Some(genesis_config::HexString(eth_hot_pk.to_string())); config.protocol_public_key = Some(genesis_config::HexString(protocol_pk.to_string())); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index fc916b9caa..80ef4ee7b0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -772,21 +772,15 @@ pub fn genesis() -> Genesis { 65, 17, 187, 6, 238, 141, 63, 188, 76, 38, 102, 7, 47, 185, 28, 52, ]) .unwrap(); - let secp_eth_hot_keypair = secp256k1::SecretKey::try_from_slice(&[ - 117, 93, 118, 129, 202, 67, 51, 62, 202, 196, 130, 244, 5, 44, 88, 200, - 121, 169, 11, 227, 79, 223, 74, 88, 49, 132, 213, 59, 64, 20, 13, 82, - ]) - .unwrap(); let staking_reward_keypair = common::SecretKey::try_from_sk(&ed_staking_reward_keypair).unwrap(); let eth_cold_keypair = common::SecretKey::try_from_sk(&secp_eth_cold_keypair).unwrap(); - let eth_hot_keypair = - common::SecretKey::try_from_sk(&secp_eth_hot_keypair).unwrap(); let address = wallet::defaults::validator_address(); let staking_reward_address = Address::decode("atest1v4ehgw36xcersvee8qerxd35x9prsw2xg5erxv6pxfpygd2x89z5xsf5xvmnysejgv6rwd2rnj2avt").unwrap(); - let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); + let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = + wallet::defaults::validator_keys(); let validator = Validator { pos_data: GenesisValidator { address, @@ -795,7 +789,7 @@ pub fn genesis() -> Genesis { consensus_key: consensus_keypair.ref_to(), staking_reward_key: staking_reward_keypair.ref_to(), eth_cold_key: eth_cold_keypair.ref_to(), - eth_hot_key: eth_hot_keypair.ref_to(), + eth_hot_key: eth_bridge_keypair.ref_to(), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -920,7 +914,7 @@ pub mod tests { let staking_reward_keypair: common::SecretKey = ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); let srkp_arr = staking_reward_keypair.try_to_vec().unwrap(); - let (protocol_keypair, dkg_keypair) = + let (protocol_keypair, _eth_hot_bridge_keypair, dkg_keypair) = wallet::defaults::validator_keys(); // TODO: derive validator eth address from an eth keypair diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 68c9c44d00..fde27d0288 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -379,13 +379,15 @@ where } #[cfg(feature = "dev")] { - let validator_keys = wallet::defaults::validator_keys(); + let (protocol_keypair, eth_bridge_keypair, dkg_keypair) = + wallet::defaults::validator_keys(); ShellMode::Validator { data: wallet::ValidatorData { address: wallet::defaults::validator_address(), keys: wallet::ValidatorKeys { - protocol_keypair: validator_keys.0, - dkg_keypair: Some(validator_keys.1), + protocol_keypair, + eth_bridge_keypair, + dkg_keypair: Some(dkg_keypair), }, }, broadcast_sender, diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index eb5a6f3d7a..d4fb1b1b83 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -81,13 +81,23 @@ mod dev { use crate::wallet::alias::Alias; - /// Generate a new protocol signing keypair and DKG session keypair - pub fn validator_keys() -> (common::SecretKey, DkgKeypair) { + /// Generate a new protocol signing keypair, eth hot key and DKG session + /// keypair + pub fn validator_keys() -> (common::SecretKey, common::SecretKey, DkgKeypair) + { + // ed25519 bytes let bytes: [u8; 33] = [ 0, 200, 107, 23, 252, 78, 80, 8, 164, 142, 3, 194, 33, 12, 250, 169, 211, 127, 47, 13, 194, 54, 199, 81, 102, 246, 189, 119, 144, 25, 27, 113, 222, ]; + // secp256k1 bytes + let eth_bridge_key_bytes = [ + 1, 117, 93, 118, 129, 202, 67, 51, 62, 202, 196, 130, 244, 5, 44, + 88, 200, 121, 169, 11, 227, 79, 223, 74, 88, 49, 132, 213, 59, 64, + 20, 13, 82, + ]; + // DkgKeypair let dkg_bytes = [ 32, 0, 0, 0, 210, 193, 55, 24, 92, 233, 23, 2, 73, 204, 221, 107, 110, 222, 192, 136, 54, 24, 108, 236, 137, 27, 121, 142, 142, 7, @@ -96,6 +106,8 @@ mod dev { ( BorshDeserialize::deserialize(&mut bytes.as_ref()).unwrap(), + BorshDeserialize::deserialize(&mut eth_bridge_key_bytes.as_ref()) + .unwrap(), BorshDeserialize::deserialize(&mut dkg_bytes.as_ref()).unwrap(), ) } diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 1feb8108c1..492cee51bc 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -130,8 +130,14 @@ impl Wallet { Rc::new(data.keys.eth_bridge_keypair) })?; Ok(Store::gen_validator_keys( - eth_bridge_keypair, - protocol_keypair, + eth_bridge_keypair.map(|sk| { + Rc::try_unwrap(sk) + .expect("There should be only a single strong RC reference") + }), + protocol_keypair.map(|sk| { + Rc::try_unwrap(sk) + .expect("There should be only a single strong RC reference") + }), protocol_key_scheme, )) } diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index bc5bb84106..c2c7cf393b 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -60,13 +60,11 @@ pub struct ValidatorStore { pub consensus_key: wallet::StoredKeypair, /// Cryptographic keypair for eth cold key pub eth_cold_key: wallet::StoredKeypair, - /// Cryptographic keypair for eth hot key - pub eth_hot_key: wallet::StoredKeypair, /// Cryptographic keypair for rewards key pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: wallet::StoredKeypair, - /// Special validator keys + /// Special validator keys. Contains the ETH hot key. pub validator_keys: wallet::ValidatorKeys, } @@ -130,7 +128,7 @@ impl ValidatorWallet { let eth_cold_key = store.eth_cold_key.get(true, password.clone())?; let eth_hot_key = - store.eth_hot_key.get(true, password.clone())?; + Rc::new(store.validator_keys.eth_bridge_keypair.clone()); let rewards_key = store.rewards_key.get(true, password.clone())?; @@ -166,8 +164,6 @@ impl ValidatorWallet { ); let (eth_cold_key, eth_cold_sk) = gen_key_to_store(SchemeType::Secp256k1, &password); - let (eth_hot_key, eth_hot_sk) = - gen_key_to_store(SchemeType::Secp256k1, &password); let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( @@ -175,12 +171,13 @@ impl ValidatorWallet { SchemeType::Ed25519, &password, ); - let validator_keys = store::Store::gen_validator_keys(None, scheme); + let validator_keys = + store::Store::gen_validator_keys(None, None, scheme); + let eth_hot_key = Rc::new(validator_keys.eth_bridge_keypair.clone()); let store = ValidatorStore { account_key, consensus_key, eth_cold_key, - eth_hot_key, rewards_key, tendermint_node_key, validator_keys, @@ -190,7 +187,7 @@ impl ValidatorWallet { account_key: account_sk, consensus_key: consensus_sk, eth_cold_key: eth_cold_sk, - eth_hot_key: eth_hot_sk, + eth_hot_key, rewards_key: rewards_sk, tendermint_node_key: tendermint_node_sk, } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index d296137572..88af4c9111 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -8,7 +8,6 @@ use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; -use either::*; use file_lock::{FileLock, FileOptions}; use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::dkg_session_keys::DkgKeypair; @@ -542,7 +541,7 @@ mod test_wallet { fn test_toml_roundtrip_ed25519() { let mut store = Store::new(); let validator_keys = - Store::gen_validator_keys(None, Left(SchemeType::Ed25519)); + Store::gen_validator_keys(None, None, SchemeType::Ed25519); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys @@ -555,7 +554,7 @@ mod test_wallet { fn test_toml_roundtrip_secp256k1() { let mut store = Store::new(); let validator_keys = - Store::gen_validator_keys(None, Left(SchemeType::Secp256k1)); + Store::gen_validator_keys(None, None, SchemeType::Secp256k1); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 861eac5b53..7e9b468e50 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -97,7 +97,7 @@ impl TryFrom<&PublicKey> for EthAddress { fn try_from(value: &PublicKey) -> Result { match value { PublicKey::Ed25519(_) => Err(EthAddressConvError::CannotBeEd25519), - PublicKey::Secp256k1(pk) => Ok(EthAddress::from(pk).into()), + PublicKey::Secp256k1(pk) => Ok(EthAddress::from(pk)), } } } From dac56f26b4a6fd81dcdedb8bbceac10e27fdf437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 12 Sep 2022 17:35:31 +0200 Subject: [PATCH 0626/1995] fixup! WIP: gen eth keys --- apps/src/lib/wallet/mod.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 492cee51bc..5646416a38 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -130,14 +130,8 @@ impl Wallet { Rc::new(data.keys.eth_bridge_keypair) })?; Ok(Store::gen_validator_keys( - eth_bridge_keypair.map(|sk| { - Rc::try_unwrap(sk) - .expect("There should be only a single strong RC reference") - }), - protocol_keypair.map(|sk| { - Rc::try_unwrap(sk) - .expect("There should be only a single strong RC reference") - }), + eth_bridge_keypair.map(|sk| sk.as_ref().clone()), + protocol_keypair.map(|sk| sk.as_ref().clone()), protocol_key_scheme, )) } From 5cbc47ca1e406c69e2a0e88527279d44978eecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 12 Sep 2022 17:35:34 +0200 Subject: [PATCH 0627/1995] fix cli --- apps/src/lib/cli.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f271433b9e..a714c2af22 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1765,9 +1765,9 @@ pub mod args { will be generated if none given. Note that this must be \ secp256k1.", )) - .arg(VALIDATOR_ETH_COLD_KEY.def().about( - "An Eth cold key for the validator account. A new one \ - will be generated if none given. Note that this must be \ + .arg(VALIDATOR_ETH_HOT_KEY.def().about( + "An Eth hot key for the validator account. A new one will \ + be generated if none given. Note that this must be \ secp256k1.", )) .arg(REWARDS_KEY.def().about( From 175da50fafc2f7bfacaf6ebac48a2b0d3e59e61a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 12 Sep 2022 16:45:44 +0100 Subject: [PATCH 0628/1995] Fix wasm tests --- shared/src/types/key/mod.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index bb80372c4e..1c5ac85558 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -367,7 +367,7 @@ pub mod testing { use super::SigScheme; use crate::types::key::*; - /// A keypair for tests + /// An ed25519 keypair for tests pub fn keypair_1() -> ::SecretKey { // generated from `cargo test gen_keypair -- --nocapture` let bytes = [ @@ -381,7 +381,7 @@ pub mod testing { .unwrap() } - /// A keypair for tests + /// An ed25519 keypair for tests pub fn keypair_2() -> ::SecretKey { // generated from `cargo test gen_keypair -- --nocapture` let bytes = [ @@ -395,6 +395,32 @@ pub mod testing { .unwrap() } + /// An Ethereum keypair for tests + pub fn keypair_3() -> ::SecretKey { + let bytes = [ + 0xf3, 0x78, 0x78, 0x80, 0xba, 0x85, 0x0b, 0xa4, 0xc5, 0x74, 0x50, + 0x5a, 0x23, 0x54, 0x6d, 0x46, 0x74, 0xa1, 0x3f, 0x09, 0x75, 0x0c, + 0xf4, 0xb5, 0xb8, 0x17, 0x69, 0x64, 0xf4, 0x08, 0xd4, 0x80, + ]; + secp256k1::SecretKey::try_from_slice(bytes.as_ref()) + .unwrap() + .try_to_sk() + .unwrap() + } + + /// An Ethereum keypair for tests + pub fn keypair_4() -> ::SecretKey { + let bytes = [ + 0x68, 0xab, 0xce, 0x64, 0x54, 0x07, 0x7e, 0xf5, 0x1a, 0xb4, 0x31, + 0x7a, 0xb8, 0x8b, 0x98, 0x30, 0x27, 0x11, 0x4e, 0x58, 0x69, 0xd6, + 0x45, 0x94, 0xdc, 0x90, 0x8d, 0x94, 0xee, 0x58, 0x46, 0x91, + ]; + secp256k1::SecretKey::try_from_slice(bytes.as_ref()) + .unwrap() + .try_to_sk() + .unwrap() + } + /// Generate an arbitrary [`super::SecretKey`]. pub fn arb_keypair() -> impl Strategy { any::<[u8; 32]>().prop_map(move |seed| { From 3950da6c8c5855ebecc3021b548c0a9e45840167 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 08:56:32 +0100 Subject: [PATCH 0629/1995] Remove duplicate tiny-keccak dep --- shared/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b35b85bd36..9edd4079e8 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,9 +92,8 @@ 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", features = ["secp256k1"]} tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} -tiny-keccak = {version = "2.0.2", features = ["keccak"]} thiserror = "1.0.30" -tiny-keccak = "2.0.2" +tiny-keccak = {version = "2.0.2", features = ["keccak"]} tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} From 7ca2f72901a9e0c15c6ae1284f4a09a81bf3318b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 09:05:44 +0100 Subject: [PATCH 0630/1995] Fix prepare and process proposal unit tests --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 +++++----- apps/src/lib/node/ledger/shell/process_proposal.rs | 6 +++--- proof_of_stake/src/types.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 8ee6550a94..8c97f91897 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -331,7 +331,7 @@ mod test_prepare_proposal { shell.storage.last_height = LAST_HEIGHT; let signed_vote_extension = { - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); // generate a valid signature @@ -363,7 +363,7 @@ mod test_prepare_proposal { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { @@ -422,7 +422,7 @@ mod test_prepare_proposal { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); let ethereum_event = EthereumEvent::TransfersToNamada { @@ -499,7 +499,7 @@ mod test_prepare_proposal { // artificially change the block height shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); let ethereum_event = EthereumEvent::TransfersToNamada { @@ -620,7 +620,7 @@ mod test_prepare_proposal { ); // test prepare proposal - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let validator_addr = wallet::defaults::validator_address(); let ethereum_event = EthereumEvent::TransfersToNamada { diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c3a1fbd14b..59e4cc91ce 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -463,7 +463,7 @@ mod test_process_proposal { const LAST_HEIGHT: BlockHeight = BlockHeight(2); let (mut shell, _, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { let validator_addr = wallet::defaults::validator_address(); let signed_vote_extension = { @@ -531,7 +531,7 @@ mod test_process_proposal { const LAST_HEIGHT: BlockHeight = BlockHeight(2); let (mut shell, _, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { let addr = wallet::defaults::validator_address(); let event = EthereumEvent::TransfersToNamada { @@ -579,7 +579,7 @@ mod test_process_proposal { const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); let (mut shell, _, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; - let (protocol_key, _) = wallet::defaults::validator_keys(); + let (protocol_key, _, _) = wallet::defaults::validator_keys(); let vote_extension_digest = { let addr = wallet::defaults::validator_address(); let event = EthereumEvent::TransfersToNamada { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index a559b67fa9..5a115e96eb 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -36,7 +36,7 @@ pub type Unbonds = pub type ValidatorSets
= Epoched, OffsetUnboundingLen>; /// Epoched total voting power. -pub type TotalVotingPowers = EpochedDelta; +pub type TotalVotingPowers = EpochedDelta; /// Epoched validator's eth key. pub type ValidatorEthKey = Epoched; /// Map from eth addresses back to native addresses. From e22a4e28ec060c9afc311fd4495526884eee074e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 09:06:02 +0100 Subject: [PATCH 0631/1995] Run make fmt --- proof_of_stake/src/types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 5a115e96eb..5e16c69bcf 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -36,7 +36,8 @@ pub type Unbonds = pub type ValidatorSets
= Epoched, OffsetUnboundingLen>; /// Epoched total voting power. -pub type TotalVotingPowers = EpochedDelta; +pub type TotalVotingPowers = + EpochedDelta; /// Epoched validator's eth key. pub type ValidatorEthKey = Epoched; /// Map from eth addresses back to native addresses. From 149c87c9d4d1f513643b7c318dd8a998de686d96 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 12:40:21 +0100 Subject: [PATCH 0632/1995] Implement BorshSchema for both Vext kinds --- .../types/vote_extensions/ethereum_events.rs | 26 ++++++++++++++++--- .../vote_extensions/validator_set_update.rs | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 4de69d98c4..9eaf838480 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -17,9 +17,7 @@ use crate::types::storage::BlockHeight; /// This struct will be created and signed over by each /// active validator, to be included as a vote extension at the end of a /// Tendermint PreCommit phase. -#[derive( - Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, -)] +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Vext { /// The block height for which this [`Vext`] was made. pub block_height: BlockHeight, @@ -49,6 +47,28 @@ impl Vext { } } +impl BorshSchema for Vext { + fn add_definitions_recursively( + definitions: &mut HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + BlockHeight::declaration(), + Address::declaration(), + Vec::::declaration(), + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "ethereum_events::Vext".into() + } +} + /// Aggregates an Ethereum event with the corresponding /// validators who saw this event. #[derive( diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b7de35613c..767444f30a 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -144,6 +144,28 @@ impl Vext { } } +impl BorshSchema for Vext { + fn add_definitions_recursively( + definitions: &mut HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + let fields = + borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ + VotingPowersMap::declaration(), + Address::declaration(), + BlockHeight::declaration(), + ]); + let definition = borsh::schema::Definition::Struct { fields }; + Self::add_definition(Self::declaration(), definition, definitions); + } + + fn declaration() -> borsh::schema::Declaration { + "validator_set_update::Vext".into() + } +} + /// Container type for both kinds of Ethereum bridge addresses: /// /// - An address derived from a hot key. From 80e9f6a0f0df3d678a4a5bd9380f97e555617866 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 12:42:44 +0100 Subject: [PATCH 0633/1995] Remove eth-fullnode feature flag from Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9f2e373be9..3d4b0611a9 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ clippy-abcipp: ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets \ --manifest-path ./apps/Cargo.toml \ --no-default-features \ - --features "std testing abcipp eth-fullnode" && \ + --features "std testing abcipp" && \ $(cargo) +$(nightly) clippy --all-targets \ --manifest-path ./proof_of_stake/Cargo.toml \ --features "testing" && \ From aaad19fee120da35928ae6597de3e9db51e664dd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 09:54:40 +0100 Subject: [PATCH 0634/1995] Remove duped EthAddress type from namada shared --- shared/src/ledger/pos/mod.rs | 2 +- shared/src/types/ethereum_events.rs | 7 +++++++ shared/src/types/key/common.rs | 2 +- shared/src/types/key/secp256k1.rs | 5 +---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 7bfdd3cee6..7253240a72 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -17,8 +17,8 @@ pub use vp::PosVP; use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{self, Address, InternalAddress}; +use crate::types::ethereum_events::EthAddress; use crate::types::key::common; -use crate::types::key::secp256k1::EthAddress; use crate::types::storage::Epoch; use crate::types::token; diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 869b4a4f14..5e4d286f88 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -80,6 +80,13 @@ impl FromStr for EthAddress { } } +impl From for EthAddress { + #[inline] + fn from(addr: namada_proof_of_stake::types::EthAddress) -> EthAddress { + EthAddress(addr.0) + } +} + /// A Keccak hash #[derive( Clone, diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 7e9b468e50..59196e1ff0 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -10,12 +10,12 @@ use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::secp256k1::EthAddress; use super::{ ed25519, secp256k1, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; +use crate::types::ethereum_events::EthAddress; /// Public key #[derive( diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index d3b037c0e3..9ff662aa36 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -17,15 +17,12 @@ use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; +use crate::types::ethereum_events::EthAddress; /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PublicKey(pub libsecp256k1::PublicKey); -/// Eth address derived from secp256k1 key -#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct EthAddress(pub [u8; 20]); - impl super::PublicKey for PublicKey { const TYPE: SchemeType = SigScheme::TYPE; From 483daf00c6429ab86c80d7aadc1ec6a99cf21738 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 09:58:35 +0100 Subject: [PATCH 0635/1995] Fix docs --- apps/src/lib/wallet/pre_genesis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 72f719d1e4..f28be00d1b 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -139,8 +139,8 @@ impl ValidatorWallet { } } - /// Generate a new [`Validator`] with required pre-genesis keys. Will prompt - /// for password when `!unsafe_dont_encrypt`. + /// Generate a new [`ValidatorWallet`] with required pre-genesis keys. Will + /// prompt for password when `!unsafe_dont_encrypt`. 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); From 166e48350ec5d5338ef6114d8d5e2beebcc471a9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 10:27:58 +0100 Subject: [PATCH 0636/1995] Return an Ethereum bridge addr from a Namada validator addr --- apps/src/lib/node/ledger/shell/queries.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index f03a37a5b1..92248a1d6e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -9,6 +9,7 @@ use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; use namada::types::address::Address; +use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, Key, PrefixValue}; @@ -332,6 +333,13 @@ pub(crate) trait QueriesExt { /// Retrieves the [`BlockHeight`] that is currently being decided. fn get_current_decision_height(&self) -> BlockHeight; + + /// For a given Namada validator, return its corresponding Ethereum bridge + /// address. + fn get_ethbridge_from_namada_addr( + &self, + validator: &Address, + ) -> Option; } impl QueriesExt for Storage @@ -541,6 +549,15 @@ where fn get_current_decision_height(&self) -> BlockHeight { self.last_height + 1 } + + fn get_ethbridge_from_namada_addr( + &self, + validator: &Address, + ) -> Option { + self.read_validator_eth_hot_key(validator) + .as_ref() + .and_then(|pk| pk.try_into().ok()) + } } /// This enum is used as a parameter to From 8fffeca14f1cf07bad355ad9dd4cb62c391534b4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 10:54:17 +0100 Subject: [PATCH 0637/1995] Add voting powers map to valset upd vexts --- apps/src/lib/node/ledger/shell/queries.rs | 18 ++++++++ .../lib/node/ledger/shell/vote_extensions.rs | 42 +++++++++++++++---- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 92248a1d6e..80442a6588 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -340,6 +340,13 @@ pub(crate) trait QueriesExt { &self, validator: &Address, ) -> Option; + + /// For a given Namada validator, return its corresponding Ethereum + /// governance address. + fn get_ethgov_from_namada_addr( + &self, + validator: &Address, + ) -> Option; } impl QueriesExt for Storage @@ -550,6 +557,7 @@ where self.last_height + 1 } + #[inline] fn get_ethbridge_from_namada_addr( &self, validator: &Address, @@ -558,6 +566,16 @@ where .as_ref() .and_then(|pk| pk.try_into().ok()) } + + #[inline] + fn get_ethgov_from_namada_addr( + &self, + validator: &Address, + ) -> Option { + self.read_validator_eth_cold_key(validator) + .as_ref() + .and_then(|pk| pk.try_into().ok()) + } } /// This enum is used as a parameter to diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 041b6a3f9c..73242f9e32 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -11,6 +11,7 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; + use namada::types::vote_extensions::validator_set_update::EthAddrBook; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, @@ -109,28 +110,51 @@ mod extend_votes { .can_send_validator_set_update(SendValsetUpd::Now) .then(|| { let next_epoch = self.storage.get_current_epoch().0.next(); - let _validator_set = - self.storage.get_active_validators(Some(next_epoch)); + let voting_powers = self + .storage + .get_active_validators(Some(next_epoch)) + .into_iter() + .map(|validator| { + let hot_key_addr = self + .storage + .get_ethbridge_from_namada_addr( + &validator.address, + ) + .expect( + "All Namada validators should have an \ + Ethereum bridge key", + ); + let cold_key_addr = self + .storage + .get_ethgov_from_namada_addr(&validator.address) + .expect( + "All Namada validators should have an \ + Ethereum governance key", + ); + let eth_addr_book = EthAddrBook { + hot_key_addr, + cold_key_addr, + }; + (eth_addr_book, validator.voting_power) + }) + .collect(); let ext = validator_set_update::Vext { validator_addr, - // TODO: we need a way to map ethereum addresses to - // namada validator addresses - voting_powers: std::collections::HashMap::new(), + voting_powers, block_height: self .storage .get_current_decision_height(), }; - let protocol_key = match &self.mode { + let eth_key = match &self.mode { ShellMode::Validator { data, .. } => { - &data.keys.protocol_keypair + &data.keys.eth_bridge_keypair } _ => unreachable!("{VALIDATOR_EXPECT_MSG}"), }; - // TODO: sign validator set update with secp key instead - ext.sign(protocol_key) + ext.sign(eth_key) }) } From 48e1fe97a33eff02ea56cebb28c672221e0019ef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 11:24:52 +0100 Subject: [PATCH 0638/1995] Add missing read_eth_key_addresses() to PosReadOnly --- proof_of_stake/src/lib.rs | 4 ++++ shared/src/ledger/pos/storage.rs | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 8c7a39c628..51d032e479 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -655,6 +655,10 @@ pub trait PosBase { key: &Self::Address, ) -> Option; + /// Read PoS map from eth address derived from cold or hot keys to native + /// addresses + fn read_eth_key_addresses(&self) -> EthKeyAddresses; + /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); /// Write PoS validator's raw hash its address. diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 3d613926ce..0c3599cd2a 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -536,6 +536,11 @@ where value.map(|value| decode(value).unwrap()) } + fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { + let (value, _gas) = self.read(ð_key_addresses_key()).unwrap(); + decode(value.unwrap()).unwrap() + } + fn write_pos_params(&mut self, params: &PosParams) { self.write(¶ms_key(), encode(params)).unwrap(); } From ca8aef4da633ea894e13d7211c3b0f20cbcf7aa5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 13:28:07 +0100 Subject: [PATCH 0639/1995] WIP: Verify voting powers in valset upd vext --- .../lib/node/ledger/shell/vote_extensions.rs | 10 ++++ .../vote_extensions/validator_set_update.rs | 51 +++++++++++++++++-- shared/src/types/ethereum_events.rs | 7 +++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 73242f9e32..566d520da1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -47,6 +47,16 @@ mod extend_votes { PubKeyNotInStorage, #[error("The vote extension's signature is invalid.")] VerifySigFailed, + #[error( + "Validator is missing from an expected field in the vote \ + extension." + )] + ValidatorMissingFromExtension, + #[error( + "Found value for a field in the vote extension diverging from the \ + equivalent field in storage." + )] + DivergesFromStorage, } impl Shell diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index f56ef4a341..b22897703c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; +use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::types::storage::BlockHeight; @@ -69,11 +70,51 @@ where tracing::error!("Dropping vote extension issued at genesis"); return Err(VoteExtensionError::IssuedAtGenesis); } - // get the public key associated with this validator - let validator = &ext.data.validator_addr; + // verify if the voting powers in storage match the voting powers in the + // vote extensions + let eth_to_namada_map = self.storage.read_eth_key_addresses(); let last_height_epoch = self.storage.get_epoch(last_height).expect( "The epoch of the last block height should always be known", ); + let next_epoch = last_height_epoch.next(); + for (eth_addr, namada_addr) in eth_to_namada_map.iter() { + let &ext_power = match ext.data.voting_powers.get(eth_addr) { + Some(voting_power) => voting_power, + _ => { + tracing::error!( + eth_addr, + "Could not find expected Ethereum address in valset \ + upd vote extension", + ); + return Err( + VoteExtensionError::ValidatorMissingFromExtension, + ); + } + }; + let (namada_power, _) = self + .storage + .get_validator_from_address(namada_addr, Some(next_epoch)) + .map_err(|err| { + tracing::error!( + ?err, + validator = %namada_addr, + "Could not get voting power from Storage for some validator, \ + while validating valset upd vote extension" + ); + VoteExtensionError::PubKeyNotInStorage + })?; + if namada_power != ext_power { + tracing::error!( + validator = %namada_addr, + expected = ?namada_power, + got = ?ext_power, + "Found unexpected voting power value in valset upd vote extension", + ); + return Err(VoteExtensionError::DivergesFromStorage); + } + } + // get the public key associated with this validator + let validator = &ext.data.validator_addr; let (voting_power, pk) = self .storage .get_validator_from_address(validator, Some(last_height_epoch)) @@ -81,7 +122,8 @@ where tracing::error!( ?err, %validator, - "Could not get public key from Storage for some validator, while validating validator set update vote extension" + "Could not get public key from Storage for some validator, \ + while validating valset upd vote extension" ); VoteExtensionError::PubKeyNotInStorage })?; @@ -91,7 +133,8 @@ where tracing::error!( ?err, %validator, - "Failed to verify the signature of a validator set update vote extension issued by some validator" + "Failed to verify the signature of a valset upd vote \ + extension issued by some validator" ); VoteExtensionError::VerifySigFailed }) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 5e4d286f88..77e1713b42 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -1,5 +1,6 @@ //! Types representing data intended for Anoma via Ethereum events +use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -68,6 +69,12 @@ impl EthAddress { } } +impl Display for EthAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_canonical()) + } +} + impl FromStr for EthAddress { type Err = eyre::Error; From ab24237ca7bd4d0aff321b63cd655c5d5d095000 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 14:16:50 +0100 Subject: [PATCH 0640/1995] Verify voting powers in valset upd vext --- apps/src/lib/node/ledger/shell/queries.rs | 38 +++++++++++++++++++ .../lib/node/ledger/shell/vote_extensions.rs | 28 ++------------ .../vote_extensions/validator_set_update.rs | 24 +++--------- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 80442a6588..75938e6ee1 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -14,6 +14,7 @@ use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, Key, PrefixValue}; use namada::types::token::{self, Amount}; +use namada::types::vote_extensions::validator_set_update::EthAddrBook; use tendermint_proto::crypto::{ProofOp, ProofOps}; use tendermint_proto::google::protobuf; use tendermint_proto::types::EvidenceParams; @@ -347,6 +348,13 @@ pub(crate) trait QueriesExt { &self, validator: &Address, ) -> Option; + + /// Extension of [`Self::get_active_validators`], which additionally returns + /// all Ethereum addresses of some validator. + fn get_active_eth_addresses<'db>( + &'db self, + epoch: Option, + ) -> Box + 'db>; } impl QueriesExt for Storage @@ -576,6 +584,36 @@ where .as_ref() .and_then(|pk| pk.try_into().ok()) } + + #[inline] + fn get_active_eth_addresses<'db>( + &'db self, + epoch: Option, + ) -> Box + 'db> + { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + Box::new(self.get_active_validators(Some(epoch)).into_iter().map( + |validator| { + let hot_key_addr = self + .get_ethbridge_from_namada_addr(&validator.address) + .expect( + "All Namada validators should have an Ethereum bridge \ + key", + ); + let cold_key_addr = self + .get_ethgov_from_namada_addr(&validator.address) + .expect( + "All Namada validators should have an Ethereum \ + governance key", + ); + let eth_addr_book = EthAddrBook { + hot_key_addr, + cold_key_addr, + }; + (eth_addr_book, validator.address, validator.voting_power) + }, + )) + } } /// This enum is used as a parameter to diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 566d520da1..6007f0db81 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -11,7 +11,6 @@ mod extend_votes { use borsh::BorshDeserialize; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::vote_extensions::validator_set_update::EthAddrBook; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, @@ -122,30 +121,9 @@ mod extend_votes { let next_epoch = self.storage.get_current_epoch().0.next(); let voting_powers = self .storage - .get_active_validators(Some(next_epoch)) - .into_iter() - .map(|validator| { - let hot_key_addr = self - .storage - .get_ethbridge_from_namada_addr( - &validator.address, - ) - .expect( - "All Namada validators should have an \ - Ethereum bridge key", - ); - let cold_key_addr = self - .storage - .get_ethgov_from_namada_addr(&validator.address) - .expect( - "All Namada validators should have an \ - Ethereum governance key", - ); - let eth_addr_book = EthAddrBook { - hot_key_addr, - cold_key_addr, - }; - (eth_addr_book, validator.voting_power) + .get_active_eth_addresses(Some(next_epoch)) + .map(|(eth_addr_book, _, voting_power)| { + (eth_addr_book, voting_power) }) .collect(); diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index b22897703c..32344c5c75 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; -use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::types::storage::BlockHeight; @@ -72,18 +71,19 @@ where } // verify if the voting powers in storage match the voting powers in the // vote extensions - let eth_to_namada_map = self.storage.read_eth_key_addresses(); let last_height_epoch = self.storage.get_epoch(last_height).expect( "The epoch of the last block height should always be known", ); let next_epoch = last_height_epoch.next(); - for (eth_addr, namada_addr) in eth_to_namada_map.iter() { - let &ext_power = match ext.data.voting_powers.get(eth_addr) { + for (eth_addr_book, namada_addr, namada_power) in + self.storage.get_active_eth_addresses(Some(next_epoch)) + { + let &ext_power = match ext.data.voting_powers.get(ð_addr_book) { Some(voting_power) => voting_power, _ => { tracing::error!( - eth_addr, - "Could not find expected Ethereum address in valset \ + ?eth_addr_book, + "Could not find expected Ethereum addresses in valset \ upd vote extension", ); return Err( @@ -91,18 +91,6 @@ where ); } }; - let (namada_power, _) = self - .storage - .get_validator_from_address(namada_addr, Some(next_epoch)) - .map_err(|err| { - tracing::error!( - ?err, - validator = %namada_addr, - "Could not get voting power from Storage for some validator, \ - while validating valset upd vote extension" - ); - VoteExtensionError::PubKeyNotInStorage - })?; if namada_power != ext_power { tracing::error!( validator = %namada_addr, From f22f19db1dd2ef00d6413591cf7e36561de807a2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 14:54:40 +0100 Subject: [PATCH 0641/1995] Verify valset upd vext with secp key --- .../shell/vote_extensions/validator_set_update.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index 32344c5c75..f82992ec13 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; +use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::types::storage::BlockHeight; @@ -42,12 +43,6 @@ where /// This method behaves exactly like [`Self::validate_valset_upd_vext`], /// with the added bonus of returning the vote extension back, if it /// is valid. - // TODO: - // - verify if the voting powers in the vote extension are the same - // as the ones in storage. we can't do this yet, because we need to map - // ethereum addresses to namada validator addresses - // - // - verify signatures with a secp key, instead of an ed25519 key pub fn validate_valset_upd_vext_and_get_it_back( &self, ext: validator_set_update::SignedVext, @@ -103,7 +98,7 @@ where } // get the public key associated with this validator let validator = &ext.data.validator_addr; - let (voting_power, pk) = self + let (voting_power, _) = self .storage .get_validator_from_address(validator, Some(last_height_epoch)) .map_err(|err| { @@ -115,6 +110,10 @@ where ); VoteExtensionError::PubKeyNotInStorage })?; + let pk = self + .storage + .read_validator_eth_hot_key(validator) + .expect("We should have this hot key in storage"); // verify the signature of the vote extension ext.verify(&pk) .map_err(|err| { From 5e5d88f1227f8ca14595c494bb09c09a4dbf2a80 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 15:34:50 +0100 Subject: [PATCH 0642/1995] Remove unused eth key addresses pos methods --- proof_of_stake/src/lib.rs | 95 ++------------------------------ proof_of_stake/src/types.rs | 2 - shared/src/ledger/pos/storage.rs | 29 ---------- shared/src/ledger/pos/vp.rs | 13 +---- vm_env/src/proof_of_stake.rs | 11 ---- 5 files changed, 7 insertions(+), 143 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 51d032e479..cb45560e93 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -33,11 +33,11 @@ use epoched::{ use parameters::PosParams; use thiserror::Error; use types::{ - ActiveValidator, Bonds, Epoch, EthAddress, EthKeyAddresses, - GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, TryRefTo, - Unbond, Unbonds, ValidatorConsensusKeys, ValidatorEthKey, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + ActiveValidator, Bonds, Epoch, EthAddress, GenesisValidator, Slash, + SlashType, Slashes, TotalVotingPowers, TryRefTo, Unbond, Unbonds, + ValidatorConsensusKeys, ValidatorEthKey, ValidatorSet, ValidatorSetUpdate, + ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, + ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; use crate::btree_set::BTreeSetShims; @@ -159,20 +159,6 @@ pub trait PosReadOnly { &self, key: &Self::Address, ) -> Option; - - /// Read PoS map from eth address derived from cold or hot keys to native - /// addresses - fn read_eth_key_addresses(&self) -> EthKeyAddresses; - - /// Try to find a native address associated with the given Ethereum address - /// derived from an Ethereum cold or hot key - fn find_address_from_eth_key_address( - &self, - eth_addr: &EthAddress, - ) -> Option { - let addresses = self.read_eth_key_addresses(); - addresses.get(eth_addr).cloned() - } } /// PoS system trait to be implemented in integration that can read and write @@ -245,10 +231,6 @@ pub trait PosActions: PosReadOnly { value: ValidatorEthKey, ); - /// Write PoS map from eth address derived from cold or hot keys to native - /// addresses - fn write_eth_key_addresses(&self, value: EthKeyAddresses); - /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond(&mut self, key: &BondId); /// Delete an emptied PoS unbond (unbonded tokens from validator self-bond @@ -296,9 +278,6 @@ pub trait PosActions: PosReadOnly { consensus_key, eth_cold_key, eth_hot_key, - eth_cold_key_addr, - eth_hot_key_addr, - state, total_deltas, voting_power, @@ -311,19 +290,6 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, )?; - let mut eth_addresses_map = self.read_eth_key_addresses(); - if eth_addresses_map - .insert(eth_cold_key_addr, address.clone()) - .is_some() - { - return Err(BecomeValidatorError::DupedEthKeyFound); - } - if eth_addresses_map - .insert(eth_hot_key_addr, address.clone()) - .is_some() - { - return Err(BecomeValidatorError::DupedEthKeyFound); - } self.write_validator_staking_reward_address( address, staking_reward_address.clone(), @@ -331,7 +297,6 @@ pub trait PosActions: PosReadOnly { self.write_validator_consensus_key(address, consensus_key); self.write_validator_eth_cold_key(address, eth_cold_key); self.write_validator_eth_hot_key(address, eth_hot_key); - self.write_eth_key_addresses(eth_addresses_map); self.write_validator_state(address, state); self.write_validator_set(validator_set); self.write_validator_address_raw_hash(address); @@ -655,10 +620,6 @@ pub trait PosBase { key: &Self::Address, ) -> Option; - /// Read PoS map from eth address derived from cold or hot keys to native - /// addresses - fn read_eth_key_addresses(&self) -> EthKeyAddresses; - /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); /// Write PoS validator's raw hash its address. @@ -723,12 +684,6 @@ pub trait PosBase { address: &Self::Address, value: &ValidatorEthKey, ); - /// Write PoS map from eth address derived from cold or hot keys to native - /// addresses - fn write_eth_key_addresses( - &mut self, - value: &EthKeyAddresses, - ); /// Initialize staking reward account with the given public key. fn init_staking_reward_account( &mut self, @@ -782,8 +737,6 @@ pub trait PosBase { total_bonded_balance, } = init_genesis(params, validators, current_epoch)?; - let mut eth_addresses_map = HashMap::default(); - for res in validators { let GenesisValidatorData { ref address, @@ -796,8 +749,6 @@ pub trait PosBase { bond: (bond_id, bond), eth_cold_key, eth_hot_key, - eth_cold_key_addr, - eth_hot_key_addr, } = res?; self.write_validator_address_raw_hash(address); self.write_validator_staking_reward_address( @@ -815,20 +766,7 @@ pub trait PosBase { &staking_reward_address, &staking_reward_key, ); - if eth_addresses_map - .insert(eth_cold_key_addr, address.clone()) - .is_some() - { - return Err(GenesisError::DupedEthKeyFound); - } - if eth_addresses_map - .insert(eth_hot_key_addr, address.clone()) - .is_some() - { - return Err(GenesisError::DupedEthKeyFound); - } } - self.write_eth_key_addresses(ð_addresses_map); self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); // Credit the bonded tokens to the PoS account @@ -1024,8 +962,6 @@ pub enum GenesisError { VotingPowerOverflow(TryFromIntError), #[error("Ethereum address can only be of secp kind")] SecpKeyConversion, - #[error("Duplicate Ethereum key found")] - DupedEthKeyFound, } #[allow(missing_docs)] @@ -1040,8 +976,6 @@ pub enum BecomeValidatorError { StakingRewardAddressEqValidatorAddress(Address), #[error("Ethereum address can only be of secp kind")] SecpKeyConversion, - #[error("Duplicate Ethereum key found")] - DupedEthKeyFound, } #[allow(missing_docs)] @@ -1195,8 +1129,6 @@ where bond: (BondId
, Bonds), eth_cold_key: ValidatorEthKey, eth_hot_key: ValidatorEthKey, - eth_cold_key_addr: EthAddress, - eth_hot_key_addr: EthAddress, } /// A function that returns genesis data created from the initial validator set. @@ -1305,11 +1237,6 @@ where eth_cold_key, eth_hot_key, }| { - let convert_key_to_addr = |k: &'a PK| { - k.try_ref_to().map_err(|_| GenesisError::SecpKeyConversion) - }; - let eth_cold_key_addr = convert_key_to_addr(eth_cold_key)?; - let eth_hot_key_addr = convert_key_to_addr(eth_hot_key)?; let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); let eth_cold_key = @@ -1347,8 +1274,6 @@ where bond: (bond_id, bond), eth_cold_key, eth_hot_key, - eth_cold_key_addr, - eth_hot_key_addr, }) }, ); @@ -1461,8 +1386,6 @@ where consensus_key: ValidatorConsensusKeys, eth_cold_key: ValidatorEthKey, eth_hot_key: ValidatorEthKey, - eth_cold_key_addr: EthAddress, - eth_hot_key_addr: EthAddress, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1502,12 +1425,6 @@ where + BorshSerialize + BorshSchema, { - let convert_key_to_addr = |k: &'a PK| { - k.try_ref_to() - .map_err(|_| BecomeValidatorError::SecpKeyConversion) - }; - let eth_cold_key_addr = convert_key_to_addr(eth_cold_key)?; - let eth_hot_key_addr = convert_key_to_addr(eth_hot_key)?; let consensus_key = Epoched::init(consensus_key.clone(), current_epoch, params); let eth_cold_key = @@ -1556,8 +1473,6 @@ where voting_power, eth_cold_key, eth_hot_key, - eth_cold_key_addr, - eth_hot_key_addr, }) } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 5e16c69bcf..1d9a08e05a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -40,8 +40,6 @@ pub type TotalVotingPowers = EpochedDelta; /// Epoched validator's eth key. pub type ValidatorEthKey = Epoched; -/// Map from eth addresses back to native addresses. -pub type EthKeyAddresses
= HashMap; /// Eth address derived from secp256k1 key #[derive( diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 0c3599cd2a..1443c27811 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -24,7 +24,6 @@ const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_ETH_COLD_KEY_STORAGE_KEY: &str = "eth_cold_key"; const VALIDATOR_ETH_HOT_KEY_STORAGE_KEY: &str = "eth_hot_key"; -const ETH_KEY_ADDRESSES_STORAGE_KEY: &str = "eth_key_addresses"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; @@ -419,22 +418,6 @@ pub fn get_validator_address_from_bond(key: &Key) -> Option
{ } } -/// Storage key for look-up from validator's eth key addresses to native -/// address. -pub fn eth_key_addresses_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(Ð_KEY_ADDRESSES_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validators' eth key addresses? -pub fn is_eth_key_addresses_key(key: &Key) -> bool { - matches!(&key.segments[..], - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(suffix)] - if addr == &ADDRESS - && suffix == ETH_KEY_ADDRESSES_STORAGE_KEY) -} - impl PosBase for Storage where D: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -536,11 +519,6 @@ where value.map(|value| decode(value).unwrap()) } - fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { - let (value, _gas) = self.read(ð_key_addresses_key()).unwrap(); - decode(value.unwrap()).unwrap() - } - fn write_pos_params(&mut self, params: &PosParams) { self.write(¶ms_key(), encode(params)).unwrap(); } @@ -638,13 +616,6 @@ where .unwrap(); } - fn write_eth_key_addresses( - &mut self, - value: &types::EthKeyAddresses, - ) { - self.write(ð_key_addresses_key(), encode(value)).unwrap(); - } - fn init_staking_reward_account( &mut self, address: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 66a348fbf9..7d2b7680f1 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -16,8 +16,8 @@ use namada_proof_of_stake::{validation, PosReadOnly}; use thiserror::Error; use super::{ - bond_key, eth_key_addresses_key, is_bond_key, is_params_key, - is_total_voting_power_key, is_unbond_key, is_validator_set_key, + bond_key, is_bond_key, is_params_key, is_total_voting_power_key, + is_unbond_key, is_validator_set_key, is_validator_staking_reward_address_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_key, validator_consensus_key_key, @@ -433,15 +433,6 @@ where let value = self.ctx.read_pre(&validator_eth_hot_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } - - fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { - let value = self - .ctx - .read_pre(ð_key_addresses_key()) - .unwrap() - .unwrap(); - decode(value).unwrap() - } } impl From for Error { diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 835d7d6ab1..53e2ab9470 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -187,10 +187,6 @@ impl namada_proof_of_stake::PosReadOnly for PoS { ) -> Option { tx::read(validator_eth_hot_key_key(key).to_string()) } - - fn read_eth_key_addresses(&self) -> types::EthKeyAddresses { - tx::read(eth_key_addresses_key().to_string()).unwrap() - } } impl namada_proof_of_stake::PosActions for PoS { @@ -298,11 +294,4 @@ impl namada_proof_of_stake::PosActions for PoS { ) { tx::write(validator_eth_hot_key_key(address).to_string(), &value) } - - fn write_eth_key_addresses( - &self, - value: types::EthKeyAddresses, - ) { - tx::write(eth_key_addresses_key().to_string(), &value) - } } From 930891721f713b7c0cf7a6319777a143b7e41bc9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 15:44:52 +0100 Subject: [PATCH 0643/1995] Ignore test_secp_key_belongs_to_active_validator() unit test --- .../node/ledger/shell/vote_extensions/validator_set_update.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index f82992ec13..d45e5da841 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -464,7 +464,9 @@ mod test_vote_extensions { /// Test if a [`validator_set_update::Vext`] is signed with a secp key /// that belongs to an active validator of some previous epoch #[test] + #[ignore] fn test_secp_key_belongs_to_active_validator() { - // TODO + // TODO: we need to prove ownership of validator keys + // https://github.com/anoma/namada/issues/106 } } From e7c09940931a12923b9e21056513128c0388af05 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 15:54:10 +0100 Subject: [PATCH 0644/1995] Add get_eth_bridge_keypair() --- apps/src/lib/node/ledger/shell/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index fde27d0288..9c9e65f279 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -244,9 +244,9 @@ impl ShellMode { } } - /// Get the protocol keypair for this validator + /// Get the protocol keypair for this validator. pub fn get_protocol_key(&self) -> Option<&common::SecretKey> { - match &self { + match self { ShellMode::Validator { data: ValidatorData { @@ -261,6 +261,24 @@ impl ShellMode { _ => None, } } + + /// Get the Ethereum bridge keypair for this validator. + pub fn get_eth_bridge_keypair(&self) -> Option<&common::SecretKey> { + match self { + ShellMode::Validator { + data: + ValidatorData { + keys: + ValidatorKeys { + eth_bridge_keypair, .. + }, + .. + }, + .. + } => Some(eth_bridge_keypair), + _ => None, + } + } } #[derive(Clone, Debug)] From cb0e980dd6b62f631a5e65904be1db2850848591 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 16:03:37 +0100 Subject: [PATCH 0645/1995] WIP: Fix unit tests --- apps/src/lib/node/ledger/shell/mod.rs | 18 +++-- .../vote_extensions/validator_set_update.rs | 69 ++++++++++++++----- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9c9e65f279..a604ee9491 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -793,14 +793,20 @@ mod test_utils { pub(super) fn invalidate_signature( sig: common::Signature, ) -> common::Signature { - let mut sig_bytes = match sig { + match sig { common::Signature::Ed25519(ed25519::Signature(ref sig)) => { - sig.to_bytes() + let mut sig_bytes = sig.to_bytes(); + sig_bytes[0] = sig_bytes[0].wrapping_add(1); + common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - _ => unreachable!(), - }; - sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) + common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + let mut sig_bytes = sig.to_bytes(); + sig_bytes[0] = sig_bytes[0].wrapping_add(1); + common::Signature::Secp256k1(secp256k1::Signature( + sig_bytes.into(), + )) + } + } } /// A wrapper around the shell that implements diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index d45e5da841..ba69465939 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -261,9 +261,6 @@ mod test_vote_extensions { /// Test if a [`validator_set_update::Vext`] that incorrectly labels what /// block height it was included on in a vote extension is rejected - // TODO: - // - sign with secp key - // - add validator voting powers from storage #[test] fn test_reject_incorrect_block_height() { let (shell, _, _) = test_utils::setup(); @@ -271,6 +268,8 @@ mod test_vote_extensions { shell.mode.get_validator_address().unwrap().clone(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + let eth_bridge_key = + shell.mode.get_eth_bridge_keypair().expect("Test failed"); let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), @@ -278,17 +277,24 @@ mod test_vote_extensions { ) .sign(protocol_key); + let voting_powers = { + let next_epoch = shell.storage.get_current_epoch().0.next(); + shell + .storage + .get_active_eth_addresses(Some(next_epoch)) + .map(|(eth_addr_book, _, voting_power)| { + (eth_addr_book, voting_power) + }) + .collect() + }; let validator_set_update = Some( validator_set_update::Vext { - // TODO: get voting powers from storage, associated with eth - // addrs - voting_powers: std::collections::HashMap::new(), + voting_powers, validator_addr, // invalid height block_height: shell.storage.get_current_decision_height() + 1, } - // TODO: sign with secp key - .sign(protocol_key), + .sign(eth_bridge_key), ); let req = request::VerifyVoteExtension { @@ -321,11 +327,19 @@ mod test_vote_extensions { validator_addr.clone(), ) .sign(&protocol_key); + let voting_powers = { + let next_epoch = shell.storage.get_current_epoch().0.next(); + shell + .storage + .get_active_eth_addresses(Some(next_epoch)) + .map(|(eth_addr_book, _, voting_power)| { + (eth_addr_book, voting_power) + }) + .collect() + }; let validator_set_update = Some( validator_set_update::Vext { - // TODO: get voting powers from storage, associated with eth - // addrs - voting_powers: std::collections::HashMap::new(), + voting_powers, block_height: shell.storage.get_current_decision_height(), validator_addr, } @@ -361,10 +375,18 @@ mod test_vote_extensions { .expect("Test failed") .clone(); let signed_height = shell.storage.get_current_decision_height(); + let voting_powers = { + let next_epoch = shell.storage.get_current_epoch().0.next(); + shell + .storage + .get_active_eth_addresses(Some(next_epoch)) + .map(|(eth_addr_book, _, voting_power)| { + (eth_addr_book, voting_power) + }) + .collect() + }; let vote_ext = validator_set_update::Vext { - // TODO: get voting powers from storage, associated with eth - // addrs - voting_powers: std::collections::HashMap::new(), + voting_powers, block_height: signed_height, validator_addr, } @@ -425,6 +447,8 @@ mod test_vote_extensions { shell.mode.get_validator_address().unwrap().clone(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); + let eth_bridge_key = + shell.mode.get_eth_bridge_keypair().expect("Test failed"); let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), @@ -433,15 +457,22 @@ mod test_vote_extensions { .sign(protocol_key); let validator_set_update = { + let voting_powers = { + let next_epoch = shell.storage.get_current_epoch().0.next(); + shell + .storage + .get_active_eth_addresses(Some(next_epoch)) + .map(|(eth_addr_book, _, voting_power)| { + (eth_addr_book, voting_power) + }) + .collect() + }; let mut ext = validator_set_update::Vext { - // TODO: get voting powers from storage, associated with eth - // addrs - voting_powers: std::collections::HashMap::new(), + voting_powers, block_height: shell.storage.get_current_decision_height(), validator_addr, } - // TODO: sign with secp key - .sign(protocol_key); + .sign(eth_bridge_key); ext.sig = test_utils::invalidate_signature(ext.sig); Some(ext) }; From 0d5c02907f833963d7d33bcfa8b19ca2a8e9596f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 16:38:18 +0100 Subject: [PATCH 0646/1995] Add method to recover secp sig from a byte array --- shared/src/types/key/secp256k1.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9ff662aa36..f9eebcd228 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -405,6 +405,21 @@ impl PartialOrd for Signature { } } +impl TryFrom<&[u8; 64]> for Signature { + type Error = ParseSignatureError; + + fn try_from(sig: &[u8; 64]) -> Result { + libsecp256k1::Signature::parse_standard(sig) + .map(Self) + .map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + }) + } +} + /// An implementation of the Secp256k1 signature scheme #[derive( Debug, From 66c89db9f5a65e79de06c0abd74706c727c5050e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 16:38:58 +0100 Subject: [PATCH 0647/1995] Add new test utils --- apps/src/lib/node/ledger/shell/mod.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a604ee9491..dccd77ac55 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -781,7 +781,13 @@ mod test_utils { } /// Generate a random public/private keypair + #[inline] pub(super) fn gen_keypair() -> common::SecretKey { + gen_ed25519_keypair() + } + + /// Generate a random ed25519 public/private keypair + pub(super) fn gen_ed25519_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; use rand::thread_rng; @@ -789,6 +795,17 @@ mod test_utils { ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap() } + /// Generate a random secp256k1 public/private keypair + pub(super) fn gen_secp256k1_keypair() -> common::SecretKey { + use rand::prelude::ThreadRng; + use rand::thread_rng; + + let mut rng: ThreadRng = thread_rng(); + secp256k1::SigScheme::generate(&mut rng) + .try_to_sk() + .unwrap() + } + /// Invalidate a valid signature `sig`. pub(super) fn invalidate_signature( sig: common::Signature, @@ -800,11 +817,9 @@ mod test_utils { common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { - let mut sig_bytes = sig.to_bytes(); + let mut sig_bytes = sig.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1(secp256k1::Signature( - sig_bytes.into(), - )) + common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) } } } From e0f88e0d2eac84d98ea34080e779d0041ea81425 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 15 Sep 2022 16:39:13 +0100 Subject: [PATCH 0648/1995] Fix clippy checks --- .../shell/vote_extensions/validator_set_update.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index ba69465939..e479b52f3f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -317,10 +317,10 @@ mod test_vote_extensions { #[test] fn test_valset_upd_must_be_signed_by_validator() { let (shell, _, _) = test_utils::setup(); - let (protocol_key, validator_addr) = { + let (eth_bridge_key, protocol_key, validator_addr) = { let bertha_key = wallet::defaults::bertha_keypair(); let bertha_addr = wallet::defaults::bertha_address(); - (bertha_key, bertha_addr) + (test_utils::gen_secp256k1_keypair(), bertha_key, bertha_addr) }; let ethereum_events = ethereum_events::Vext::empty( shell.storage.get_current_decision_height(), @@ -343,7 +343,7 @@ mod test_vote_extensions { block_height: shell.storage.get_current_decision_height(), validator_addr, } - .sign(&protocol_key), + .sign(ð_bridge_key), ); let req = request::VerifyVoteExtension { vote_extension: VoteExtension { @@ -369,6 +369,11 @@ mod test_vote_extensions { let (mut shell, _, _) = test_utils::setup(); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); + let eth_bridge_key = shell + .mode + .get_eth_bridge_keypair() + .expect("Test failed") + .clone(); let validator_addr = shell .mode .get_validator_address() @@ -390,7 +395,7 @@ mod test_vote_extensions { block_height: signed_height, validator_addr, } - .sign(&protocol_key); + .sign(ð_bridge_key); // validators from the current epoch sign over validator // set of the next epoch From 86a4d5fb1728a09ca48ec0dce1960356e713c2c6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 16 Sep 2022 09:18:34 +0100 Subject: [PATCH 0649/1995] Fixed signature of PoS eth key read methods --- proof_of_stake/src/lib.rs | 8 ++++---- shared/src/ledger/pos/storage.rs | 4 ++-- shared/src/ledger/pos/vp.rs | 4 ++-- vm_env/src/proof_of_stake.rs | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index cb45560e93..a7bc5b9e9f 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -152,13 +152,13 @@ pub trait PosReadOnly { fn read_validator_eth_cold_key( &self, key: &Self::Address, - ) -> Option; + ) -> Option>; /// Read PoS validator's Eth validator set update signing key fn read_validator_eth_hot_key( &self, key: &Self::Address, - ) -> Option; + ) -> Option>; } /// PoS system trait to be implemented in integration that can read and write @@ -612,13 +612,13 @@ pub trait PosBase { fn read_validator_eth_cold_key( &self, key: &Self::Address, - ) -> Option; + ) -> Option>; /// Read PoS validator's Eth validator set update signing key fn read_validator_eth_hot_key( &self, key: &Self::Address, - ) -> Option; + ) -> Option>; /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 1443c27811..496ebdfbbd 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -505,7 +505,7 @@ where fn read_validator_eth_cold_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { let (value, _gas) = self.read(&validator_eth_cold_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) @@ -514,7 +514,7 @@ where fn read_validator_eth_hot_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { let (value, _gas) = self.read(&validator_eth_hot_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 7d2b7680f1..0b581ad260 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -420,7 +420,7 @@ where fn read_validator_eth_cold_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { let value = self.ctx.read_pre(&validator_eth_cold_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) @@ -429,7 +429,7 @@ where fn read_validator_eth_hot_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { let value = self.ctx.read_pre(&validator_eth_hot_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 53e2ab9470..295cf6e692 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -177,14 +177,14 @@ impl namada_proof_of_stake::PosReadOnly for PoS { fn read_validator_eth_cold_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { tx::read(validator_eth_cold_key_key(key).to_string()) } fn read_validator_eth_hot_key( &self, key: &Self::Address, - ) -> Option { + ) -> Option> { tx::read(validator_eth_hot_key_key(key).to_string()) } } From 7e08fc09b6ea6f7e15d3e2615be4043d22a6d399 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 16 Sep 2022 09:53:38 +0100 Subject: [PATCH 0650/1995] Add epoched Ethereum keys --- apps/src/lib/node/ledger/shell/queries.rs | 22 ++++++++++++++----- .../vote_extensions/validator_set_update.rs | 7 ++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 75938e6ee1..70753faa64 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -340,6 +340,7 @@ pub(crate) trait QueriesExt { fn get_ethbridge_from_namada_addr( &self, validator: &Address, + epoch: Option, ) -> Option; /// For a given Namada validator, return its corresponding Ethereum @@ -347,6 +348,7 @@ pub(crate) trait QueriesExt { fn get_ethgov_from_namada_addr( &self, validator: &Address, + epoch: Option, ) -> Option; /// Extension of [`Self::get_active_validators`], which additionally returns @@ -569,20 +571,24 @@ where fn get_ethbridge_from_namada_addr( &self, validator: &Address, + epoch: Option, ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); self.read_validator_eth_hot_key(validator) .as_ref() - .and_then(|pk| pk.try_into().ok()) + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) } #[inline] fn get_ethgov_from_namada_addr( &self, validator: &Address, + epoch: Option, ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); self.read_validator_eth_cold_key(validator) .as_ref() - .and_then(|pk| pk.try_into().ok()) + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) } #[inline] @@ -593,15 +599,21 @@ where { let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); Box::new(self.get_active_validators(Some(epoch)).into_iter().map( - |validator| { + move |validator| { let hot_key_addr = self - .get_ethbridge_from_namada_addr(&validator.address) + .get_ethbridge_from_namada_addr( + &validator.address, + Some(epoch), + ) .expect( "All Namada validators should have an Ethereum bridge \ key", ); let cold_key_addr = self - .get_ethgov_from_namada_addr(&validator.address) + .get_ethgov_from_namada_addr( + &validator.address, + Some(epoch), + ) .expect( "All Namada validators should have an Ethereum \ governance key", diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs index e479b52f3f..255bf9c7b7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/validator_set_update.rs @@ -110,12 +110,15 @@ where ); VoteExtensionError::PubKeyNotInStorage })?; - let pk = self + let epoched_pk = self .storage .read_validator_eth_hot_key(validator) .expect("We should have this hot key in storage"); + let pk = epoched_pk + .get(last_height_epoch) + .expect("We should have the hot key of the given epoch"); // verify the signature of the vote extension - ext.verify(&pk) + ext.verify(pk) .map_err(|err| { tracing::error!( ?err, From daab76d638221e2089b8f6174d51de056da2b5da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 16 Sep 2022 10:04:14 +0100 Subject: [PATCH 0651/1995] Fix typo in secp key code --- shared/src/types/key/secp256k1.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index f9eebcd228..96b892fdbf 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -454,14 +454,14 @@ impl super::SigScheme for SigScheme { /// Sign the data with a key fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { - #[cfg(not(any(test, features = "secp256k1-sign-verify")))] + #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (keypair, data); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(any(test, features = "secp256k1-sign-verify"))] + #[cfg(any(test, feature = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let hash = Sha256::digest(data.as_ref()); @@ -476,14 +476,14 @@ impl super::SigScheme for SigScheme { data: &T, sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(any(test, features = "secp256k1-sign-verify")))] + #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (pk, data, sig); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(any(test, features = "secp256k1-sign-verify"))] + #[cfg(any(test, feature = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let bytes = &data @@ -509,14 +509,14 @@ impl super::SigScheme for SigScheme { data: &[u8], sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(any(test, features = "secp256k1-sign-verify")))] + #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (pk, data, sig); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(any(test, features = "secp256k1-sign-verify"))] + #[cfg(any(test, feature = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let hash = Sha256::digest(data); From 79a1f342a727302a884069c76c0dde52297e6469 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Sep 2022 15:00:11 +0000 Subject: [PATCH 0652/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1b2e9d4885..a112d705c4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.5873123abda899833648cbaec31f0596750fb683767c821712adc6b37b8f90b7.wasm", - "tx_ibc.wasm": "tx_ibc.b64374d501fb27b8200a496f2c50f2fd151c86b480b14910a1c678b9a18be255.wasm", - "tx_init_account.wasm": "tx_init_account.5753889fee02137cf35c2abf2b9992b624b66eb4e136a4ff345a7783ade4b0c8.wasm", - "tx_init_nft.wasm": "tx_init_nft.6a77790d623a80ce095117779b6c73e08650543e3552fcb50b76e7c78954a2ed.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d3a307253fbfd916fe5f78788d8f384e4040291198720aaff57df41b32b53e83.wasm", - "tx_init_validator.wasm": "tx_init_validator.ac9f6a6521312806b9aa73357780a7e3a4b6d4b454ff278aca11bd3ed36e10e2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.537ac560ebd83b67faaef4f61835c685b4cff662dd19d18e6a7e5d5c291d20c8.wasm", - "tx_transfer.wasm": "tx_transfer.a45e9e7f4ce046f0fbb1a1c111a83647009b597c9a3eb9de256642fd7a4ee9e1.wasm", - "tx_unbond.wasm": "tx_unbond.6506f23d8a5214ce7087dfc47db49430321f611869db3a2854c84112fea58862.wasm", - "tx_update_vp.wasm": "tx_update_vp.15772fa9ca0bc32600ec40e787541a1973fba7e3321b5f1f360a72683473e168.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e7d5aa0d70b0ece804af38cf225e13181f37dce92c687dde4455438ac828aebe.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdd9d50f9a9834584c590ef02e9294dad3f38ae721e79cbb27eee471a6d83679.wasm", - "vp_nft.wasm": "vp_nft.7c26f1d2ba12740b1b40edfed74eeba9c36173e968f82e82dbea2ac40d8d548f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d46022e48d1d20c4c62b2dd8c979bbfeb18f8e56d941b977d61f83e871ab5d11.wasm", - "vp_token.wasm": "vp_token.093ededf7f0163edb6498fcd6cd022902f461a1f1e110332b313fe8b92b1abb9.wasm", - "vp_user.wasm": "vp_user.6aa18329e89c9f3752a53ebc7a165f3a619563ee7dbaf65006518c4f4bccb76b.wasm" -} + "tx_from_intent.wasm": "tx_from_intent.3d14e2d1686da9426e7ee302f161711d276f8f56b212828ad10eebfad771126a.wasm", + "tx_ibc.wasm": "tx_ibc.90bcb48e525be4946627ed50aa064721783e3cfafa1b85086fc42e8e40ec41e6.wasm", + "tx_init_account.wasm": "tx_init_account.c5fceffd000ba09c5baa57b89bcd77e41d748ebb76640b51ffeaab38bda1a741.wasm", + "tx_init_nft.wasm": "tx_init_nft.de0244a2aebeae80e6a5f597184a5954af11bad8bd0bbc6c379ce9c667e5ab42.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7bad024a9c110d398f6bf2e0dc4a24eb651f31cd805eaadca42eb22a1ffe79ef.wasm", + "tx_init_validator.wasm": "tx_init_validator.588f42b882e718cf24e3e4361e46cc26c65a2ef244a0ffc382b4c9e053233120.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.5963f9f1517db04d0447f873b4ce3575a18e82b6e6580d568b8695815c4bb58d.wasm", + "tx_transfer.wasm": "tx_transfer.cb50725fdcae3aab5eb62ca0e31f1e32ee685d575105036faf9c54ccc80f3cca.wasm", + "tx_unbond.wasm": "tx_unbond.aabd65bad5c4253ad58bd6010bd1f48581d7e2fb31d3a75adc6302589831e557.wasm", + "tx_update_vp.wasm": "tx_update_vp.ececb049c6bfeac4a8c1fdd57846731277f8be4b353da02221bc25c8f6c08307.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e225c5c8230f6b78e2c82bc87e52638667696100231158c01758eca0285657da.wasm", + "tx_withdraw.wasm": "tx_withdraw.05fc02b992823b781f241d1f23da68b8fed9365d71fc0ed31e94b7b01c039553.wasm", + "vp_nft.wasm": "vp_nft.aac80ee4b961f933eb893637b11fafb090bf9416b3347902f96e0860d0fbfbb3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9e2f3e02672e7995384e444931f801a3ea539ffed8bcbc13a5667fc0973d8fb0.wasm", + "vp_token.wasm": "vp_token.bdd12480ab0bb59f8ffe04a40ddef07885e7d76bec41bd011ca0025d2567205a.wasm", + "vp_user.wasm": "vp_user.5fe4630e9f67f4f7518c4d7f25e7428144898d1123fca394525f5b0528b3178f.wasm" +} \ No newline at end of file From 47222b690e37fea65b9a9dac17b014af85663065 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 20 Sep 2022 15:00:11 +0000 Subject: [PATCH 0653/1995] 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 1b2e9d4885..a112d705c4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.5873123abda899833648cbaec31f0596750fb683767c821712adc6b37b8f90b7.wasm", - "tx_ibc.wasm": "tx_ibc.b64374d501fb27b8200a496f2c50f2fd151c86b480b14910a1c678b9a18be255.wasm", - "tx_init_account.wasm": "tx_init_account.5753889fee02137cf35c2abf2b9992b624b66eb4e136a4ff345a7783ade4b0c8.wasm", - "tx_init_nft.wasm": "tx_init_nft.6a77790d623a80ce095117779b6c73e08650543e3552fcb50b76e7c78954a2ed.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d3a307253fbfd916fe5f78788d8f384e4040291198720aaff57df41b32b53e83.wasm", - "tx_init_validator.wasm": "tx_init_validator.ac9f6a6521312806b9aa73357780a7e3a4b6d4b454ff278aca11bd3ed36e10e2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.537ac560ebd83b67faaef4f61835c685b4cff662dd19d18e6a7e5d5c291d20c8.wasm", - "tx_transfer.wasm": "tx_transfer.a45e9e7f4ce046f0fbb1a1c111a83647009b597c9a3eb9de256642fd7a4ee9e1.wasm", - "tx_unbond.wasm": "tx_unbond.6506f23d8a5214ce7087dfc47db49430321f611869db3a2854c84112fea58862.wasm", - "tx_update_vp.wasm": "tx_update_vp.15772fa9ca0bc32600ec40e787541a1973fba7e3321b5f1f360a72683473e168.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e7d5aa0d70b0ece804af38cf225e13181f37dce92c687dde4455438ac828aebe.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdd9d50f9a9834584c590ef02e9294dad3f38ae721e79cbb27eee471a6d83679.wasm", - "vp_nft.wasm": "vp_nft.7c26f1d2ba12740b1b40edfed74eeba9c36173e968f82e82dbea2ac40d8d548f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d46022e48d1d20c4c62b2dd8c979bbfeb18f8e56d941b977d61f83e871ab5d11.wasm", - "vp_token.wasm": "vp_token.093ededf7f0163edb6498fcd6cd022902f461a1f1e110332b313fe8b92b1abb9.wasm", - "vp_user.wasm": "vp_user.6aa18329e89c9f3752a53ebc7a165f3a619563ee7dbaf65006518c4f4bccb76b.wasm" -} + "tx_from_intent.wasm": "tx_from_intent.3d14e2d1686da9426e7ee302f161711d276f8f56b212828ad10eebfad771126a.wasm", + "tx_ibc.wasm": "tx_ibc.90bcb48e525be4946627ed50aa064721783e3cfafa1b85086fc42e8e40ec41e6.wasm", + "tx_init_account.wasm": "tx_init_account.c5fceffd000ba09c5baa57b89bcd77e41d748ebb76640b51ffeaab38bda1a741.wasm", + "tx_init_nft.wasm": "tx_init_nft.de0244a2aebeae80e6a5f597184a5954af11bad8bd0bbc6c379ce9c667e5ab42.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7bad024a9c110d398f6bf2e0dc4a24eb651f31cd805eaadca42eb22a1ffe79ef.wasm", + "tx_init_validator.wasm": "tx_init_validator.588f42b882e718cf24e3e4361e46cc26c65a2ef244a0ffc382b4c9e053233120.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.5963f9f1517db04d0447f873b4ce3575a18e82b6e6580d568b8695815c4bb58d.wasm", + "tx_transfer.wasm": "tx_transfer.cb50725fdcae3aab5eb62ca0e31f1e32ee685d575105036faf9c54ccc80f3cca.wasm", + "tx_unbond.wasm": "tx_unbond.aabd65bad5c4253ad58bd6010bd1f48581d7e2fb31d3a75adc6302589831e557.wasm", + "tx_update_vp.wasm": "tx_update_vp.ececb049c6bfeac4a8c1fdd57846731277f8be4b353da02221bc25c8f6c08307.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e225c5c8230f6b78e2c82bc87e52638667696100231158c01758eca0285657da.wasm", + "tx_withdraw.wasm": "tx_withdraw.05fc02b992823b781f241d1f23da68b8fed9365d71fc0ed31e94b7b01c039553.wasm", + "vp_nft.wasm": "vp_nft.aac80ee4b961f933eb893637b11fafb090bf9416b3347902f96e0860d0fbfbb3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9e2f3e02672e7995384e444931f801a3ea539ffed8bcbc13a5667fc0973d8fb0.wasm", + "vp_token.wasm": "vp_token.bdd12480ab0bb59f8ffe04a40ddef07885e7d76bec41bd011ca0025d2567205a.wasm", + "vp_user.wasm": "vp_user.5fe4630e9f67f4f7518c4d7f25e7428144898d1123fca394525f5b0528b3178f.wasm" +} \ No newline at end of file From b2fc5c1ab3586348539a2a0d2360dc6efb03a0c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 21 Sep 2022 10:30:07 +0100 Subject: [PATCH 0654/1995] Load the right keys from ValidatorConfig --- apps/src/lib/config/genesis.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 80ef4ee7b0..5441ce524f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -323,13 +323,13 @@ pub mod genesis_config { .to_public_key() .unwrap(), eth_cold_key: config - .staking_reward_public_key + .eth_cold_key .as_ref() .unwrap() .to_public_key() .unwrap(), eth_hot_key: config - .staking_reward_public_key + .eth_hot_key .as_ref() .unwrap() .to_public_key() From 34621dae717d3d2d015f4ebf0143432b22507480 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 10:57:54 +0100 Subject: [PATCH 0655/1995] Temporarily disable eth bridge e2e tests --- tests/src/e2e.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index a9eb2b2cf5..503ac62f03 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -11,7 +11,7 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -pub mod eth_bridge_tests; +//pub mod eth_bridge_tests; pub mod gossip_tests; pub mod helpers; pub mod ledger_tests; From 58408301e5a22b74c1fa856e8eedf4eff02899f2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 10:58:37 +0100 Subject: [PATCH 0656/1995] Disable eth full node in ledger tests --- tests/src/e2e/ledger_tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1f19433ca1..1cc64c729c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -18,6 +18,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use namada::types::token; +use namada_apps::config::ethereum; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; @@ -1765,6 +1766,8 @@ fn test_genesis_validators() -> Result<()> { .rpc_address .set_port(first_port + 1); config.ledger.shell.ledger_address.set_port(first_port + 2); + // disable eth full node + config.ledger.ethereum.mode = ethereum::Mode::Off; config }; From 8f9c604cb3a2f3af9e21f0126c543a6508b7b04e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 13:00:19 +0100 Subject: [PATCH 0657/1995] Add abciplus feature flag to e2e tests --- tests/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4ae17e8fff..5c25d839ca 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -12,7 +12,7 @@ default = ["wasm-runtime"] wasm-runtime = ["namada/wasm-runtime"] [dependencies] -namada = {path = "../shared", features = ["testing", "ibc-mocks"]} +namada = {path = "../shared", features = ["abciplus", "testing", "ibc-mocks"]} namada_vm_env = {path = "../vm_env"} chrono = "0.4.19" concat-idents = "1.1.2" @@ -26,7 +26,7 @@ tracing-subscriber = {version = "0.3.7", default-features = false, features = [" derivative = "2.2.0" [dev-dependencies] -namada_apps = {path = "../apps", default-features = false, features = ["testing"]} +namada_apps = {path = "../apps", default-features = false, features = ["abciplus", "testing"]} assert_cmd = "1.0.7" borsh = "0.9.1" color-eyre = "0.5.11" From 1fdd1be3ab211ca0b0a1d94fdc792906bb3fb94f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 13:05:46 +0100 Subject: [PATCH 0658/1995] Fix e2e test regex matching on fullnodes --- apps/src/lib/node/ledger/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 88b0588b7e..27e29730cc 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -480,8 +480,11 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { TendermintMode::Validator => { tracing::info!("This node is a validator"); } - TendermintMode::Full | TendermintMode::Seed => { - tracing::info!("This node is not a validator"); + TendermintMode::Full => { + tracing::info!("This node is a fullnode"); + } + TendermintMode::Seed => { + tracing::info!("This node is a seednode"); } } shell.run() From 4950c44962fab6047c9a06a2a3208f31b64862f6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 13:05:59 +0100 Subject: [PATCH 0659/1995] Run make fmt --- tests/src/e2e.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index 503ac62f03..a935e4c2ba 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -11,7 +11,7 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -//pub mod eth_bridge_tests; +// pub mod eth_bridge_tests; pub mod gossip_tests; pub mod helpers; pub mod ledger_tests; From 95d71af572f5711a1212361a5f31d9f2ce3a3c5d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 13:17:57 +0100 Subject: [PATCH 0660/1995] Fix regex for valid tx in e2e tests --- tests/src/e2e/ledger_tests.rs | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1cc64c729c..f079cc56c0 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -110,7 +110,7 @@ fn test_node_connectivity() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 3. Check that all the nodes processed the tx with the same result @@ -344,7 +344,7 @@ fn ledger_txs_and_queries() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; } - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); } } @@ -588,7 +588,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 3. Submit a delegation to the genesis validator @@ -610,7 +610,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 4. Submit an unbond of the self-bond @@ -631,7 +631,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -653,7 +653,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 6. Wait for the unbonding epoch @@ -694,7 +694,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 8. Submit a withdrawal of the delegation @@ -714,7 +714,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); Ok(()) @@ -783,7 +783,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 3. Submit a delegation to the new validator @@ -808,7 +808,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // Then self-bond the tokens: let tx_args = vec![ @@ -829,7 +829,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 4. Transfer some XAN to the new validator @@ -853,7 +853,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 5. Submit a self-bond for the new validator @@ -873,7 +873,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 6. Wait for the pipeline epoch when the validator's voting power should @@ -963,7 +963,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut client = run!(*test, Bin::Client, args, Some(40))?; client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); let res: Result<()> = Ok(()); res @@ -1032,7 +1032,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 2. Submit valid proposal @@ -1077,7 +1077,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 3. Query the proposal @@ -1233,7 +1233,7 @@ fn proposal_submission() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); let submit_proposal_vote_delagator = vec![ @@ -1250,7 +1250,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 10. Send a yay vote from a non-validator/non-delegator user @@ -1269,7 +1269,7 @@ fn proposal_submission() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 11. Query the proposal and check the result @@ -1379,7 +1379,7 @@ fn proposal_offline() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 2. Create an offline proposal @@ -1839,7 +1839,7 @@ fn test_genesis_validators() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; + client.exp_string("Transaction added to mempool:")?; client.assert_success(); // 3. Check that all the nodes processed the tx with the same result From eb798c95c21c7fba1fe04e018a17b1c995058913 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 17:11:52 +0100 Subject: [PATCH 0661/1995] Add a script to unwrap e2e log output --- scripts/unwrap_e2e_log.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 scripts/unwrap_e2e_log.py diff --git a/scripts/unwrap_e2e_log.py b/scripts/unwrap_e2e_log.py new file mode 100755 index 0000000000..c4910763e4 --- /dev/null +++ b/scripts/unwrap_e2e_log.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +import re +import sys + +UNICODE = re.compile(r'\\u{([\da-fA-F]+)}') + +def main(): + if len(sys.argv) > 1: + with open(sys.argv[1], 'r') as f: + process_file(f) + else: + process_file(sys.stdin) + +def process_file(f): + for line in f.readlines(): + process_line(line) + sys.stdout.flush() + +def process_line(line): + prefix = 'read: ' + for m in UNICODE.findall(line): + line = line.replace(f'\\u{{{m}}}', f'\\u{int(m, 16):04x}') + line = eval(line[len(prefix):]) + sys.stdout.write(line) + +if __name__ == '__main__': + main() From 7800d906f2ee4fa279376a98f9d96e6437eb2614 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 13 Sep 2022 17:15:17 +0100 Subject: [PATCH 0662/1995] Describe unwrap e2e tests script --- scripts/unwrap_e2e_log.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/unwrap_e2e_log.py b/scripts/unwrap_e2e_log.py index c4910763e4..7fc60301f7 100755 --- a/scripts/unwrap_e2e_log.py +++ b/scripts/unwrap_e2e_log.py @@ -1,4 +1,8 @@ #!/usr/bin/env python3 + +# this script takes `expectrl` log outputs, such as the ones emitted by +# e2e tests, and unwraps them into a more readable format + import re import sys From 56350f1b7852cdd88945d906f6b2a4a6580ea16d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 21 Sep 2022 13:42:48 +0100 Subject: [PATCH 0663/1995] Revert "Fix regex for valid tx in e2e tests" This reverts commit d8a7d6a284c189a5f494caeabdeafb1fe776acdc. --- tests/src/e2e/ledger_tests.rs | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f079cc56c0..1cc64c729c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -110,7 +110,7 @@ fn test_node_connectivity() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 3. Check that all the nodes processed the tx with the same result @@ -344,7 +344,7 @@ fn ledger_txs_and_queries() -> Result<()> { client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; } - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); } } @@ -588,7 +588,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 3. Submit a delegation to the genesis validator @@ -610,7 +610,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 4. Submit an unbond of the self-bond @@ -631,7 +631,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -653,7 +653,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 6. Wait for the unbonding epoch @@ -694,7 +694,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 8. Submit a withdrawal of the delegation @@ -714,7 +714,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); Ok(()) @@ -783,7 +783,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 3. Submit a delegation to the new validator @@ -808,7 +808,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // Then self-bond the tokens: let tx_args = vec![ @@ -829,7 +829,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 4. Transfer some XAN to the new validator @@ -853,7 +853,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 5. Submit a self-bond for the new validator @@ -873,7 +873,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 6. Wait for the pipeline epoch when the validator's voting power should @@ -963,7 +963,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut client = run!(*test, Bin::Client, args, Some(40))?; client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); let res: Result<()> = Ok(()); res @@ -1032,7 +1032,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 2. Submit valid proposal @@ -1077,7 +1077,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 3. Query the proposal @@ -1233,7 +1233,7 @@ fn proposal_submission() -> Result<()> { submit_proposal_vote, Some(15) )?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); let submit_proposal_vote_delagator = vec![ @@ -1250,7 +1250,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 10. Send a yay vote from a non-validator/non-delegator user @@ -1269,7 +1269,7 @@ fn proposal_submission() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 11. Query the proposal and check the result @@ -1379,7 +1379,7 @@ fn proposal_offline() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 2. Create an offline proposal @@ -1839,7 +1839,7 @@ fn test_genesis_validators() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction added to mempool:")?; + client.exp_string("Transaction is valid.")?; client.assert_success(); // 3. Check that all the nodes processed the tx with the same result From 4babd0b7fe8004c46f151eac8596e7c8ddd2141b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 19 Sep 2022 10:22:20 +0100 Subject: [PATCH 0664/1995] Partially fix ledger_txs_and_queries() e2e test --- tests/src/e2e/ledger_tests.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1cc64c729c..92f6af5af2 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -236,8 +236,24 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 7. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { + use namada_apps::config::Config; + let test = setup::network(|genesis| genesis, None)?; + let update_config = |mut config: Config| { + // disable eth full node + config.ledger.ethereum.mode = ethereum::Mode::Off; + config + }; + + let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); + let validator_0_config = update_config( + Config::load(&validator_0_base_dir, &test.net.chain_id, None), + ); + validator_0_config + .write(&validator_0_base_dir, &test.net.chain_id, true) + .unwrap(); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; From d6f1ecd9f03695407bffc1404587f356b304522d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 21 Sep 2022 16:36:17 +0100 Subject: [PATCH 0665/1995] Return the protocol txs from PrepareProposal This is done so Tendermint can remove the protocol txs from its mempool. --- .../lib/node/ledger/shell/prepare_proposal.rs | 9 +++- .../lib/node/ledger/shell/vote_extensions.rs | 41 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 765ace2b6c..84240aadbf 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -120,7 +120,12 @@ where .votes, ); #[cfg(not(feature = "abcipp"))] - let (eth_events, valset_upds) = split_vote_extensions(txs); + let (protocol_txs, eth_events, valset_upds) = + split_vote_extensions(txs); + + // TODO: remove this later, when we get rid of `abciplus` + #[cfg(feature = "abcipp")] + let protocol_txs = vec![]; let ethereum_events = self .compress_ethereum_events(eth_events) @@ -148,6 +153,8 @@ where validator_set_update, }) .map(|tx| tx.sign(protocol_key).to_bytes()) + // TODO: remove this later, when we get rid of `abciplus` + .chain(protocol_txs.into_iter()) .collect() } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 7ace4dd853..afbb707847 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -273,11 +273,11 @@ pub fn deserialize_vote_extensions( #[cfg(not(feature = "abcipp"))] pub fn deserialize_vote_extensions( txs: &[TxBytes], -) -> impl Iterator + '_ { +) -> impl Iterator + '_ { use namada::types::transaction::protocol::ProtocolTx; - txs.iter().filter_map(|tx| { - let tx = match Tx::try_from(tx.as_slice()) { + txs.iter().filter_map(|tx_bytes| { + let tx = match Tx::try_from(tx_bytes.as_slice()) { Ok(tx) => tx, Err(err) => { tracing::warn!( @@ -291,7 +291,7 @@ pub fn deserialize_vote_extensions( TxType::Protocol(ProtocolTx { tx: ProtocolTxType::VoteExtension(ext), .. - }) => Some(ext), + }) => Some((tx_bytes.clone(), ext)), _ => None, } }) @@ -315,9 +315,9 @@ pub fn iter_protocol_txs( /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering /// out invalid data, and splits these into [`ethereum_events::Vext`] /// and [`validator_set_update::Vext`] instances. +#[cfg(feature = "abcipp")] pub fn split_vote_extensions( - #[cfg(feature = "abcipp")] vote_extensions: Vec, - #[cfg(not(feature = "abcipp"))] vote_extensions: &[TxBytes], + vote_extensions: Vec, ) -> ( Vec>, Vec, @@ -334,3 +334,32 @@ pub fn split_vote_extensions( (eth_evs, valset_upds) } + +/// Deserializes [`VoteExtension`] instances from mempool protocol txs, +/// filtering out non-protocol txs, and splits these into +/// [`ethereum_events::Vext`] and [`validator_set_update::Vext`] instances. +/// +/// The original [`TxBytes`] are also returned, such that we can remove +/// them from Tendermint's mempool. +#[cfg(not(feature = "abcipp"))] +pub fn split_vote_extensions( + mempool_txs: &[TxBytes], +) -> ( + Vec, + Vec>, + Vec, +) { + let mut txs = vec![]; + let mut eth_evs = vec![]; + let mut valset_upds = vec![]; + + for (tx, ext) in deserialize_vote_extensions(mempool_txs) { + if let Some(validator_set_update) = ext.validator_set_update { + valset_upds.push(validator_set_update); + } + eth_evs.push(ext.ethereum_events); + txs.push(tx); + } + + (txs, eth_evs, valset_upds) +} From d66c97de2228ba8124e104ffe0b08e64e07f01ea Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 21 Sep 2022 16:54:09 +0100 Subject: [PATCH 0666/1995] Fix PrepareProposal tests --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 84240aadbf..865cf382b6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -728,14 +728,8 @@ mod test_prepare_proposal { txs: vec![tx], ..Default::default() }); - #[cfg(feature = "abcipp")] - assert_eq!(rsp.txs.len(), 1); - #[cfg(not(feature = "abcipp"))] - assert_eq!(rsp.txs.len(), 2); + assert_eq!(rsp.txs.len(), 3); - #[cfg(feature = "abcipp")] - let tx_bytes = rsp.txs.pop().unwrap(); - #[cfg(not(feature = "abcipp"))] // NOTE: we remove the first pos, bc the ethereum events // vote extension protocol tx will always precede the // valset upd vext protocol tx @@ -868,7 +862,7 @@ mod test_prepare_proposal { txs: vec![vote], ..Default::default() }); - assert_eq!(rsp.txs.len(), 2); + assert_eq!(rsp.txs.len(), 3); let tx_bytes = rsp.txs.remove(0); let got = Tx::try_from(&tx_bytes[..]).unwrap(); From 3a9bc8521f4a97561992b96b4ad8a6f1a1e9bfe4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 10:57:05 +0100 Subject: [PATCH 0667/1995] WIP: Start Ethereum node --- apps/src/lib/node/ledger/mod.rs | 172 +++++++++++++++++++------------- 1 file changed, 101 insertions(+), 71 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d24a88bf0f..2c1f9a6ccc 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -18,9 +18,11 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; use namada::ledger::governance::storage as gov_storage; +use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; +use tokio::sync::mpsc; use tower::ServiceBuilder; use self::abortable::AbortableSpawner; @@ -205,11 +207,12 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// /// This includes: /// - A Tendermint node. -/// - A shell which contains an ABCI server, for talking to the Tendermint node. +/// - A shell which contains an ABCI server, for talking to the Tendermint +/// node. /// - A broadcaster, for the ledger may submit txs to the chain. /// - An Ethereum full node. -/// - An oracle, to receive events from the Ethereum full node, -/// and forward them to the ledger. +/// - An oracle, to receive events from the Ethereum full node, and forward +/// them to the ledger. /// /// All must be alive for correct functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { @@ -223,7 +226,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let tendermint_node = start_tendermint(&mut spawner, &config); // Start Ethereum full node and its oracle - let (eth_node, eth_receiver, oracle) = start_ethereum_node(&mut spawner, &config); + let (eth_node, eth_receiver, oracle) = + start_ethereum_node(&mut spawner, &config).await; // Start ABCI server and broadcaster (the latter only if we are a validator // node) @@ -239,7 +243,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let aborted = spawner.wait_for_abort().await.child_terminated(); // Wait for all managed tasks to finish. - let res = tokio::try_join!(tendermint_node, eth_node, abci, oracle, broadcaster); + let res = + tokio::try_join!(tendermint_node, eth_node, abci, oracle, broadcaster); match res { Ok((tendermint_res, eth_res, abci_res, _, _)) => { @@ -382,7 +387,7 @@ async fn run_aux_setup( /// Lastly, this function executes an ABCI shell on a new OS thread. fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, - eth_receiver: (), + eth_receiver: Option>, wasm_dir: PathBuf, setup_data: RunAuxSetup, config: config::Ledger, @@ -400,8 +405,7 @@ fn start_abci_broadcaster_shell( // 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_sender, broadcaster_receiver) = mpsc::unbounded_channel(); // Start broadcaster let broadcaster = if matches!( @@ -426,8 +430,7 @@ fn start_abci_broadcaster_shell( let _ = bc_abort_send.send(()); }) } else { - // dummy async task, which will resolve instantly - tokio::spawn(async { std::future::ready(()).await }) + spawn_dummy_task() }; // Construct our ABCI application. @@ -448,7 +451,7 @@ fn start_abci_broadcaster_shell( // Start the ABCI server let abci = spawner - .spawn_abortable("ABCI", move |aborter| async move { + .spawn_abortable("ABCI-server", move |aborter| async move { let res = run_abci(abci_service, ledger_address, abci_abort_recv).await; @@ -592,73 +595,100 @@ fn start_tendermint( }) } -// let (eth_node, eth_receiver, oracle) = start_ethereum_node(&mut spawner, &config); -fn start_ethereum_node(spawner: (), config: ()) -> ((), (), ()) { +// TODO: add docstring +async fn start_ethereum_node( + spawner: &mut AbortableSpawner, + config: &config::Ledger, +) -> ( + task::JoinHandle>, + Option>, + task::JoinHandle<()>, +) { + if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { + let eth_node = spawn_dummy_task(); + let oracle = spawn_dummy_task(); + return (eth_node, None, oracle); + } + let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); - let (eth_node, oracle) = if matches!( - config.tendermint.tendermint_mode, - TendermintMode::Validator - ) { - // boot up the ethereum node process and wait for it to finish syncing - let (eth_sender, eth_receiver) = unbounded_channel(); - let url = ethereum_url.clone(); - let start_managed_eth_node = - matches!(config.ethereum.mode, ethereum::Mode::Managed); - let (eth_node, abort_sender) = - ethereum_node::start(&url, start_managed_eth_node) - .await - .expect("Unable to start the Ethereum fullnode"); - - // Start Ethereum fullnode - // Channel for signalling shut down to Tendermint process - let (eth_abort_send, eth_abort_recv) = - tokio::sync::oneshot::channel::>(); - let abort_send_for_eth = abort_send.clone(); - // run geth in the background - let eth_node = tokio::spawn(async move { - // On panic or exit, the `Drop` of `AbortSender` will send abort - // message - let aborter = Aborter { - sender: abort_send_for_eth, - who: "Ethereum", - }; - - let res = ethereum_node::monitor(eth_node, eth_abort_recv) - .map_err(Error::Ethereum) - .await; - tracing::info!("Ethereum fullnode is no longer running."); - drop(aborter); - res - }); + // boot up the ethereum node process and wait for it to finish syncing + let (eth_sender, eth_receiver) = mpsc::unbounded_channel(); + let url = ethereum_url.clone(); + let start_managed_eth_node = + matches!(config.ethereum.mode, ethereum::Mode::Managed); + let (eth_node, abort_sender) = + ethereum_node::start(&url, start_managed_eth_node) + .await + .expect("Unable to start the Ethereum fullnode"); + + // Start Ethereum fullnode + // Channel for signalling shut down to Tendermint process + let (eth_abort_send, eth_abort_recv) = + tokio::sync::oneshot::channel::>(); + let abort_send_for_eth = abort_send.clone(); + // run geth in the background + let eth_node = tokio::spawn(async move { + // On panic or exit, the `Drop` of `AbortSender` will send abort + // message + let aborter = Aborter { + sender: abort_send_for_eth, + who: "Ethereum", + }; - let oracle = match config.ethereum.mode { - ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( + let res = ethereum_node::monitor(eth_node, eth_abort_recv) + .map_err(Error::Ethereum) + .await; + tracing::info!("Ethereum fullnode is no longer running."); + + drop(aborter); + res + }); + + let oracle = match config.ethereum.mode { + ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ), + ethereum::Mode::EventsEndpoint => { + ethereum_node::test_tools::event_endpoint::start_oracle(eth_sender) + } + ethereum::Mode::Off => { + ethereum_node::test_tools::mock_oracle::run_oracle( ethereum_url, eth_sender, abort_sender, - ), - ethereum::Mode::EventsEndpoint => { - ethereum_node::test_tools::event_endpoint::start_oracle( - eth_sender, - ) - } - ethereum::Mode::Off => { - ethereum_node::test_tools::mock_oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ) + ) + } + }; + + // Shutdown the Ethereum node subprocess via a message to ensure that + // the child process is properly cleaned-up. + let (eth_abort_resp_send, eth_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(()) = eth_abort_send.send(eth_abort_resp_send) { + match eth_abort_resp_recv.await { + Ok(()) => {} + Err(err) => { + tracing::error!( + "Failed to receive a response from Ethereum: {}", + err + ); } - }; + } + } - // Shutdown the Ethereum node subprocess via a message to ensure that - // the child process is properly cleaned-up. - let (eth_abort_resp_send, eth_abort_resp_recv) = - tokio::sync::oneshot::channel::<()>(); - } else { - ((), ()) - }; + (eth_node, eth_receiver, oracle) +} - ((), (), ()) +/// Spawn a dummy async task, which will resolve instantly. +fn spawn_dummy_task() -> task::JoinHandle { + // ``` + // tokio::spawn(async { std::future::ready(()).await }) + // ``` + tokio::spawn(std::future::ready()) } From eac20d17cc47aac9707d9e3b1ef5d75d8f1c328e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 11:04:04 +0100 Subject: [PATCH 0668/1995] WIP: Fixing up docstrings --- apps/src/lib/node/ledger/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 2c1f9a6ccc..778ec8ffd8 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -379,12 +379,9 @@ 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. +/// This function spawns an ABCI server and a [`Broadcaster`] into the +/// asynchronous runtime. Additionally, it executes a shell in +/// a new OS thread, to drive the ABCI server. fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, eth_receiver: Option>, @@ -534,7 +531,7 @@ async fn run_abci( } /// Launches a new task managing a Tendermint process into the asynchronous -/// runtime, and returns its `JoinHandle`. +/// runtime, and returns its [`task::JoinHandle`]. fn start_tendermint( spawner: &mut AbortableSpawner, config: &config::Ledger, @@ -685,7 +682,8 @@ async fn start_ethereum_node( (eth_node, eth_receiver, oracle) } -/// Spawn a dummy async task, which will resolve instantly. +/// Spawn a dummy asynchronous task into the runtime, +/// which will resolve instantly. fn spawn_dummy_task() -> task::JoinHandle { // ``` // tokio::spawn(async { std::future::ready(()).await }) From 00fc8abe196ffdaa7f88a2b36de97c22981dd5ee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 11:42:20 +0100 Subject: [PATCH 0669/1995] Fix all clippy checks --- apps/src/lib/node/ledger/mod.rs | 94 +++++++++++++++------------------ 1 file changed, 42 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 778ec8ffd8..04f24b9970 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -23,6 +23,7 @@ use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; use tokio::sync::mpsc; +use tokio::task; use tower::ServiceBuilder; use self::abortable::AbortableSpawner; @@ -59,7 +60,6 @@ const ENV_VAR_RAYON_THREADS: &str = "ANOMA_RAYON_THREADS"; // Poll::Ready(Ok(())) // } //``` - impl Shell { fn load_proposals(&mut self) { let proposals_key = gov_storage::get_commiting_proposals_prefix( @@ -427,9 +427,14 @@ fn start_abci_broadcaster_shell( let _ = bc_abort_send.send(()); }) } else { - spawn_dummy_task() + spawn_dummy_task(()) }; + // 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 tendermint_mode = config.tendermint.tendermint_mode.clone(); let ledger_address = config.shell.ledger_address; @@ -448,7 +453,7 @@ fn start_abci_broadcaster_shell( // Start the ABCI server let abci = spawner - .spawn_abortable("ABCI-server", move |aborter| async move { + .spawn_abortable("ABCI", move |aborter| async move { let res = run_abci(abci_service, ledger_address, abci_abort_recv).await; @@ -602,46 +607,53 @@ async fn start_ethereum_node( task::JoinHandle<()>, ) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { - let eth_node = spawn_dummy_task(); - let oracle = spawn_dummy_task(); + let eth_node = spawn_dummy_task(Ok(())); + let oracle = spawn_dummy_task(()); return (eth_node, None, oracle); } let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); - // boot up the ethereum node process and wait for it to finish syncing - let (eth_sender, eth_receiver) = mpsc::unbounded_channel(); - let url = ethereum_url.clone(); + // Boot up geth and wait for it to finish syncing let start_managed_eth_node = matches!(config.ethereum.mode, ethereum::Mode::Managed); let (eth_node, abort_sender) = - ethereum_node::start(&url, start_managed_eth_node) + ethereum_node::start(ðereum_url, start_managed_eth_node) .await .expect("Unable to start the Ethereum fullnode"); - // Start Ethereum fullnode - // Channel for signalling shut down to Tendermint process + // Run geth in the background let (eth_abort_send, eth_abort_recv) = tokio::sync::oneshot::channel::>(); - let abort_send_for_eth = abort_send.clone(); - // run geth in the background - let eth_node = tokio::spawn(async move { - // On panic or exit, the `Drop` of `AbortSender` will send abort - // message - let aborter = Aborter { - sender: abort_send_for_eth, - who: "Ethereum", - }; + let eth_node = spawner + .spawn_abortable("Ethereum", move |aborter| async move { + let res = ethereum_node::monitor(eth_node, eth_abort_recv) + .map_err(Error::Ethereum) + .await; + tracing::info!("Ethereum fullnode is no longer running."); - let res = ethereum_node::monitor(eth_node, eth_abort_recv) - .map_err(Error::Ethereum) - .await; - tracing::info!("Ethereum fullnode is no longer running."); + drop(aborter); + res + }) + .with_cleanup(async move { + let (eth_abort_resp_send, eth_abort_resp_recv) = + tokio::sync::oneshot::channel::<()>(); - drop(aborter); - res - }); + if let Ok(()) = eth_abort_send.send(eth_abort_resp_send) { + match eth_abort_resp_recv.await { + Ok(()) => {} + Err(err) => { + tracing::error!( + "Failed to receive a response from Ethereum: {}", + err + ); + } + } + } + }); + // Start the oracle for listening to Ethereum events + let (eth_sender, eth_receiver) = mpsc::unbounded_channel(); let oracle = match config.ethereum.mode { ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( ethereum_url, @@ -660,33 +672,11 @@ async fn start_ethereum_node( } }; - // Shutdown the Ethereum node subprocess via a message to ensure that - // the child process is properly cleaned-up. - let (eth_abort_resp_send, eth_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(()) = eth_abort_send.send(eth_abort_resp_send) { - match eth_abort_resp_recv.await { - Ok(()) => {} - Err(err) => { - tracing::error!( - "Failed to receive a response from Ethereum: {}", - err - ); - } - } - } - - (eth_node, eth_receiver, oracle) + (eth_node, Some(eth_receiver), oracle) } /// Spawn a dummy asynchronous task into the runtime, /// which will resolve instantly. -fn spawn_dummy_task() -> task::JoinHandle { - // ``` - // tokio::spawn(async { std::future::ready(()).await }) - // ``` - tokio::spawn(std::future::ready()) +fn spawn_dummy_task(ready: T) -> task::JoinHandle { + tokio::spawn(async { std::future::ready(ready).await }) } From 1289d745c93a767008646175505f42dd5ffa2a45 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 11:45:26 +0100 Subject: [PATCH 0670/1995] Remove changelog file --- .../improvements/1231-refactor-ledger-run-with-cleanup.md | 2 -- 1 file changed, 2 deletions(-) delete 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 deleted file mode 100644 index 6d0ee99747..0000000000 --- a/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md +++ /dev/null @@ -1,2 +0,0 @@ -- Refactored ledger startup code - ([#1231](https://github.com/anoma/anoma/pull/1231)) \ No newline at end of file From 7d7101a65fcec0b23a595b8fd7e89f141dc426bb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 11:47:19 +0100 Subject: [PATCH 0671/1995] Fix docstring --- apps/src/lib/node/ledger/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 04f24b9970..e70676d728 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -209,7 +209,7 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// - A Tendermint node. /// - A shell which contains an ABCI server, for talking to the Tendermint /// node. -/// - A broadcaster, for the ledger may submit txs to the chain. +/// - A [`Broadcaster`], for the ledger to submit txs to Tendermint's mempool. /// - An Ethereum full node. /// - An oracle, to receive events from the Ethereum full node, and forward /// them to the ledger. From afedaaeb767b95e5c454dfe1eb7d6a087ca39901 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 11:53:45 +0100 Subject: [PATCH 0672/1995] Add missing docstring --- apps/src/lib/node/ledger/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index e70676d728..7e35f9bb1f 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -597,7 +597,11 @@ fn start_tendermint( }) } -// TODO: add docstring +/// Launches a new task managing a `geth` process into the asynchronous +/// runtime, and returns its [`task::JoinHandle`]. +/// +/// An oracle is also returned, along with its associated channel, +/// for receiving Ethereum events from `geth`. async fn start_ethereum_node( spawner: &mut AbortableSpawner, config: &config::Ledger, From 69904c8590ef364ad56696dc1b0b04ad2e09898d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 12:01:29 +0100 Subject: [PATCH 0673/1995] Better logging of Tendermint node kind --- apps/src/lib/node/ledger/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 7e35f9bb1f..369c938c1d 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -473,8 +473,11 @@ fn start_abci_broadcaster_shell( TendermintMode::Validator => { tracing::info!("This node is a validator"); } - TendermintMode::Full | TendermintMode::Seed => { - tracing::info!("This node is not a validator"); + TendermintMode::Full => { + tracing::info!("This node is a fullnode"); + } + TendermintMode::Seed => { + tracing::info!("This node is a seednode"); } } shell.run() From e7132507994d9be7e5b5d59f90638e0d4e876883 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 10:29:36 +0100 Subject: [PATCH 0674/1995] Avoid starving other Tokio tasks while waiting for geth to exit --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 1eb3c03025..c3defe85ec 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -99,7 +99,6 @@ pub mod eth_fullnode { use async_trait::async_trait; use tokio::process::{Child, Command}; - use tokio::sync::oneshot::error::TryRecvError; use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::task::LocalSet; use web30::client::Web3; @@ -209,22 +208,19 @@ pub mod eth_fullnode { /// received from the Oracle process. If either, return the /// status. async fn wait(&mut self) -> Result<()> { - loop { - match self.process.try_wait() { - Ok(Some(status)) => { - return if status.success() { - Ok(()) - } else { - Err(Error::Runtime(status.to_string())) - }; - } - Ok(None) => {} - Err(err) => return Err(Error::Runtime(err.to_string())), + use futures::future::{self, Either}; + + let child_proc = self.process.wait(); + futures::pin_mut!(child_proc); + + match future::select(&mut self.abort_recv, child_proc).await { + Either::Left((_abort_received, _)) => Err(Error::Oracle), + Either::Right((Ok(status), _)) if status.success() => Ok(()), + Either::Right((Ok(status), _)) => { + Err(Error::Runtime(format!("{status}"))) } - match self.abort_recv.try_recv() { - Ok(()) => return Ok(()), - Err(TryRecvError::Empty) => {} - Err(TryRecvError::Closed) => return Err(Error::Oracle), + Either::Right((Err(err), _)) => { + Err(Error::Runtime(format!("{err}"))) } } } From c779354632f21a0d29151b269ad740d526fc4798 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 12 Aug 2022 12:13:43 +0100 Subject: [PATCH 0675/1995] Permit transfers between wrapped ERC20 balances --- shared/src/ledger/eth_bridge/storage/mod.rs | 41 ++ .../eth_bridge/storage/wrapped_erc20s.rs | 278 ++++++++++- shared/src/ledger/eth_bridge/vp.rs | 52 --- shared/src/ledger/eth_bridge/vp/authorize.rs | 36 ++ shared/src/ledger/eth_bridge/vp/mod.rs | 433 ++++++++++++++++++ shared/src/ledger/eth_bridge/vp/store.rs | 92 ++++ shared/src/types/ethereum_events.rs | 6 + 7 files changed, 875 insertions(+), 63 deletions(-) delete mode 100644 shared/src/ledger/eth_bridge/vp.rs create mode 100644 shared/src/ledger/eth_bridge/vp/authorize.rs create mode 100644 shared/src/ledger/eth_bridge/vp/mod.rs create mode 100644 shared/src/ledger/eth_bridge/vp/store.rs diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 7377e423d0..5cfdbf0efb 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -10,3 +10,44 @@ use crate::types::storage::{Key, KeySeg}; pub fn prefix() -> Key { Key::from(ADDRESS.to_db_key()) } + +/// Returns whether a key belongs to this account or not +pub fn is_eth_bridge_key(key: &Key) -> bool { + matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::types::address; + + #[test] + fn test_is_eth_bridge_key_returns_true_for_eth_bridge_address() { + let key = Key::from(super::ADDRESS.to_db_key()); + assert!(is_eth_bridge_key(&key)); + } + + #[test] + fn test_is_eth_bridge_key_returns_true_for_eth_bridge_subkey() { + let key = Key::from(super::ADDRESS.to_db_key()) + .push(&"arbitrary key segment".to_owned()) + .expect("Could not set up test"); + assert!(is_eth_bridge_key(&key)); + } + + #[test] + fn test_is_eth_bridge_key_returns_false_for_different_address() { + let key = + Key::from(address::testing::established_address_1().to_db_key()); + assert!(!is_eth_bridge_key(&key)); + } + + #[test] + fn test_is_eth_bridge_key_returns_false_for_different_address_subkey() { + let key = + Key::from(address::testing::established_address_1().to_db_key()) + .push(&"arbitrary key segment".to_owned()) + .expect("Could not set up test"); + assert!(!is_eth_bridge_key(&key)); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index c308395bc7..6a6bc25f31 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -1,16 +1,20 @@ //! Functionality for accessing the multitoken subspace +use std::str::FromStr; + +use eyre::eyre; + use crate::types::address::Address; use crate::types::ethereum_events::EthAddress; -use crate::types::storage::Key; +use crate::types::storage::{self, DbKeySeg}; #[allow(missing_docs)] -pub const PREFIX_KEY_SEGMENT: &str = "ERC20"; +pub const MULTITOKEN_KEY_SEGMENT: &str = "ERC20"; /// Get the key prefix corresponding to the storage subspace that holds wrapped /// ERC20 tokens -pub fn prefix() -> Key { +pub fn prefix() -> storage::Key { super::prefix() - .push(&PREFIX_KEY_SEGMENT.to_owned()) + .push(&MULTITOKEN_KEY_SEGMENT.to_owned()) .expect("should always be able to construct this key") } @@ -21,13 +25,13 @@ const SUPPLY_KEY_SEGMENT: &str = "supply"; pub struct Keys { /// The prefix of keys under which the details for a specific ERC20 token /// are stored - pub prefix: Key, + prefix: storage::Key, } impl Keys { /// Get the `balance` key for a specific owner - there should be a /// [`crate::types::token::Amount`] stored here - pub fn balance(&self, owner: &Address) -> Key { + pub fn balance(&self, owner: &Address) -> storage::Key { self.prefix .push(&BALANCE_KEY_SEGMENT.to_owned()) .expect("should always be able to construct this key") @@ -37,7 +41,7 @@ impl Keys { /// Get the `supply` key - there should be a /// [`crate::types::token::Amount`] stored here - pub fn supply(&self) -> Key { + pub fn supply(&self) -> storage::Key { self.prefix .push(&SUPPLY_KEY_SEGMENT.to_owned()) .expect("should always be able to construct this key") @@ -54,8 +58,130 @@ impl From<&EthAddress> for Keys { } } +/// Represents the type of a key relating to a wrapped ERC20 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub enum KeyType { + /// The key holds a wrapped ERC20 balance + Balance { + /// The owner of the balance + owner: Address, + }, + /// A type of key which tracks the total supply of some wrapped ERC20 + Supply, +} + +/// Represents a key relating to a wrapped ERC20 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct Key { + /// The specific ERC20 as identified by its Ethereum address + pub asset: EthAddress, + /// The type of this key + pub suffix: KeyType, +} + +impl From<&Key> for storage::Key { + fn from(mt_key: &Key) -> Self { + let keys = Keys::from(&mt_key.asset); + match &mt_key.suffix { + KeyType::Balance { owner } => keys.balance(owner), + KeyType::Supply => keys.supply(), + } + } +} + +fn has_erc20_segment(key: &storage::Key) -> bool { + matches!( + key.segments.get(1), + Some(segment) if segment == &DbKeySeg::StringSeg(MULTITOKEN_KEY_SEGMENT.to_owned()), + ) +} + +impl TryFrom<&storage::Key> for Key { + type Error = eyre::Error; + + fn try_from(key: &storage::Key) -> Result { + if !super::is_eth_bridge_key(key) { + return Err(eyre!("key does not belong to the EthBridge")); + } + if !has_erc20_segment(key) { + return Err(eyre!("key does not have ERC20 segment")); + } + + let asset = match key.segments.get(2) { + Some(segment) => match segment { + DbKeySeg::StringSeg(segment) => EthAddress::from_str(segment)?, + _ => { + return Err(eyre!( + "key has unrecognized segment at index #2, expected \ + an Ethereum address" + )); + } + }, + None => { + return Err(eyre!( + "key has no segment at index #2, expected an Ethereum \ + address" + )); + } + }; + + let segment_3 = match key.segments.get(3) { + Some(segment) => match segment { + DbKeySeg::StringSeg(segment) => segment.to_owned(), + _ => { + return Err(eyre!( + "key has unrecognized segment at index #3, expected a \ + string segment" + )); + } + }, + None => { + return Err(eyre!( + "key has no segment at index #3, expected a string segment" + )); + } + }; + + match segment_3.as_str() { + SUPPLY_KEY_SEGMENT => { + let supply_key = Key { + asset, + suffix: KeyType::Supply, + }; + Ok(supply_key) + } + BALANCE_KEY_SEGMENT => { + let owner = match key.segments.get(4) { + Some(segment) => match segment { + DbKeySeg::AddressSeg(address) => address.to_owned(), + DbKeySeg::StringSeg(_) => { + return Err(eyre!( + "key has string segment at index #4, expected \ + an address segment" + )); + } + }, + None => { + return Err(eyre!( + "key has no segment at index #4, expected an \ + address segment" + )); + } + }; + let balance_key = Key { + asset, + suffix: KeyType::Balance { owner }, + }; + Ok(balance_key) + } + _ => Err(eyre!("key has unrecognized string segment at index #3")), + } + } +} + #[cfg(test)] mod test { + use std::result::Result; use std::str::FromStr; use super::*; @@ -77,7 +203,7 @@ mod test { DbKeySeg::AddressSeg(multitoken_addr), DbKeySeg::StringSeg(multitoken_path), ] if multitoken_addr == &ADDRESS && - multitoken_path == PREFIX_KEY_SEGMENT + multitoken_path == MULTITOKEN_KEY_SEGMENT ) } @@ -91,7 +217,7 @@ mod test { DbKeySeg::StringSeg(multitoken_path), DbKeySeg::StringSeg(token_id), ] if multitoken_addr == &ADDRESS && - multitoken_path == PREFIX_KEY_SEGMENT && + multitoken_path == MULTITOKEN_KEY_SEGMENT && token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() ) } @@ -110,7 +236,7 @@ mod test { DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::AddressSeg(owner_addr), ] if multitoken_addr == &ADDRESS && - multitoken_path == PREFIX_KEY_SEGMENT && + multitoken_path == MULTITOKEN_KEY_SEGMENT && token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && balance_key_seg == BALANCE_KEY_SEGMENT && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() @@ -140,7 +266,7 @@ mod test { DbKeySeg::StringSeg(token_id), DbKeySeg::StringSeg(supply_key_seg), ] if multitoken_addr == &ADDRESS && - multitoken_path == PREFIX_KEY_SEGMENT && + multitoken_path == MULTITOKEN_KEY_SEGMENT && token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && supply_key_seg == SUPPLY_KEY_SEGMENT ) @@ -155,4 +281,134 @@ mod test { key.to_string(), ) } + + #[test] + fn test_from_multitoken_key_for_key() { + // supply key + let wdai_supply = Key { + asset: DAI_ERC20_ETH_ADDRESS, + suffix: KeyType::Supply, + }; + let key: storage::Key = (&wdai_supply).into(); + assert_matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + DbKeySeg::StringSeg(token_id), + DbKeySeg::StringSeg(supply_key_seg), + ] if multitoken_addr == &ADDRESS && + multitoken_path == MULTITOKEN_KEY_SEGMENT && + token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && + supply_key_seg == SUPPLY_KEY_SEGMENT + ); + + // balance key + let wdai_balance = Key { + asset: DAI_ERC20_ETH_ADDRESS, + suffix: KeyType::Balance { + owner: Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap(), + }, + }; + let key: storage::Key = (&wdai_balance).into(); + assert_matches!( + &key.segments[..], + [ + DbKeySeg::AddressSeg(multitoken_addr), + DbKeySeg::StringSeg(multitoken_path), + DbKeySeg::StringSeg(token_id), + DbKeySeg::StringSeg(balance_key_seg), + DbKeySeg::AddressSeg(owner_addr), + ] if multitoken_addr == &ADDRESS && + multitoken_path == MULTITOKEN_KEY_SEGMENT && + token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && + balance_key_seg == BALANCE_KEY_SEGMENT && + owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() + ); + } + + #[test] + fn test_try_from_key_for_multitoken_key_supply() { + // supply key + let key = storage::Key::from_str(&format!( + "#{}/ERC20/{}/supply", + ADDRESS, + DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + )) + .expect("Should be able to construct key for test"); + + let result: Result = Key::try_from(&key); + + let mt_key = match result { + Ok(mt_key) => mt_key, + Err(error) => { + panic!( + "Could not convert key {:?} to MultitokenKey: {:?}", + key, error + ) + } + }; + + assert_eq!(mt_key.asset, DAI_ERC20_ETH_ADDRESS); + assert_eq!(mt_key.suffix, KeyType::Supply); + } + + #[test] + fn test_try_from_key_for_multitoken_key_balance() { + // supply key + let key = storage::Key::from_str(&format!( + "#{}/ERC20/{}/balance/#{}", + ADDRESS, + DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + ARBITRARY_OWNER_ADDRESS + )) + .expect("Should be able to construct key for test"); + + let result: Result = Key::try_from(&key); + + let mt_key = match result { + Ok(mt_key) => mt_key, + Err(error) => { + panic!( + "Could not convert key {:?} to MultitokenKey: {:?}", + key, error + ) + } + }; + + assert_eq!(mt_key.asset, DAI_ERC20_ETH_ADDRESS); + assert_eq!( + mt_key.suffix, + KeyType::Balance { + owner: Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap() + } + ); + } + + #[test] + fn test_has_erc20_segment() { + let key = storage::Key::from_str(&format!( + "#{}/ERC20/{}/balance/#{}", + ADDRESS, + DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + ARBITRARY_OWNER_ADDRESS + )) + .expect("Should be able to construct key for test"); + + assert!(has_erc20_segment(&key)); + + let key = storage::Key::from_str(&format!( + "#{}/ERC20/{}/supply", + ADDRESS, + DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + )) + .expect("Should be able to construct key for test"); + + assert!(has_erc20_segment(&key)); + + let key = storage::Key::from_str(&format!("#{}/ERC20", ADDRESS)) + .expect("Should be able to construct key for test"); + + assert!(has_erc20_segment(&key)); + } } diff --git a/shared/src/ledger/eth_bridge/vp.rs b/shared/src/ledger/eth_bridge/vp.rs deleted file mode 100644 index 02d21f37b0..0000000000 --- a/shared/src/ledger/eth_bridge/vp.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Validity predicate for the Ethereum bridge - -use std::collections::BTreeSet; - -use crate::ledger::native_vp::{Ctx, NativeVp}; -use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::StorageHasher; -use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Key; -use crate::vm::WasmCacheAccess; - -/// Validity predicate for the Ethereum bridge -pub struct EthBridge<'ctx, DB, H, CA> -where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// Context to interact with the host structures. - pub ctx: Ctx<'ctx, DB, H, CA>, -} - -#[derive(thiserror::Error, Debug)] -#[error(transparent)] -/// Generic error that may be returned by the validity predicate -pub struct Error(#[from] eyre::Error); - -impl<'a, DB, H, CA> NativeVp for EthBridge<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - - const ADDR: InternalAddress = super::INTERNAL_ADDRESS; - - fn validate_tx( - &self, - _tx_data: &[u8], - _keys_changed: &BTreeSet, - _verifiers: &BTreeSet
, - ) -> Result { - tracing::debug!( - tx_data_len = _tx_data.len(), - keys_changed_len = _keys_changed.len(), - verifiers_len = _verifiers.len(), - "Validity predicate triggered", - ); - Ok(false) - } -} diff --git a/shared/src/ledger/eth_bridge/vp/authorize.rs b/shared/src/ledger/eth_bridge/vp/authorize.rs new file mode 100644 index 0000000000..5e38f4d1bd --- /dev/null +++ b/shared/src/ledger/eth_bridge/vp/authorize.rs @@ -0,0 +1,36 @@ +//! Functionality to do with checking whether a transaction is authorized by the +//! "owner" of some key under this account +use eyre::Result; + +use super::store; +use crate::types::address::Address; + +pub(super) fn is_authorized( + _reader: impl store::Reader, + _tx_data: &[u8], + _owner: &Address, +) -> Result { + tracing::warn!( + "authorize::is_authorized is not implemented, so all transfers are \ + authorized" + ); + Ok(true) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::address; + + #[test] + fn test_is_authorized_established_address() -> Result<()> { + let reader = store::testing::FakeReader::default(); + let tx_data = vec![]; + let owner = address::testing::established_address_1(); + + let authorized = is_authorized(reader, &tx_data, &owner)?; + + assert!(authorized); + Ok(()) + } +} diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs new file mode 100644 index 0000000000..b5b4f83334 --- /dev/null +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -0,0 +1,433 @@ +//! Validity predicate for the Ethereum bridge + +mod authorize; +mod store; + +use std::collections::{BTreeSet, HashSet}; + +use eyre::{eyre, Result}; +use itertools::Itertools; + +use self::store::Reader; +use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; +use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::ledger::storage as ledger_storage; +use crate::ledger::storage::StorageHasher; +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::Key; +use crate::types::token::Amount; +use crate::vm::WasmCacheAccess; + +/// Validity predicate for the Ethereum bridge +pub struct EthBridge<'ctx, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'ctx, DB, H, CA>, +} + +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error that may be returned by the validity predicate +pub struct Error(#[from] eyre::Error); + +impl<'a, DB, H, CA> NativeVp for EthBridge<'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + const ADDR: InternalAddress = super::INTERNAL_ADDRESS; + + /// Validate that a wasm transaction is permitted to change keys under this + /// account. + /// + /// We permit only the following changes via wasm for the time being: + /// - a wrapped ERC20's supply key to decrease iff one of its balance keys + /// decreased by the same amount + /// - a wrapped ERC20's balance key to decrease iff another one of its + /// balance keys increased by the same amount + /// + /// Some other changes to the storage subspace of this account are expected + /// to happen natively i.e. bypassing this validity predicate. For example, + /// changes to the `eth_msgs/...` keys. For those cases, we reject here as + /// no wasm transactions should be able to modify those keys. + fn validate_tx( + &self, + tx_data: &[u8], + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, + ) -> Result { + tracing::debug!( + tx_data_len = tx_data.len(), + keys_changed_len = keys_changed.len(), + verifiers_len = verifiers.len(), + "Validity predicate triggered", + ); + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { + Some((key_a, key_b)) => (key_a, key_b), + None => return Ok(false), + }; + let sender = match extract_valid_sender(&self.ctx, key_a, key_b)? { + Some(sender) => sender, + None => return Ok(false), + }; + let authed = authorize::is_authorized(&self.ctx, tx_data, &sender)?; + Ok(authed) + } +} + +/// If `keys_changed` represents a valid set of changed keys, return them, +/// otherwise return `None`. +fn extract_valid_keys_changed( + keys_changed: &BTreeSet, +) -> Result, Error> { + // we aren't concerned with keys that changed outside of our account + let keys_changed: HashSet<_> = keys_changed + .iter() + .filter(|key| storage::is_eth_bridge_key(key)) + .collect(); + if keys_changed.is_empty() { + return Err(Error(eyre!( + "No keys changed under our account so this validity predicate \ + shouldn't have been triggered" + ))); + } + tracing::debug!( + relevant_keys.len = keys_changed.len(), + "Found keys changed under our account" + ); + + if keys_changed.len() != 2 { + tracing::debug!( + relevant_keys.len = keys_changed.len(), + "Rejecting transaction as only two keys should have changed" + ); + return Ok(None); + } + + let mut keys = HashSet::<_>::default(); + for key in keys_changed.into_iter() { + let key = match wrapped_erc20s::Key::try_from(key) { + Ok(key) => { + // Until burning is implemented, we disallow changes to any + // supply keys via wasm transactions + if matches!(key.suffix, wrapped_erc20s::KeyType::Supply) { + tracing::debug!( + ?key, + "Rejecting transaction as key is a supply key" + ); + return Ok(None); + } + key + } + Err(error) => { + tracing::debug!( + %key, + ?error, + "Rejecting transaction as key is not a wrapped ERC20 key" + ); + return Ok(None); + } + }; + keys.insert(key); + } + + // We can .unwrap() here as we know for sure that this set has len=2 + let (key_a, key_b) = keys.into_iter().collect_tuple().unwrap(); + if key_a.asset != key_b.asset { + tracing::debug!( + ?key_a, + ?key_b, + "Rejecting transaction as keys are for different assets" + ); + return Ok(None); + } + Ok(Some((key_a, key_b))) +} + +/// Returns the `Address` which should be authorizing the balance change, or +/// `None` if the balance change is invalid. +fn extract_valid_sender( + reader: impl Reader, + key_a: wrapped_erc20s::Key, + key_b: wrapped_erc20s::Key, +) -> Result> { + let (balance_a, balance_b) = + match (key_a.suffix.clone(), key_b.suffix.clone()) { + ( + wrapped_erc20s::KeyType::Balance { .. }, + wrapped_erc20s::KeyType::Balance { .. }, + ) => (Key::from(&key_a), Key::from(&key_b)), + ( + wrapped_erc20s::KeyType::Balance { .. }, + wrapped_erc20s::KeyType::Supply, + ) + | ( + wrapped_erc20s::KeyType::Supply, + wrapped_erc20s::KeyType::Balance { .. }, + ) => { + tracing::debug!( + ?key_a, + ?key_b, + "Rejecting transaction that is attempting to change a \ + supply key" + ); + return Ok(None); + } + ( + wrapped_erc20s::KeyType::Supply, + wrapped_erc20s::KeyType::Supply, + ) => { + // in theory, this should be unreachable!() as we would have + // already rejected if both supply keys were for + // the same asset + tracing::debug!( + ?key_a, + ?key_b, + "Rejecting transaction that is attempting to change two \ + supply keys" + ); + return Ok(None); + } + }; + let balance_a_pre = match reader.read_pre::(&balance_a)? { + Some(value) => value, + None => Amount::from(0), + } + .change(); + let balance_a_post = match reader.read_post::(&balance_a)? { + Some(value) => value, + None => { + tracing::debug!( + ?balance_a, + "Rejecting transaction as could not read_post balance key" + ); + return Ok(None); + } + } + .change(); + let balance_b_pre = match reader.read_pre::(&balance_b)? { + Some(value) => value, + None => Amount::from(0), + } + .change(); + let balance_b_post = match reader.read_post::(&balance_b)? { + Some(value) => value, + None => { + tracing::debug!( + ?balance_b, + "Rejecting transaction as could not read_post balance key" + ); + return Ok(None); + } + } + .change(); + + let balance_a_delta = calculate_delta(balance_a_pre, balance_a_post)?; + let balance_b_delta = calculate_delta(balance_b_pre, balance_b_post)?; + if balance_a_delta != -balance_b_delta { + tracing::debug!( + ?balance_a_pre, + ?balance_b_pre, + ?balance_a_post, + ?balance_b_post, + ?balance_a_delta, + ?balance_b_delta, + "Rejecting transaction as balance changes do not match" + ); + return Ok(None); + } + if balance_a_delta == 0 { + assert_eq!(balance_b_delta, 0); + tracing::debug!("Rejecting transaction as no balance change"); + return Ok(None); + } + if balance_a_post < 0 { + tracing::debug!( + ?balance_a_post, + "Rejecting transaction as balance is negative" + ); + return Ok(None); + } + if balance_b_post < 0 { + tracing::debug!( + ?balance_b_post, + "Rejecting transaction as balance is negative" + ); + return Ok(None); + } + + if balance_a_delta < 0 { + if let wrapped_erc20s::KeyType::Balance { owner } = key_a.suffix { + Ok(Some(owner)) + } else { + unreachable!() + } + } else { + assert!(balance_b_delta < 0); + if let wrapped_erc20s::KeyType::Balance { owner } = key_b.suffix { + Ok(Some(owner)) + } else { + unreachable!() + } + } +} + +/// Return the delta between `balance_pre` and `balance_post`, erroring if there +/// is an underflow +fn calculate_delta(balance_pre: i128, balance_post: i128) -> Result { + match balance_post.checked_sub(balance_pre) { + Some(result) => Ok(result), + None => Err(eyre!( + "Underflow while calculating delta: {} - {}", + balance_post, + balance_pre + )), + } +} + +#[cfg(test)] +mod tests { + use rand::Rng; + + use super::*; + use crate::types::ethereum_events; + + const ARBITRARY_OWNER_A_ADDRESS: &str = + "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; + const ARBITRARY_OWNER_B_ADDRESS: &str = + "atest1v4ehgw36xuunwd6989prwdfkxqmnvsfjxs6nvv6xxucrs3f3xcmns3fcxdzrvvz9xverzvzr56le8f"; + + /// Return some arbitrary random key belonging to this account + fn arbitrary_key() -> Key { + let mut rng = rand::thread_rng(); + let rn = rng.gen::(); + storage::prefix() + .push(&format!("arbitrary key segment {}", rn)) + .expect("should always be able to construct this key") + } + + #[test] + fn test_error_if_triggered_without_keys_changed() { + let keys_changed = BTreeSet::new(); + + let result = extract_valid_keys_changed(&keys_changed); + + assert!(result.is_err()); + } + + #[test] + fn test_rejects_if_not_two_keys_changed() { + { + let keys_changed = BTreeSet::from_iter(vec![arbitrary_key()]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + { + let keys_changed = BTreeSet::from_iter(vec![ + arbitrary_key(), + arbitrary_key(), + arbitrary_key(), + ]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + } + + #[test] + fn test_rejects_if_not_two_multitoken_keys_changed() { + { + let keys_changed = + BTreeSet::from_iter(vec![arbitrary_key(), arbitrary_key()]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + + { + let keys_changed = BTreeSet::from_iter(vec![ + arbitrary_key(), + wrapped_erc20s::Keys::from( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ) + .supply(), + ]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + + { + let keys_changed = BTreeSet::from_iter(vec![ + arbitrary_key(), + wrapped_erc20s::Keys::from( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ) + .balance( + &Address::decode(ARBITRARY_OWNER_A_ADDRESS) + .expect("Couldn't set up test"), + ), + ]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + } + + #[test] + fn test_rejects_if_multitoken_keys_for_different_assets() { + { + let keys_changed = BTreeSet::from_iter(vec![ + wrapped_erc20s::Keys::from( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ) + .balance( + &Address::decode(ARBITRARY_OWNER_A_ADDRESS) + .expect("Couldn't set up test"), + ), + wrapped_erc20s::Keys::from( + ðereum_events::testing::USDC_ERC20_ETH_ADDRESS, + ) + .balance( + &Address::decode(ARBITRARY_OWNER_B_ADDRESS) + .expect("Couldn't set up test"), + ), + ]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + } + + #[test] + fn test_rejects_if_supply_key_changed() { + let asset = ðereum_events::testing::DAI_ERC20_ETH_ADDRESS; + { + let keys_changed = BTreeSet::from_iter(vec![ + wrapped_erc20s::Keys::from(asset).supply(), + wrapped_erc20s::Keys::from(asset).balance( + &Address::decode(ARBITRARY_OWNER_B_ADDRESS) + .expect("Couldn't set up test"), + ), + ]); + + let result = extract_valid_keys_changed(&keys_changed); + + assert_matches!(result, Ok(None)); + } + } +} diff --git a/shared/src/ledger/eth_bridge/vp/store.rs b/shared/src/ledger/eth_bridge/vp/store.rs new file mode 100644 index 0000000000..fc6c3996fe --- /dev/null +++ b/shared/src/ledger/eth_bridge/vp/store.rs @@ -0,0 +1,92 @@ +//! Functionality for reading from storage in order to validate transactions + +use borsh::BorshDeserialize; +use eyre::{Context, Result}; + +use crate::ledger::native_vp::Ctx; +use crate::ledger::storage as ledger_storage; +use crate::ledger::storage::StorageHasher; +use crate::types::storage::Key; +use crate::vm::WasmCacheAccess; + +/// Read pre/post storage +pub(super) trait Reader { + /// Storage read prior state (before tx execution). It will try to read from + /// the storage. + fn read_pre(&self, key: &Key) -> Result>; + + /// 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. + fn read_post(&self, key: &Key) -> Result>; + + /// If `maybe_bytes` is not empty, return an `Option` containing the + /// deserialization of the bytes inside `maybe_bytes`. + fn deserialize_if_present( + maybe_bytes: Option>, + ) -> Result> { + let bytes = match maybe_bytes { + Some(bytes) => bytes, + None => return Ok(None), + }; + let deserialized = T::try_from_slice(&bytes) + .wrap_err_with(|| "couldn't deserialize".to_string())?; + Ok(Some(deserialized)) + } +} + +impl<'ctx, DB, H, CA> Reader for &Ctx<'ctx, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> + 'static, + H: StorageHasher + 'static, + CA: 'static + WasmCacheAccess, +{ + fn read_pre(&self, key: &Key) -> Result> { + let x = Ctx::read_pre(self, key) + .wrap_err_with(|| format!("couldn't read_pre {}", key))?; + Self::deserialize_if_present(x) + } + + fn read_post(&self, key: &Key) -> Result> { + let x = Ctx::read_post(self, key) + .wrap_err_with(|| format!("couldn't read_post {}", key))?; + Self::deserialize_if_present(x) + } +} + +#[cfg(any(test, feature = "testing"))] +pub(super) mod testing { + use std::collections::HashMap; + + use super::*; + + #[derive(Debug, Default)] + pub(in super::super) struct FakeReader { + pre: HashMap>, + post: HashMap>, + } + + impl Reader for FakeReader { + fn read_pre( + &self, + key: &Key, + ) -> Result> { + let bytes = match self.pre.get(key) { + Some(bytes) => bytes.to_owned(), + None => return Ok(None), + }; + Self::deserialize_if_present(Some(bytes)) + } + + fn read_post( + &self, + key: &Key, + ) -> Result> { + let bytes = match self.post.get(key) { + Some(bytes) => bytes.to_owned(), + None => return Ok(None), + }; + Self::deserialize_if_present(Some(bytes)) + } + } +} diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 869b4a4f14..98d031e8bf 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -291,4 +291,10 @@ pub mod testing { 107, 23, 84, 116, 232, 144, 148, 196, 77, 169, 139, 149, 78, 237, 234, 196, 149, 39, 29, 15, ]); + pub const USDC_ERC20_ETH_ADDRESS_CHECKSUMMED: &str = + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + pub const USDC_ERC20_ETH_ADDRESS: EthAddress = EthAddress([ + 160, 184, 105, 145, 198, 33, 139, 54, 193, 209, 157, 74, 46, 158, 176, + 206, 54, 6, 235, 72, + ]); } From 8009e0ae4b18ad335b60082860fdcdb49572c44a Mon Sep 17 00:00:00 2001 From: James Date: Wed, 7 Sep 2022 15:36:52 +0100 Subject: [PATCH 0676/1995] Update shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs Co-authored-by: Jacob Turner --- .../eth_bridge/storage/wrapped_erc20s.rs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 6a6bc25f31..9175016549 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -125,21 +125,12 @@ impl TryFrom<&storage::Key> for Key { } }; - let segment_3 = match key.segments.get(3) { - Some(segment) => match segment { - DbKeySeg::StringSeg(segment) => segment.to_owned(), - _ => { - return Err(eyre!( - "key has unrecognized segment at index #3, expected a \ - string segment" - )); - } - }, - None => { - return Err(eyre!( - "key has no segment at index #3, expected a string segment" - )); - } + let segment_3 = if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { + segment.to_owned() + } else { + return Err(eyre!( + "key has an incorrect segment at index #3, expected a string segment" + )); }; match segment_3.as_str() { From c774700cd73f0ddfe380d1d3265fc536f530a489 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 7 Sep 2022 15:37:07 +0100 Subject: [PATCH 0677/1995] Update shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs Co-authored-by: Jacob Turner --- .../eth_bridge/storage/wrapped_erc20s.rs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 9175016549..cdbd82cd7f 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -107,22 +107,12 @@ impl TryFrom<&storage::Key> for Key { return Err(eyre!("key does not have ERC20 segment")); } - let asset = match key.segments.get(2) { - Some(segment) => match segment { - DbKeySeg::StringSeg(segment) => EthAddress::from_str(segment)?, - _ => { - return Err(eyre!( - "key has unrecognized segment at index #2, expected \ - an Ethereum address" - )); - } - }, - None => { - return Err(eyre!( - "key has no segment at index #2, expected an Ethereum \ - address" - )); - } + let asset = if let Some(DbKeySeg::StringSe(segment) = key.segments.get(2) { + EthAddress:from_str(segment)? + } else { + return Err(eyre!( + "key has an incorrect segment at index #2, expected an Ethereum address" + )); }; let segment_3 = if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { From 7f8a9fc74e16f061f03565899560aea142229326 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 7 Sep 2022 15:37:42 +0100 Subject: [PATCH 0678/1995] Update shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs Co-authored-by: Jacob Turner --- .../eth_bridge/storage/wrapped_erc20s.rs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index cdbd82cd7f..b4f6d262d6 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -132,22 +132,12 @@ impl TryFrom<&storage::Key> for Key { Ok(supply_key) } BALANCE_KEY_SEGMENT => { - let owner = match key.segments.get(4) { - Some(segment) => match segment { - DbKeySeg::AddressSeg(address) => address.to_owned(), - DbKeySeg::StringSeg(_) => { - return Err(eyre!( - "key has string segment at index #4, expected \ - an address segment" - )); - } - }, - None => { - return Err(eyre!( - "key has no segment at index #4, expected an \ - address segment" - )); - } + let owner = if let Some(DbKeySeg::AddressSeg(address)) = key.segments.get(4) { + address.to_owned() + } else { + return Err(eyre!( + "key has an incorrect segment at index #4, expected an address segment" + )) }; let balance_key = Key { asset, From fc943335bb9e9c75b8770d86e89540b0cb036410 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 7 Sep 2022 15:38:05 +0100 Subject: [PATCH 0679/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: Jacob Turner --- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index b5b4f83334..c6e9abbc7d 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -67,7 +67,7 @@ where tx_data_len = tx_data.len(), keys_changed_len = keys_changed.len(), verifiers_len = verifiers.len(), - "Validity predicate triggered", + "Ethereum Bridge VP triggered", ); let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), From 5c2afdd7efca96d81d6505f160e6c7218f6e1ff9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 15:43:10 +0100 Subject: [PATCH 0680/1995] Use .unwrap_or_default() for handling uninitialized balances --- shared/src/ledger/eth_bridge/vp/mod.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index c6e9abbc7d..2f1259735f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -196,11 +196,10 @@ fn extract_valid_sender( return Ok(None); } }; - let balance_a_pre = match reader.read_pre::(&balance_a)? { - Some(value) => value, - None => Amount::from(0), - } - .change(); + let balance_a_pre = reader + .read_pre::(&balance_a)? + .unwrap_or_default() + .change(); let balance_a_post = match reader.read_post::(&balance_a)? { Some(value) => value, None => { @@ -212,11 +211,10 @@ fn extract_valid_sender( } } .change(); - let balance_b_pre = match reader.read_pre::(&balance_b)? { - Some(value) => value, - None => Amount::from(0), - } - .change(); + let balance_b_pre = reader + .read_pre::(&balance_b)? + .unwrap_or_default() + .change(); let balance_b_post = match reader.read_post::(&balance_b)? { Some(value) => value, None => { From feadb5c15666a95087fa82447d157e6ec0601322 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 15:44:23 +0100 Subject: [PATCH 0681/1995] Fix a compilation error --- .../eth_bridge/storage/wrapped_erc20s.rs | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index b4f6d262d6..a3f4df81ca 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -107,21 +107,25 @@ impl TryFrom<&storage::Key> for Key { return Err(eyre!("key does not have ERC20 segment")); } - let asset = if let Some(DbKeySeg::StringSe(segment) = key.segments.get(2) { - EthAddress:from_str(segment)? - } else { - return Err(eyre!( - "key has an incorrect segment at index #2, expected an Ethereum address" - )); - }; - - let segment_3 = if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { - segment.to_owned() - } else { - return Err(eyre!( - "key has an incorrect segment at index #3, expected a string segment" - )); - }; + let asset = + if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(2) { + EthAddress::from_str(segment)? + } else { + return Err(eyre!( + "key has an incorrect segment at index #2, expected an \ + Ethereum address" + )); + }; + + let segment_3 = + if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { + segment.to_owned() + } else { + return Err(eyre!( + "key has an incorrect segment at index #3, expected a \ + string segment" + )); + }; match segment_3.as_str() { SUPPLY_KEY_SEGMENT => { @@ -132,12 +136,15 @@ impl TryFrom<&storage::Key> for Key { Ok(supply_key) } BALANCE_KEY_SEGMENT => { - let owner = if let Some(DbKeySeg::AddressSeg(address)) = key.segments.get(4) { + let owner = if let Some(DbKeySeg::AddressSeg(address)) = + key.segments.get(4) + { address.to_owned() } else { return Err(eyre!( - "key has an incorrect segment at index #4, expected an address segment" - )) + "key has an incorrect segment at index #4, expected \ + an address segment" + )); }; let balance_key = Key { asset, From fd1094612eab70c18edf18d9da2360fadc607cda Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 15:49:33 +0100 Subject: [PATCH 0682/1995] Use .transpose() in deserialize_if_present --- shared/src/ledger/eth_bridge/vp/store.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/store.rs b/shared/src/ledger/eth_bridge/vp/store.rs index fc6c3996fe..5cf3779498 100644 --- a/shared/src/ledger/eth_bridge/vp/store.rs +++ b/shared/src/ledger/eth_bridge/vp/store.rs @@ -25,13 +25,12 @@ pub(super) trait Reader { fn deserialize_if_present( maybe_bytes: Option>, ) -> Result> { - let bytes = match maybe_bytes { - Some(bytes) => bytes, - None => return Ok(None), - }; - let deserialized = T::try_from_slice(&bytes) - .wrap_err_with(|| "couldn't deserialize".to_string())?; - Ok(Some(deserialized)) + maybe_bytes + .map(|ref bytes| { + T::try_from_slice(bytes) + .wrap_err_with(|| "couldn't deserialize".to_string()) + }) + .transpose() } } From cec1888410d7b23c61b38d26f622c856f2955603 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 16:12:24 +0100 Subject: [PATCH 0683/1995] Rename extract_valid_sender -> check_balance_changes --- shared/src/ledger/eth_bridge/vp/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2f1259735f..93ebf53abb 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -73,7 +73,7 @@ where Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), }; - let sender = match extract_valid_sender(&self.ctx, key_a, key_b)? { + let sender = match check_balance_changes(&self.ctx, key_a, key_b)? { Some(sender) => sender, None => return Ok(false), }; @@ -151,9 +151,12 @@ fn extract_valid_keys_changed( Ok(Some((key_a, key_b))) } -/// Returns the `Address` which should be authorizing the balance change, or -/// `None` if the balance change is invalid. -fn extract_valid_sender( +/// Checks that the balances at both `key_a` and `key_b` have changed by some +/// amount, and that the changes balance each other out. If the balance changes +/// are invalid, the reason is logged and a `None` is returned. Otherwise, +/// return the `Address` of the owner of the balance which is decreasing, which +/// should be authorizing the balance change. +fn check_balance_changes( reader: impl Reader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, From 35094c88dd13d4b2662ebaa031efad1f20bc36eb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 8 Sep 2022 16:32:14 +0100 Subject: [PATCH 0684/1995] Refactor read_pre_value/read_post_value --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 16 +++++++-- shared/src/ledger/native_vp.rs | 34 ++++++++++++------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index e031d59e61..c5545e87ff 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -58,8 +58,20 @@ where /// associated with an address fn account_balance_delta(&self, address: &Address) -> Option { let account_key = balance_key(&xan(), address); - let before: Amount = self.ctx.read_pre_value(&account_key)?; - let after: Amount = self.ctx.read_post_value(&account_key)?; + let before: Amount = self + .ctx + .read_pre_value(&account_key) + .unwrap_or_else(|error| { + tracing::warn!(?error, %account_key, "reading pre value"); + None + })?; + let after: Amount = self + .ctx + .read_post_value(&account_key) + .unwrap_or_else(|error| { + tracing::warn!(?error, %account_key, "reading post value"); + None + })?; if before > after { Some(SignedAmount::Negative(before - after)) } else { diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 252ac8a49a..8f889df795 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -4,6 +4,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; use borsh::BorshDeserialize; +use eyre::Context; use thiserror::Error; use crate::ledger::gas::VpGasMeter; @@ -132,30 +133,39 @@ where .map_err(Error::ContextError) } + /// If `maybe_bytes` is not empty, return an `Option` containing the + /// deserialization of the bytes inside `maybe_bytes`. + fn deserialize_if_present( + maybe_bytes: Option>, + ) -> eyre::Result> { + maybe_bytes + .map(|ref bytes| { + T::try_from_slice(bytes) + .wrap_err_with(|| "couldn't deserialize".to_string()) + }) + .transpose() + } + /// Helper function. After reading posterior state, /// borsh deserialize to specified type - pub fn read_post_value(&self, key: &Key) -> Option + pub fn read_post_value(&self, key: &Key) -> eyre::Result> where T: BorshDeserialize, { - if let Ok(Some(bytes)) = self.read_post(key) { - ::try_from_slice(bytes.as_slice()).ok() - } else { - None - } + let maybe_bytes = Ctx::read_post(self, key) + .wrap_err_with(|| format!("couldn't read_post {}", key))?; + Self::deserialize_if_present(maybe_bytes) } /// Helper function. After reading prior state, /// borsh deserialize to specified type - pub fn read_pre_value(&self, key: &Key) -> Option + pub fn read_pre_value(&self, key: &Key) -> eyre::Result> where T: BorshDeserialize, { - if let Ok(Some(bytes)) = self.read_pre(key) { - ::try_from_slice(bytes.as_slice()).ok() - } else { - None - } + let maybe_bytes = Ctx::read_pre(self, key) + .wrap_err_with(|| format!("couldn't read_pre {}", key))?; + Self::deserialize_if_present(maybe_bytes) } /// Storage read temporary state (after tx execution). It will try to read From 635f68cbf39656c2390d784f28104a98e57ed991 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 8 Sep 2022 16:52:46 +0100 Subject: [PATCH 0685/1995] Continuing with refactor --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 12 +- shared/src/ledger/eth_bridge/vp/authorize.rs | 7 +- shared/src/ledger/eth_bridge/vp/mod.rs | 14 +- shared/src/ledger/eth_bridge/vp/store.rs | 91 ------------ shared/src/ledger/native_vp.rs | 133 +++++++++++++----- 5 files changed, 113 insertions(+), 144 deletions(-) delete mode 100644 shared/src/ledger/eth_bridge/vp/store.rs diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c5545e87ff..b05b4db7be 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -17,7 +17,7 @@ use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; -use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::{DBIter, StorageHasher, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; @@ -58,15 +58,13 @@ where /// associated with an address fn account_balance_delta(&self, address: &Address) -> Option { let account_key = balance_key(&xan(), address); - let before: Amount = self - .ctx + let before: Amount = (&self.ctx) .read_pre_value(&account_key) .unwrap_or_else(|error| { tracing::warn!(?error, %account_key, "reading pre value"); None })?; - let after: Amount = self - .ctx + let after: Amount = (&self.ctx) .read_post_value(&account_key) .unwrap_or_else(|error| { tracing::warn!(?error, %account_key, "reading post value"); @@ -129,11 +127,11 @@ where // but that will be a separate PR. let pending_key = get_pending_key(); let pending_pre: HashSet = - self.ctx.read_pre_value(&pending_key).ok_or(eyre!( + (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( "The bridge pool transfers are missing from storage" ))?; let pending_post: HashSet = - self.ctx.read_post_value(&pending_key).ok_or(eyre!( + (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( "The bridge pool transfers are missing from storage" ))?; if !pending_post.contains(&transfer) { diff --git a/shared/src/ledger/eth_bridge/vp/authorize.rs b/shared/src/ledger/eth_bridge/vp/authorize.rs index 5e38f4d1bd..c5435b09dd 100644 --- a/shared/src/ledger/eth_bridge/vp/authorize.rs +++ b/shared/src/ledger/eth_bridge/vp/authorize.rs @@ -2,11 +2,11 @@ //! "owner" of some key under this account use eyre::Result; -use super::store; +use crate::ledger::native_vp::StorageReader; use crate::types::address::Address; pub(super) fn is_authorized( - _reader: impl store::Reader, + _reader: impl StorageReader, _tx_data: &[u8], _owner: &Address, ) -> Result { @@ -20,11 +20,12 @@ pub(super) fn is_authorized( #[cfg(test)] mod tests { use super::*; + use crate::ledger::native_vp; use crate::types::address; #[test] fn test_is_authorized_established_address() -> Result<()> { - let reader = store::testing::FakeReader::default(); + let reader = native_vp::testing::FakeStorageReader::default(); let tx_data = vec![]; let owner = address::testing::established_address_1(); diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 93ebf53abb..cdcd1815ea 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -1,16 +1,14 @@ //! Validity predicate for the Ethereum bridge mod authorize; -mod store; use std::collections::{BTreeSet, HashSet}; use eyre::{eyre, Result}; use itertools::Itertools; -use self::store::Reader; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; -use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::StorageHasher; use crate::types::address::{Address, InternalAddress}; @@ -157,7 +155,7 @@ fn extract_valid_keys_changed( /// return the `Address` of the owner of the balance which is decreasing, which /// should be authorizing the balance change. fn check_balance_changes( - reader: impl Reader, + reader: impl StorageReader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, ) -> Result> { @@ -200,10 +198,10 @@ fn check_balance_changes( } }; let balance_a_pre = reader - .read_pre::(&balance_a)? + .read_pre_value::(&balance_a)? .unwrap_or_default() .change(); - let balance_a_post = match reader.read_post::(&balance_a)? { + let balance_a_post = match reader.read_post_value::(&balance_a)? { Some(value) => value, None => { tracing::debug!( @@ -215,10 +213,10 @@ fn check_balance_changes( } .change(); let balance_b_pre = reader - .read_pre::(&balance_b)? + .read_pre_value::(&balance_b)? .unwrap_or_default() .change(); - let balance_b_post = match reader.read_post::(&balance_b)? { + let balance_b_post = match reader.read_post_value::(&balance_b)? { Some(value) => value, None => { tracing::debug!( diff --git a/shared/src/ledger/eth_bridge/vp/store.rs b/shared/src/ledger/eth_bridge/vp/store.rs deleted file mode 100644 index 5cf3779498..0000000000 --- a/shared/src/ledger/eth_bridge/vp/store.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Functionality for reading from storage in order to validate transactions - -use borsh::BorshDeserialize; -use eyre::{Context, Result}; - -use crate::ledger::native_vp::Ctx; -use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::StorageHasher; -use crate::types::storage::Key; -use crate::vm::WasmCacheAccess; - -/// Read pre/post storage -pub(super) trait Reader { - /// Storage read prior state (before tx execution). It will try to read from - /// the storage. - fn read_pre(&self, key: &Key) -> Result>; - - /// 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. - fn read_post(&self, key: &Key) -> Result>; - - /// If `maybe_bytes` is not empty, return an `Option` containing the - /// deserialization of the bytes inside `maybe_bytes`. - fn deserialize_if_present( - maybe_bytes: Option>, - ) -> Result> { - maybe_bytes - .map(|ref bytes| { - T::try_from_slice(bytes) - .wrap_err_with(|| "couldn't deserialize".to_string()) - }) - .transpose() - } -} - -impl<'ctx, DB, H, CA> Reader for &Ctx<'ctx, DB, H, CA> -where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> + 'static, - H: StorageHasher + 'static, - CA: 'static + WasmCacheAccess, -{ - fn read_pre(&self, key: &Key) -> Result> { - let x = Ctx::read_pre(self, key) - .wrap_err_with(|| format!("couldn't read_pre {}", key))?; - Self::deserialize_if_present(x) - } - - fn read_post(&self, key: &Key) -> Result> { - let x = Ctx::read_post(self, key) - .wrap_err_with(|| format!("couldn't read_post {}", key))?; - Self::deserialize_if_present(x) - } -} - -#[cfg(any(test, feature = "testing"))] -pub(super) mod testing { - use std::collections::HashMap; - - use super::*; - - #[derive(Debug, Default)] - pub(in super::super) struct FakeReader { - pre: HashMap>, - post: HashMap>, - } - - impl Reader for FakeReader { - fn read_pre( - &self, - key: &Key, - ) -> Result> { - let bytes = match self.pre.get(key) { - Some(bytes) => bytes.to_owned(), - None => return Ok(None), - }; - Self::deserialize_if_present(Some(bytes)) - } - - fn read_post( - &self, - key: &Key, - ) -> Result> { - let bytes = match self.post.get(key) { - Some(bytes) => bytes.to_owned(), - None => return Ok(None), - }; - Self::deserialize_if_present(Some(bytes)) - } - } -} diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 8f889df795..1819cde903 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -133,41 +133,6 @@ where .map_err(Error::ContextError) } - /// If `maybe_bytes` is not empty, return an `Option` containing the - /// deserialization of the bytes inside `maybe_bytes`. - fn deserialize_if_present( - maybe_bytes: Option>, - ) -> eyre::Result> { - maybe_bytes - .map(|ref bytes| { - T::try_from_slice(bytes) - .wrap_err_with(|| "couldn't deserialize".to_string()) - }) - .transpose() - } - - /// Helper function. After reading posterior state, - /// borsh deserialize to specified type - pub fn read_post_value(&self, key: &Key) -> eyre::Result> - where - T: BorshDeserialize, - { - let maybe_bytes = Ctx::read_post(self, key) - .wrap_err_with(|| format!("couldn't read_post {}", key))?; - Self::deserialize_if_present(maybe_bytes) - } - - /// Helper function. After reading prior state, - /// borsh deserialize to specified type - pub fn read_pre_value(&self, key: &Key) -> eyre::Result> - where - T: BorshDeserialize, - { - let maybe_bytes = Ctx::read_pre(self, key) - .wrap_err_with(|| format!("couldn't read_pre {}", key))?; - Self::deserialize_if_present(maybe_bytes) - } - /// 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>> { @@ -336,3 +301,101 @@ where } } } + +/// A convenience trait for reading and automatically deserializing a value from +/// storage +pub trait StorageReader { + /// If `maybe_bytes` is not empty, return an `Option` containing the + /// deserialization of the bytes inside `maybe_bytes`. + fn deserialize_if_present( + maybe_bytes: Option>, + ) -> eyre::Result> { + maybe_bytes + .map(|ref bytes| { + T::try_from_slice(bytes) + .wrap_err_with(|| "couldn't deserialize".to_string()) + }) + .transpose() + } + + /// Storage read prior state (before tx execution). It will try to read from + /// the storage. + fn read_pre_value( + &self, + key: &Key, + ) -> eyre::Result>; + + /// 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. + fn read_post_value( + &self, + key: &Key, + ) -> eyre::Result>; +} + +impl<'a, DB, H, CA> StorageReader for &Ctx<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Helper function. After reading posterior state, + /// borsh deserialize to specified type + fn read_post_value(&self, key: &Key) -> eyre::Result> + where + T: BorshDeserialize, + { + let maybe_bytes = Ctx::read_post(self, key) + .wrap_err_with(|| format!("couldn't read_post {}", key))?; + Self::deserialize_if_present(maybe_bytes) + } + + /// Helper function. After reading prior state, + /// borsh deserialize to specified type + fn read_pre_value(&self, key: &Key) -> eyre::Result> + where + T: BorshDeserialize, + { + let maybe_bytes = Ctx::read_pre(self, key) + .wrap_err_with(|| format!("couldn't read_pre {}", key))?; + Self::deserialize_if_present(maybe_bytes) + } +} + +#[cfg(any(test, feature = "testing"))] +pub(super) mod testing { + use std::collections::HashMap; + + use super::*; + + #[derive(Debug, Default)] + pub(in super::super) struct FakeStorageReader { + pre: HashMap>, + post: HashMap>, + } + + impl StorageReader for FakeStorageReader { + fn read_pre_value( + &self, + key: &Key, + ) -> eyre::Result> { + let bytes = match self.pre.get(key) { + Some(bytes) => bytes.to_owned(), + None => return Ok(None), + }; + Self::deserialize_if_present(Some(bytes)) + } + + fn read_post_value( + &self, + key: &Key, + ) -> eyre::Result> { + let bytes = match self.post.get(key) { + Some(bytes) => bytes.to_owned(), + None => return Ok(None), + }; + Self::deserialize_if_present(Some(bytes)) + } + } +} From 51cb0130a3c6fa3d1fe0ad43004e546f03173373 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 11:16:51 +0100 Subject: [PATCH 0686/1995] Update apps/src/lib/node/ledger/ethereum_node/mod.rs Co-authored-by: James --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index c3defe85ec..4556f58eb3 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -214,7 +214,7 @@ pub mod eth_fullnode { futures::pin_mut!(child_proc); match future::select(&mut self.abort_recv, child_proc).await { - Either::Left((_abort_received, _)) => Err(Error::Oracle), + Either::Left(_) => Err(Error::Oracle), Either::Right((Ok(status), _)) if status.success() => Ok(()), Either::Right((Ok(status), _)) => { Err(Error::Runtime(format!("{status}"))) From 8bb5edf2acf96d24d4464cfa66b2f55f2036144b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Sep 2022 14:04:19 +0000 Subject: [PATCH 0687/1995] [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 a112d705c4..29ccd4fd2b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3d14e2d1686da9426e7ee302f161711d276f8f56b212828ad10eebfad771126a.wasm", - "tx_ibc.wasm": "tx_ibc.90bcb48e525be4946627ed50aa064721783e3cfafa1b85086fc42e8e40ec41e6.wasm", - "tx_init_account.wasm": "tx_init_account.c5fceffd000ba09c5baa57b89bcd77e41d748ebb76640b51ffeaab38bda1a741.wasm", - "tx_init_nft.wasm": "tx_init_nft.de0244a2aebeae80e6a5f597184a5954af11bad8bd0bbc6c379ce9c667e5ab42.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7bad024a9c110d398f6bf2e0dc4a24eb651f31cd805eaadca42eb22a1ffe79ef.wasm", - "tx_init_validator.wasm": "tx_init_validator.588f42b882e718cf24e3e4361e46cc26c65a2ef244a0ffc382b4c9e053233120.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.5963f9f1517db04d0447f873b4ce3575a18e82b6e6580d568b8695815c4bb58d.wasm", - "tx_transfer.wasm": "tx_transfer.cb50725fdcae3aab5eb62ca0e31f1e32ee685d575105036faf9c54ccc80f3cca.wasm", - "tx_unbond.wasm": "tx_unbond.aabd65bad5c4253ad58bd6010bd1f48581d7e2fb31d3a75adc6302589831e557.wasm", - "tx_update_vp.wasm": "tx_update_vp.ececb049c6bfeac4a8c1fdd57846731277f8be4b353da02221bc25c8f6c08307.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e225c5c8230f6b78e2c82bc87e52638667696100231158c01758eca0285657da.wasm", - "tx_withdraw.wasm": "tx_withdraw.05fc02b992823b781f241d1f23da68b8fed9365d71fc0ed31e94b7b01c039553.wasm", - "vp_nft.wasm": "vp_nft.aac80ee4b961f933eb893637b11fafb090bf9416b3347902f96e0860d0fbfbb3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9e2f3e02672e7995384e444931f801a3ea539ffed8bcbc13a5667fc0973d8fb0.wasm", - "vp_token.wasm": "vp_token.bdd12480ab0bb59f8ffe04a40ddef07885e7d76bec41bd011ca0025d2567205a.wasm", - "vp_user.wasm": "vp_user.5fe4630e9f67f4f7518c4d7f25e7428144898d1123fca394525f5b0528b3178f.wasm" + "tx_from_intent.wasm": "tx_from_intent.de38def08090ac1a912d24a1cf0dd03b0b1ad70eca06dcd6745af9c7175be450.wasm", + "tx_ibc.wasm": "tx_ibc.8fd090bdd02727239c63ff6a2a880124320d744972723315983ce855201c605e.wasm", + "tx_init_account.wasm": "tx_init_account.abfeeca2aeecdacf3ff3db022c9aaf85040b38164703a4ee7ad6e42afacdd961.wasm", + "tx_init_nft.wasm": "tx_init_nft.0be8aba244a529f8bb65692bbf0c939550ca9bdd00de6b8df68f1780dd8d7c2d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.67cc820c203ed6f379110c94b7ad16544c03a4d94c77fbbacba1c02dd0869b0b.wasm", + "tx_init_validator.wasm": "tx_init_validator.c9d864bac4cc3090b456f07deb956b2763d8c9fbda38762953398332182ac791.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.453eed7e258e8bda5ff3136ca89e90f3dcc601d1e62dd0991c1d18e404e96d73.wasm", + "tx_transfer.wasm": "tx_transfer.cdad1f1189e5a56aa9cbd542fc983397fd24172c99368623b28001b6c440d2e1.wasm", + "tx_unbond.wasm": "tx_unbond.7498c085a34e8507d46e4eebbd9dcb442d3431685aba15da406f3b74d214e4cf.wasm", + "tx_update_vp.wasm": "tx_update_vp.0e6fe53decf185f6c8715b70a610177c035827370fe472e71a55ff5bf04d7273.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.cfa3277e9be0442f20496a115b31ec8ea96fbc98a26ea65129a68166464a4c40.wasm", + "tx_withdraw.wasm": "tx_withdraw.63b3513b411659e4ab6c63c61bb56aff33f6f39cb42d9f8618cd373320dcc087.wasm", + "vp_nft.wasm": "vp_nft.2f21e99208c9c0842106b67fa4f113e9aa76ce009a486788e5458247fd7ef1ba.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7135a8139a99f1e6cdb3c44e5a6824ddfb8c3db158b3284191d4f53d18f38710.wasm", + "vp_token.wasm": "vp_token.0cd22c3ba1c234bfbfc3a0d0913a9dc230c22ec7f629ba74907e8af40de687a4.wasm", + "vp_user.wasm": "vp_user.112577f72fcaf46c25389f67a4a643e71d61f7fb01447970acdf96a8ad72e9ec.wasm" } \ No newline at end of file From d20340fc7384d9b656853cb6d5bc7b14c182007f Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 16:23:35 +0200 Subject: [PATCH 0688/1995] [tmp]: Refactoring the merkle tree to properly support multistores more easily --- Cargo.lock | 268 ++++++++++++++++++ apps/src/lib/node/ledger/protocol/mod.rs | 3 - shared/Cargo.toml | 2 + .../ledger/eth_bridge/storage/bridge_pool.rs | 158 +++++++++++ shared/src/ledger/storage/merkle_tree.rs | 250 +++++----------- shared/src/ledger/storage/mod.rs | 1 + shared/src/ledger/storage/traits.rs | 214 ++++++++++++++ shared/src/types/eth_bridge_pool.rs | 28 +- shared/src/types/ethereum_events.rs | 15 +- shared/src/types/hash.rs | 23 +- shared/src/types/keccak.rs | 194 +++++++++++++ shared/src/types/mod.rs | 1 + shared/src/types/storage.rs | 57 +++- .../vote_extensions/validator_set_update.rs | 8 +- .../validator_set_update/encoding.rs | 99 ------- 15 files changed, 1005 insertions(+), 316 deletions(-) create mode 100644 shared/src/ledger/storage/traits.rs create mode 100644 shared/src/types/keccak.rs delete mode 100644 shared/src/types/vote_extensions/validator_set_update/encoding.rs diff --git a/Cargo.lock b/Cargo.lock index ad55b9f77b..69d5ab3f90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,6 +1137,28 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chrono-tz" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "chunked_transfer" version = "1.4.0" @@ -1780,6 +1802,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + [[package]] name = "diff" version = "0.1.12" @@ -2619,6 +2647,17 @@ dependencies = [ "regex", ] +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "gloo-timers" version = "0.2.4" @@ -2895,6 +2934,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + [[package]] name = "hyper" version = "0.10.16" @@ -3121,6 +3166,24 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils 0.8.8", + "globset", + "lazy_static 1.4.0", + "log 0.4.17", + "memchr", + "regex", + "same-file", + "thread_local 1.1.4", + "walkdir", + "winapi-util", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -4060,6 +4123,12 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4424,6 +4493,8 @@ dependencies = [ "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", + "variant_access_derive", + "variant_access_traits", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -5173,6 +5244,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.7" @@ -5239,6 +5319,40 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -5259,6 +5373,45 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "0.4.29" @@ -6736,12 +6889,27 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -7130,6 +7298,28 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "tera" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static 1.4.0", + "percent-encoding 2.1.0", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde 1.0.137", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "term_size" version = "0.3.2" @@ -7936,6 +8126,65 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check 0.9.4", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "1.4.2" @@ -8096,6 +8345,25 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "variant_access_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd235ffafb854ed81b49217ce411e850a39628a5d26740ecfb60421c873d834" +dependencies = [ + "lazy_static 1.4.0", + "quote", + "syn", + "tera", + "variant_access_traits", +] + +[[package]] +name = "variant_access_traits" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d75c5a83bb8912dd9c628adf954c9f9bff74a4e170d2c90242f4e56a0d643e" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 9b1b13c859..90bb6994b1 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -204,9 +204,6 @@ where /// containing changed keys and the like should be returned in the normal way. pub(crate) fn apply_protocol_tx<'a, D, H>( tx: ProtocolTxType, - // TODO: eventually this `storage` parameter could be tightened further to - // an impl trait of only the subset of [`Storage`] functionality that we - // need _storage: &'a mut Storage, ) -> Result where diff --git a/shared/Cargo.toml b/shared/Cargo.toml index aa20e50739..d344d346ad 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -96,6 +96,8 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "9 thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" +variant_access_derive = "0.4.1" +variant_access_traits = "0.4.1" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} wasmer-compiler-singlepass = {version = "=2.2.0", optional = true} diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index b86266f124..8588ae0550 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -1,8 +1,18 @@ //! Tools for accessing the storage subspaces of the Ethereum //! bridge pool +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::ops::Deref; + +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use eyre::eyre; use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::ethereum_events::KeccakHash; +use crate::types::hash::{Hash, keccak_hash}; use crate::types::storage::{DbKeySeg, Key}; +use crate::ledger::storage::{Sha256Hasher, StorageHasher}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -12,6 +22,11 @@ const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error that may be returned by the validity predicate +pub struct Error(#[from] eyre::Error); + /// Get the storage key for the transfers in the pool pub fn get_pending_key() -> Key { Key { @@ -44,3 +59,146 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { pub fn is_protected_storage(key: &Key) -> bool { is_bridge_pool_key(key) && *key != get_pending_key() } + +/// A simple Merkle tree for the Ethereum bridge pool +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct BridgePoolTree { + /// Root of the tree + root: KeccakHash, + /// The underlying storage + store: BTreeMap +} + +/// TODO: Hash ABI instead of Borsh +impl BridgePoolTree { + /// Create a new merkle tree for the Ethereum bridge pool + pub fn new(root: KeccakHash, store: BTreeMap) -> Self { + Self{ root, store } + } + + /// Parse the key to ensure it is of the correct type. + /// + /// If it is, it can be converted to a hash. + /// Checks if the hash is in the tree. + pub fn has_key(&self, key: &Key) -> Result { + Ok(self.store.contains_key(&Self::parse_key(key)?)) + } + + /// Update the tree with a new value. + /// + /// Returns the new root if successful. Will + /// return an error if the key is malformed. + pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { + let hash = Self::parse_key(key)?; + if hash != keccak_hash(&value.try_to_vec().unwrap()) { + return eyre!("Key does not match hash of the value")?; + } + _ = self.store.insert(hash, value); + self.root = self.compute_root(); + Ok(self.root()) + } + + /// Delete a key from storage and update the root + pub fn delete(&mut self, key: &Key) -> Result<(), Error>{ + let hash = Self::parse_key(key)?; + _ = self.store.remove(&hash); + self.root = self.compute_root(); + Ok(()) + } + + /// Compute the root of the merkle tree + pub fn compute_root(&self) -> KeccakHash { + let mut leaves = self.store.iter(); + let mut root = if let Some((hash, _)) = leaves.next() { + hash.clone() + } else { + return Default::default(); + }; + + for (leaf, _) in leaves { + root = keccak_hash([root.0, leaf.0].concat()); + } + root + } + + /// Return the root as a [`Hash`] type. + pub fn root(&self) -> Hash { + self.root.clone().into() + } + + /// Get a reference to the backing store + pub fn store(&self) -> &BTreeMap { + &self.store + } + + pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { + for (key, value) in self.store { + + } + } + + /// Parse a db key to see if it is valid for the + /// bridge pool. + /// + /// It should have one string segment which should + /// parse into a [Hash] + fn parse_key(key: &Key) -> Result { + if key.segments.len() == 1 { + match &key.segments[0] { + DbKeySeg::StringSeg(str) => str.as_str().try_into().ok_or( + eyre!("Could not parse key segment as a hash")? + ), + _ => eyre!("Bridge pool keys should be strings, not addresses")? + } + } else { + eyre!("Key for the bridge pool should not have more than one segment")? + } + } +} + +/// A multi-leaf membership proof +pub struct BridgePoolProof { + /// The hashes other than the provided leaves + pub proof: Vec, + /// The leaves; must be sorted + pub leaves: Vec, + /// flags to indicate how to combine hashes + pub flags: Vec, +} + +impl BridgePoolProof { + + /// Verify a membership proof matches the provided root + pub fn verify(&self, root: KeccakHash) -> bool { + if self.proof.len() + self.leaves.len() != self.flags.len() { + return false; + } + if self.flags.len() == 0 { + return true; + } + let mut leaf_pos = 0usize; + let mut proof_pos = 0usize; + let mut computed; + if self.flags[0] { + leaf = self.leaves[leaf_pos].clone(); + computed = keccak_hash(leaf.try_to_vec().unwrap()); + leaf_pos += 1; + } else { + computed = self.proof[proof_pos].clone(); + proof_pos += 1; + } + for flag in 1..self.flages.len() { + let mut next_hash; + if self.flags[flag] { + leaf = self.leaves[leaf_pos].clone(); + next_hash = keccak_hash(leaf.try_to_vec().unwrap()); + leaf_pos += 1; + } else { + next_hash = self.proof[proof_pos].clone(); + proof_pos += 1; + } + computed = keccak_hash([computed, next_hash].concat()); + } + computed == root + } +} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index c7fec10126..cc26b95b5d 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -16,20 +16,22 @@ use ics23::{ CommitmentProof, ExistenceProof, HashOp, LeafOp, LengthOp, NonExistenceProof, ProofSpec, }; -use itertools::Either; +use itertools::{Either, Itertools}; use prost::Message; use sha2::{Digest, Sha256}; use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; use super::IBC_KEY_LIMIT; +use super::traits::{self, StorageHasher, Sha256Hasher}; use crate::bytes::ByteBuf; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::ledger::storage::types; use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::ethereum_events::KeccakHash; use crate::types::hash::Hash; -use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, TreeBytes, -}; +use crate::types::storage::{DbKeySeg, Error as StorageError, Key, StringKey, TreeBytes, MerkleValue}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -41,11 +43,13 @@ pub enum Error { #[error("Empty Key: {0}")] EmptyKey(String), #[error("Merkle Tree error: {0}")] - MerkleTree(MtError), + MerkleTree(String), #[error("Invalid store type: {0}")] StoreType(String), #[error("Non-existence proofs not supported for store type: {0}")] NonExistenceProof(String), + #[error("Invalid value given to sub-tree storage")] + InvalidValue, } /// Result for functions that may fail @@ -54,6 +58,7 @@ 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 BridgePoolStore = std::collections::BTreeMap; pub type Smt = ArseMerkleTree; pub type Amt = ArseMerkleTree; @@ -79,6 +84,8 @@ pub enum StoreType { Ibc, /// For PoS-related data PoS, + /// For the Ethereum bridge Pool transfers + BridgePool, } /// Backing storage for merkle trees @@ -91,6 +98,8 @@ pub enum Store { Ibc(AmtStore), /// For PoS-related data PoS(SmtStore), + /// For the Ethereum bridge Pool transfers + BridgePool(BTreeSetStore) } impl Store { @@ -100,6 +109,7 @@ impl Store { Self::Account(store) => StoreRef::Account(store), Self::Ibc(store) => StoreRef::Ibc(store), Self::PoS(store) => StoreRef::PoS(store), + Self::BridgePool(store) => StoreRef::BridgePool(store), } } } @@ -114,6 +124,8 @@ pub enum StoreRef<'a> { Ibc(&'a AmtStore), /// For PoS-related data PoS(&'a SmtStore), + /// For the Ethereum bridge Pool transfers + BridgePool(&'a BTreeSetStore) } impl<'a> StoreRef<'a> { @@ -123,6 +135,7 @@ impl<'a> StoreRef<'a> { Self::Account(store) => Store::Account(store.to_owned()), Self::Ibc(store) => Store::Ibc(store.to_owned()), Self::PoS(store) => Store::PoS(store.to_owned()), + Self::BridgePool(store) => Store::BridgePool(store.to_owned()), } } @@ -132,6 +145,7 @@ impl<'a> StoreRef<'a> { Self::Account(store) => store.try_to_vec(), Self::Ibc(store) => store.try_to_vec(), Self::PoS(store) => store.try_to_vec(), + Self::BridgePool(store) => store.try_to_vec(), } .expect("Serialization failed") } @@ -140,11 +154,12 @@ impl<'a> StoreRef<'a> { impl StoreType { /// Get an iterator for the base tree and subtrees pub fn iter() -> std::slice::Iter<'static, Self> { - static SUB_TREE_TYPES: [StoreType; 4] = [ + static SUB_TREE_TYPES: [StoreType; 5] = [ StoreType::Base, StoreType::Account, StoreType::PoS, StoreType::Ibc, + StoreType::BridgePool, ]; SUB_TREE_TYPES.iter() } @@ -162,6 +177,9 @@ impl StoreType { InternalAddress::Ibc => { Ok((StoreType::Ibc, key.sub_key()?)) } + InternalAddress::EthBridgePool => { + Ok((StoreType::BridgePool, key.sub_key()?)) + } // use the same key for Parameters _ => Ok((StoreType::Account, key.clone())), } @@ -190,6 +208,9 @@ impl StoreType { Self::PoS => Ok(Store::PoS( types::decode(bytes).map_err(Error::CodingError)?, )), + Self::BridgePool => Ok(Store::BridgePool( + types::decode(bytes).map_err(Error::CodingError)?, + )), } } } @@ -203,6 +224,7 @@ impl FromStr for StoreType { "account" => Ok(StoreType::Account), "ibc" => Ok(StoreType::Ibc), "pos" => Ok(StoreType::PoS), + "eth_bridge_pool" => Ok(StoreType::BridgePool), _ => Err(Error::StoreType(s.to_string())), } } @@ -215,6 +237,7 @@ impl fmt::Display for StoreType { StoreType::Account => write!(f, "account"), StoreType::Ibc => write!(f, "ibc"), StoreType::PoS => write!(f, "pos"), + StoreType::BridgePool => write!(f, "eth_bridge_pool"), } } } @@ -226,6 +249,7 @@ pub struct MerkleTree { account: Smt, ibc: Amt, pos: Smt, + bridge_pool: BridgePoolTree, } impl core::fmt::Debug for MerkleTree { @@ -244,47 +268,43 @@ impl MerkleTree { 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); - + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, ibc, pos, + bridge_pool, + } + } + + fn tree(&self, store_type: &StoreType) -> &impl traits::MerkleTree { + match store_type { + StoreType::Base => &self.base, + StoreType::Account => &self.account, + StoreType::Ibc => &self.ibc, + StoreType::PoS => &self.pos, + StoreType::BridgePool => &self.bridge_pool, } } - fn tree(&self, store_type: &StoreType) -> Either<&Smt, &Amt> { + fn tree_mut(&mut self, store_type: &StoreType) -> &mut impl traits::MerkleTree { match store_type { - StoreType::Base => Either::Left(&self.base), - StoreType::Account => Either::Left(&self.account), - StoreType::Ibc => Either::Right(&self.ibc), - StoreType::PoS => Either::Left(&self.pos), + StoreType::Base => &mut self.base, + StoreType::Account => &mut self.account, + StoreType::Ibc => &mut self.ibc, + StoreType::PoS => &mut self.pos, + StoreType::BridgePool => &mut self.bridge_pool, } } - fn update_tree( + fn update_tree>( &mut self, store_type: &StoreType, - key: MerkleKey, - value: Either, + key: &Key, + value: MerkleValue, ) -> Result<()> { - let sub_root = match store_type { - StoreType::Account => self - .account - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - StoreType::Ibc => self - .ibc - .update(key.try_into()?, value.unwrap_right()) - .map_err(Error::MerkleTree)?, - StoreType::PoS => self - .pos - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - // base tree should not be directly updated - StoreType::Base => unreachable!(), - }; - + let sub_root = self.tree_mut(store_type).update(key, value)?; // 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()); @@ -296,41 +316,19 @@ 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 value = match self.tree(&store_type) { - Either::Left(smt) => { - 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)?.is_zero() - } - }; - Ok(!value) + self.tree(&store_type).has_key(&sub_key) } /// 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; - 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) + pub fn update>(&mut self, key: &Key, value: impl Into>) -> Result<()> { + let (store_type, sub_key) = StoreType::sub_key(key)?; + self.update_tree(&store_type, sub_key.into(), value.into()) } /// 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; - 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) + let (store_type, sub_key) = StoreType::sub_key(key)?; + self.tree_mut(&store_type).delete(&sub_key) } /// Get the root @@ -345,6 +343,7 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), + bridge_pool: (self.bridge_pool.root(). self.) } } @@ -568,45 +567,6 @@ impl fmt::Display for MerkleRoot { } } -impl From<(StoreType, Key)> for MerkleKey { - fn from((store, key): (StoreType, Key)) -> Self { - match store { - StoreType::Base | StoreType::Account | StoreType::PoS => { - MerkleKey::Sha256(key, PhantomData) - } - StoreType::Ibc => MerkleKey::Raw(key), - } - } -} - -impl TryFrom> for SmtHash { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), - _ => Err(Error::InvalidMerkleKey( - "This key is for a sparse merkle tree".into(), - )), - } - } -} - -impl TryFrom> for StringKey { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Raw(key) => { - Self::try_from_bytes(key.to_string().as_bytes()) - } - _ => Err(Error::InvalidMerkleKey( - "This is not an key for the IBC subtree".into(), - )), - } - } -} - /// The root and store pairs to restore the trees #[derive(Default)] pub struct MerkleTreeStoresRead { @@ -614,6 +574,7 @@ pub struct MerkleTreeStoresRead { account: (Hash, SmtStore), ibc: (Hash, AmtStore), pos: (Hash, SmtStore), + bridge_pool: (KeccakHash, BTreeSetStore), } impl MerkleTreeStoresRead { @@ -624,6 +585,7 @@ impl MerkleTreeStoresRead { StoreType::Account => self.account.0 = root, StoreType::Ibc => self.ibc.0 = root, StoreType::PoS => self.pos.0 = root, + StoreType::BridgePool => self.bridge_pool.0 = root.into(), } } @@ -634,6 +596,7 @@ impl MerkleTreeStoresRead { Store::Account(store) => self.account.1 = store, Store::Ibc(store) => self.ibc.1 = store, Store::PoS(store) => self.pos.1 = store, + Store::BridgePool(store) => self.bridge_pool.1 = store, } } } @@ -644,6 +607,7 @@ pub struct MerkleTreeStoresWrite<'a> { account: (Hash, &'a SmtStore), ibc: (Hash, &'a AmtStore), pos: (Hash, &'a SmtStore), + bridge_pool: (Hash, &'a BTreeSetStore) } impl<'a> MerkleTreeStoresWrite<'a> { @@ -654,6 +618,7 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => &self.account.0, StoreType::Ibc => &self.ibc.0, StoreType::PoS => &self.pos.0, + StoreType::BridgePool => &self.bridge_pool.0 } } @@ -664,96 +629,11 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => StoreRef::Account(self.account.1), StoreType::Ibc => StoreRef::Ibc(self.ibc.1), StoreType::PoS => StoreRef::PoS(self.pos.1), + StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1) } } } -impl TreeKey for StringKey { - type Error = Error; - - fn as_slice(&self) -> &[u8] { - &self.original.as_slice()[..self.length] - } - - fn try_from_bytes(bytes: &[u8]) -> Result { - 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 { - return Err(Error::InvalidMerkleKey( - "Input IBC key is too large".into(), - )); - } - original[i] = *byte; - tree_key[i] = byte.wrapping_add(1); - length += 1; - } - Ok(Self { - original, - tree_key: tree_key.into(), - length, - }) - } -} - -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 - fn hash(value: impl AsRef<[u8]>) -> H256; -} - -/// The storage hasher used for the merkle tree. -#[derive(Default)] -pub struct Sha256Hasher(Sha256); - -impl Hasher for Sha256Hasher { - fn write_bytes(&mut self, h: &[u8]) { - self.0.update(h) - } - - 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 { - ics23::HashOp::Sha256 - } -} - -impl StorageHasher for Sha256Hasher { - fn hash(value: impl AsRef<[u8]>) -> H256 { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - let hash = hasher.finalize(); - let bytes: [u8; 32] = hash - .as_slice() - .try_into() - .expect("Sha256 output conversion to fixed array shouldn't fail"); - bytes.into() - } -} - -impl fmt::Debug for Sha256Hasher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Sha256Hasher") - } -} impl From for Error { fn from(error: StorageError) -> Self { diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 8b19a8a16a..5827a7c1a4 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -5,6 +5,7 @@ mod merkle_tree; pub mod mockdb; pub mod types; pub mod write_log; +pub mod traits; use core::fmt::Debug; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs new file mode 100644 index 0000000000..d5411cc966 --- /dev/null +++ b/shared/src/ledger/storage/traits.rs @@ -0,0 +1,214 @@ +//! Traits needed to provide a uniform interface over +//! all the different Merkle trees used for storage +use std::convert::{TryFrom, TryInto}; +use std::fmt; + +use arse_merkle_tree::{H256, Hash as SmtHash, Key as TreeKey}; +use arse_merkle_tree::traits::{Value, Hasher}; +use ibc_proto::ics23::CommitmentProof; +use sha2::{Digest, Sha256}; + +use super::IBC_KEY_LIMIT; +use super::merkle_tree::{Smt, Amt, Error}; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; +use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::hash::Hash; +use crate::types::storage::{ + MerkleKey, StringKey, TreeBytes, MerkleValue, Key +}; + +pub trait MerkleTree { + type Error; + + fn has_key(&self, key: &Key) -> Result; + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result; + fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; + fn membership_proof(&self, keys: &[Key], proof: Option) -> Option; +} + +impl MerkleTree for Smt { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + self.get(&H::hash(key.to_string()).into()) + .and(Ok(true)) + .map_error(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + let value = match value { + MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_ref()) + .map_err(|| Error::InvalidValue)?, + _ => return Err(Error::InvalidValue) + }; + self.update( + H::hash(key.to_string()).into(), + value, + ) + .map(Hash::into) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + let value = Hash::zero(); + self.update( + H::hash(key.to_string()).into(), + value + ) + .and(Ok(())) + .map_err(|err|Error::MerkleTree(err.to_string())) + } +} + +impl MerkleTree for Amt { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + self.get(&key) + .and(Ok(bool)) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: MerkleKey, value: MerkleValue) -> Result { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = match value { + MerkleValue::Bytes(bytes) => TreeBytes::from(bytes.as_ref().to_vec()), + _ => return Err(Error::InvalidValue) + }; + self.update( + key, + value, + ) + .map(Into::into) + .map_err(|err|Error::MerkleTree(err.to_string())) + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = TreeBytes::zero(); + self.update( + key, + value + ) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + } +} + +impl MerkleTree for BridgePoolTree { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + self.has_key(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + if let MerkleValue::Transfer(transfer) = value { + self.update(key, transfer) + .map_err( | err| Error::MerkleTree(err.to_string())) + } else { + Err(Error::InvalidValue) + } + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + self.delete(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } +} + +impl TreeKey for StringKey { + type Error = Error; + + fn as_slice(&self) -> &[u8] { + &self.original.as_slice()[..self.length] + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + 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 { + return Err(Error::InvalidMerkleKey( + "Input IBC key is too large".into(), + )); + } + original[i] = *byte; + tree_key[i] = byte.wrapping_add(1); + length += 1; + } + Ok(Self { + original, + tree_key: tree_key.into(), + length, + }) + } +} + +impl Value for TreeBytes { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + TreeBytes::zero() + } +} + +pub trait MembershipProof { }; + +impl MembershipProof for CommitmentProof; + +/// The storage hasher used for the merkle tree. +pub trait StorageHasher: Hasher + Default { + /// Hash the value to store + fn hash(value: impl AsRef<[u8]>) -> H256; +} + +/// The storage hasher used for the merkle tree. +#[derive(Default)] +pub struct Sha256Hasher(Sha256); + +impl Hasher for Sha256Hasher { + fn write_bytes(&mut self, h: &[u8]) { + self.0.update(h) + } + + 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 { + ics23::HashOp::Sha256 + } +} + +impl StorageHasher for Sha256Hasher { + fn hash(value: impl AsRef<[u8]>) -> H256 { + let mut hasher = Sha256::new(); + hasher.update(value.as_ref()); + let hash = hasher.finalize(); + let bytes: [u8; 32] = hash + .as_slice() + .try_into() + .expect("Sha256 output conversion to fixed array shouldn't fail"); + bytes.into() + } +} + +impl fmt::Debug for Sha256Hasher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Sha256Hasher") + } +} \ No newline at end of file diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 18c6e7f1a9..17ba1f57e7 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,9 +1,11 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use ethabi::token::Token; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, Uint}; +use crate::types::ethereum_events::{EthAddress, Uint, KeccakHash}; +use crate::types::keccak; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -17,6 +19,7 @@ use crate::types::token::Amount; Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct TransferToEthereum { /// The type of token @@ -40,6 +43,7 @@ pub struct TransferToEthereum { Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct PendingTransfer { /// The message to send to Ethereum to @@ -49,6 +53,25 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } +impl keccak::encode::Encode for PendingTransfer { + + fn tokenize(&self) -> Vec { + let from = Token::String(self.gas_fee.payer.to_string()); + let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); + let to = Token::Address(self.transfer.recipient.0.into()); + let amount = Token::Uint(u64::from(self.transfer.amount).into()); + let nonce = Token::Uint(self.transfer.nonce.into()); + vec![ + from, + fee, + to, + amount, + nonce, + ] + } +} + + /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. @@ -61,6 +84,7 @@ pub struct PendingTransfer { Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct GasFee { /// The amount of fees (in NAM) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 869b4a4f14..f5870b057b 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -8,6 +8,7 @@ use eyre::{eyre, Context}; use crate::types::address::Address; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type @@ -80,20 +81,6 @@ impl FromStr for EthAddress { } } -/// A Keccak hash -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - BorshSerialize, - BorshDeserialize, - BorshSchema, -)] -pub struct KeccakHash(pub [u8; 32]); - /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 0e960ec01f..3ea2c559cc 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use arse_merkle_tree::traits::Value; use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex::FromHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use tendermint::abci::transaction; @@ -22,6 +23,8 @@ pub enum Error { Temporary { error: String }, #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError) } /// Result for functions that may fail @@ -85,6 +88,24 @@ impl TryFrom<&[u8]> for Hash { } } +impl TryFrom for Hash { + type Error = self::Error; + + fn try_from(string: String) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes) + } +} + +impl TryFrom<&str> for Hash { + type Error = self::Error; + + fn try_from(string: &str) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes) + } +} + impl From for transaction::Hash { fn from(hash: Hash) -> Self { Self::new(hash.0) @@ -143,4 +164,4 @@ impl Value for Hash { fn zero() -> Self { Hash([0u8; 32]) } -} +} \ No newline at end of file diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs new file mode 100644 index 0000000000..a489cd23ba --- /dev/null +++ b/shared/src/types/keccak.rs @@ -0,0 +1,194 @@ +//! This module is for hashing Anoma types using the keccak +//! hash function in a way that is compatible with smart contracts +//! on Ethereum +use std::convert::TryFrom; + +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use hex::FromHex; +use tiny_keccak::{Hasher, Keccak}; + +use crate::types::hash::{HASH_LENGTH, Hash, HashResult}; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("TEMPORARY error: {error}")] + Temporary { error: String }, + #[error("Failed trying to convert slice to a hash: {0}")] + ConversionFailed(std::array::TryFromSliceError), + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError) +} + +/// A Keccak hash +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct KeccakHash(pub [u8; 32]); + +impl From for Hash { + fn from(hash: KeccakHash) -> Self { + Hash(hash.0) + } +} + +impl From for KeccakHash { + fn from(hash: Hash) -> Self { + KeccakHash(hash.0) + } +} + +impl TryFrom<&[u8]> for KeccakHash { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + if value.len() != HASH_LENGTH { + return Err(Error::Temporary { + error: format!( + "Unexpected tx hash length {}, expected {}", + value.len(), + HASH_LENGTH + ), + }); + } + let hash: [u8; 32] = + TryFrom::try_from(value).map_err(Error::ConversionFailed)?; + Ok(KeccakHash(hash)) + } +} + +impl TryFrom for KeccakHash { + type Error = self::Error; + + fn try_from(string: String) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes) + } +} + +impl TryFrom<&str> for KeccakHash { + type Error = self::Error; + + fn try_from(string: &str) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes).into() + } +} + + +/// This module defines encoding methods compatible with Ethereum +/// smart contracts. +pub mod encode { + #[doc(inline)] + pub use ethabi::token::Token; + use tiny_keccak::{Hasher, Keccak}; + + use crate::types::ethereum_events::KeccakHash; + + /// Contains a method to encode data to a format compatible with Ethereum. + pub trait Encode { + + /// Encodes a struct into a sequence of ABI + /// [`Token`] instances. + fn tokenize(&self) -> Vec; + + /// Returns the encoded [`Token`] instances. + fn encode(&self) -> Vec { + let tokens = self.tokenize(); + ethabi::encode(&tokens) + } + + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string. + fn keccak256(&self) -> KeccakHash { + let mut output = [0; 32]; + + let mut state = Keccak::v256(); + state.update(self.encode().as_slice()); + state.finalize(&mut output); + + KeccakHash(output) + } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string appended to an Ethereum + /// signature header. + fn signed_keccak256(&self) -> KeccakHash { + let mut output = [0; 32]; + + let eth_message = { + let message = self.encode(); + + let mut eth_message = + format!("\x19Ethereum Signed Message:\n{}", message.len()) + .into_bytes(); + eth_message.extend_from_slice(&message); + eth_message + }; + + let mut state = Keccak::v256(); + state.update(ð_message); + state.finalize(&mut output); + + KeccakHash(output) + } + } + + /// Represents an Ethereum encoding method equivalent + /// to `abi.encode`. + pub type AbiEncode = [Token; N]; + + impl Encode for AbiEncode { + #[inline] + fn tokenize(&self) -> Vec { + return self.to_vec() + } + } + + // TODO: test signatures here once we merge secp keys + #[cfg(test)] + mod tests { + use ethabi::ethereum_types::U256; + + use super::*; + + /// Checks if we get the same result as `abi.encode`, for some given + /// input data. + #[test] + fn test_abi_encode() { + let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + let expected = hex::decode(&expected[2..]).expect("Test failed"); + let got = AbiEncode::encode(&[ + Token::Uint(U256::from(42u64)), + Token::String("test".into()), + ]); + assert_eq!(expected, got); + } + + /// Sanity check our keccak hash implementation. + #[test] + fn test_keccak_hash_impl() { + let expected = + "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; + assert_eq!( + expected, + &hex::encode({ + let mut st = Keccak::v256(); + let mut output = [0; 32]; + st.update(b"hello"); + st.finalize(&mut output); + output + }) + ); + } + } +} \ No newline at end of file diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4f491d0a99..72db38b769 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -10,6 +10,7 @@ pub mod hash; pub mod ibc; pub mod intent; pub mod internal; +pub mod keccak; pub mod key; pub mod matchmaker; pub mod nft; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b7e2005bb7..7b7ffad7a2 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -11,12 +11,15 @@ use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use variant_access_derive::*; +use variant_access_traits::*; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; +use crate::types::eth_bridge_pool::{TransferToEthereum, PendingTransfer}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -31,6 +34,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Could not parse string: '{0}' into requested type: {1}")] + ParseError((String, String)) } /// Result for functions that may fail @@ -233,14 +238,35 @@ 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), +/// An enum representing the different types of values +/// that can be passed into Anoma's storage. +/// +/// This is a multi-store organized as +/// several Merkle trees, each of which is +/// responsible for understanding how to parse +/// this value. +pub enum MerkleValue> { + /// raw bytes + Bytes(T), + /// a transfer to be put in the Ethereum bridge pool + /// We actually only need the key (which is the hash + /// of the transfer). So this variant contains no data. + Transfer(PendingTransfer), +} + +impl From for MerkleValue +where + T: AsRef<[u8]> +{ + fn from(bytes: T) -> Self { + Self::Bytes(bytes) + } +} + +impl> From for MerkleValue { + fn from(transfer: PendingTransfer) -> Self { + Self::Transfer(transfer) + } } /// Storage keys that are utf8 encoded strings @@ -581,6 +607,20 @@ impl KeySeg for Address { } } +impl KeySeg for Hash { + fn parse(seg: String) -> Result { + seg.try_into().map_error(Error::ParseError((seg, "Hash".into()))) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, @@ -630,6 +670,7 @@ impl Add for Epoch { } } + impl Sub for Epoch { type Output = Epoch; diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index b51fc83df1..4af065f990 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,8 +1,5 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. - -pub mod encoding; - use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -224,12 +221,14 @@ fn compute_hash( // this is only here so we don't pollute the // outer namespace with serde traits mod tag { + use ethabi::Token; use serde::{Deserialize, Serialize}; use super::encoding::{AbiEncode, Encode, Token}; use super::{bheight_to_token, Vext, VotingPowersMapExt}; use crate::proto::SignedSerialize; - use crate::types::ethereum_events::KeccakHash; + use crate::types::keccak::KeccakHash; + use crate::types::keccak::encode::{Encode, AbiEncode}; /// Tag type that indicates we should use [`AbiEncode`] /// to sign data in a [`crate::proto::Signed`] wrapper. @@ -263,6 +262,7 @@ mod tag { #[doc(inline)] pub use tag::SerializeWithAbiEncode; +use crate::types::keccak::encode::{AbiEncode, Token}; #[cfg(test)] mod tests { diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs deleted file mode 100644 index 0ba253ee23..0000000000 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! This module defines encoding methods compatible with Ethereum -//! smart contracts. -// TODO: probably move this module elsewhere - -#[doc(inline)] -pub use ethabi::token::Token; -use tiny_keccak::{Hasher, Keccak}; - -use crate::types::ethereum_events::KeccakHash; - -/// Contains a method to encode data to a format compatible with Ethereum. -pub trait Encode { - /// Returns the encoded [`Token`] instances. - fn encode(tokens: &[Token]) -> Vec; - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string. - fn keccak256(tokens: &[Token]) -> KeccakHash { - let mut output = [0; 32]; - - let mut state = Keccak::v256(); - state.update(&Self::encode(tokens)); - state.finalize(&mut output); - - KeccakHash(output) - } - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string appended to an Ethereum - /// signature header. - fn signed_keccak256(tokens: &[Token]) -> KeccakHash { - let mut output = [0; 32]; - - let eth_message = { - let message = Self::encode(tokens); - - let mut eth_message = - format!("\x19Ethereum Signed Message:\n{}", message.len()) - .into_bytes(); - eth_message.extend_from_slice(&message); - eth_message - }; - - let mut state = Keccak::v256(); - state.update(ð_message); - state.finalize(&mut output); - - KeccakHash(output) - } -} - -/// Represents an Ethereum encoding method equivalent -/// to `abi.encode`. -pub struct AbiEncode; - -impl Encode for AbiEncode { - #[inline] - fn encode(tokens: &[Token]) -> Vec { - ethabi::encode(tokens) - } -} - -// TODO: test signatures here once we merge secp keys -#[cfg(test)] -mod tests { - use ethabi::ethereum_types::U256; - - use super::*; - - /// Checks if we get the same result as `abi.encode`, for some given - /// input data. - #[test] - fn test_abi_encode() { - let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; - let expected = hex::decode(&expected[2..]).expect("Test failed"); - let got = AbiEncode::encode(&[ - Token::Uint(U256::from(42u64)), - Token::String("test".into()), - ]); - assert_eq!(expected, got); - } - - /// Sanity check our keccak hash implementation. - #[test] - fn test_keccak_hash_impl() { - let expected = - "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; - assert_eq!( - expected, - &hex::encode({ - let mut st = Keccak::v256(); - let mut output = [0; 32]; - st.update(b"hello"); - st.finalize(&mut output); - output - }) - ); - } -} From c23a39469ea1d2c466f71f2c8ae3551c3706f252 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 16:52:03 +0200 Subject: [PATCH 0689/1995] [feat]: Refactored Keccak hashes slightly since they will be used more widely in the future --- .../lib/node/ledger/ethereum_node/events.rs | 5 +- shared/src/types/ethereum_events.rs | 15 +- shared/src/types/keccak.rs | 215 ++++++++++++++++++ shared/src/types/mod.rs | 1 + .../vote_extensions/validator_set_update.rs | 12 +- .../validator_set_update/encoding.rs | 99 -------- 6 files changed, 225 insertions(+), 122 deletions(-) create mode 100644 shared/src/types/keccak.rs delete mode 100644 shared/src/types/vote_extensions/validator_set_update/encoding.rs diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 14b8c04ee4..0d176b875f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -49,9 +49,10 @@ pub mod eth_events { use ethabi::token::Token; use namada::types::address::Address; use namada::types::ethereum_events::{ - EthAddress, EthereumEvent, KeccakHash, TokenWhitelist, - TransferToEthereum, TransferToNamada, Uint, + EthAddress, EthereumEvent, TokenWhitelist, TransferToEthereum, + TransferToNamada, Uint, }; + use namada::types::keccak::KeccakHash; use namada::types::token::Amount; use num256::Uint256; use thiserror::Error; diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 98d031e8bf..493f92cd7b 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -8,6 +8,7 @@ use eyre::{eyre, Context}; use crate::types::address::Address; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type @@ -80,20 +81,6 @@ impl FromStr for EthAddress { } } -/// A Keccak hash -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - BorshSerialize, - BorshDeserialize, - BorshSchema, -)] -pub struct KeccakHash(pub [u8; 32]); - /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs new file mode 100644 index 0000000000..185b89956e --- /dev/null +++ b/shared/src/types/keccak.rs @@ -0,0 +1,215 @@ +//! This module is for hashing Anoma types using the keccak +//! hash function in a way that is compatible with smart contracts +//! on Ethereum +use std::convert::TryFrom; +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex::FromHex; +use thiserror::Error; + +use crate::types::hash::{Hash, HASH_LENGTH}; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("TEMPORARY error: {error}")] + Temporary { error: String }, + #[error("Failed trying to convert slice to a hash: {0}")] + ConversionFailed(std::array::TryFromSliceError), + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError), +} + +/// A Keccak hash +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct KeccakHash(pub [u8; 32]); + +impl Display for KeccakHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + + +impl From for Hash { + fn from(hash: KeccakHash) -> Self { + Hash(hash.0) + } +} + +impl From for KeccakHash { + fn from(hash: Hash) -> Self { + KeccakHash(hash.0) + } +} + +impl TryFrom<&[u8]> for KeccakHash { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + if value.len() != HASH_LENGTH { + return Err(Error::Temporary { + error: format!( + "Unexpected tx hash length {}, expected {}", + value.len(), + HASH_LENGTH + ), + }); + } + let hash: [u8; 32] = + TryFrom::try_from(value).map_err(Error::ConversionFailed)?; + Ok(KeccakHash(hash)) + } +} + +impl TryFrom for KeccakHash { + type Error = Error; + + fn try_from(string: String) -> Result { + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(bytes.as_slice()) + } +} + +impl TryFrom<&str> for KeccakHash { + type Error = Error; + + fn try_from(string: &str) -> Result { + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(bytes.as_slice()) + } +} + +/// This module defines encoding methods compatible with Ethereum +/// smart contracts. +pub mod encode { + #[doc(inline)] + pub use ethabi::token::Token; + use tiny_keccak::{Hasher, Keccak}; + + use super::*; + + /// Contains a method to encode data to a format compatible with Ethereum. + pub trait Encode { + /// Encodes a struct into a sequence of ABI + /// [`Token`] instances. + fn tokenize(&self) -> Vec; + + /// Returns the encoded [`Token`] instances. + fn encode(&self) -> Vec { + let tokens = self.tokenize(); + ethabi::encode(&tokens) + } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string. + fn keccak256(&self) -> KeccakHash { + let mut output = [0; 32]; + + let mut state = Keccak::v256(); + state.update(self.encode().as_slice()); + state.finalize(&mut output); + + KeccakHash(output) + } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string appended to an Ethereum + /// signature header. + fn signed_keccak256(&self) -> KeccakHash { + let mut output = [0; 32]; + + let eth_message = { + let message = self.encode(); + + let mut eth_message = + format!("\x19Ethereum Signed Message:\n{}", message.len()) + .into_bytes(); + eth_message.extend_from_slice(&message); + eth_message + }; + + let mut state = Keccak::v256(); + state.update(ð_message); + state.finalize(&mut output); + + KeccakHash(output) + } + } + + /// Represents an Ethereum encoding method equivalent + /// to `abi.encode`. + pub type AbiEncode = [Token; N]; + + impl Encode for AbiEncode { + #[inline] + fn tokenize(&self) -> Vec { + self.to_vec() + } + } + + // TODO: test signatures here once we merge secp keys + #[cfg(test)] + mod tests { + use std::convert::TryInto; + + use ethabi::ethereum_types::U256; + + use super::*; + + /// Checks if we get the same result as `abi.encode`, for some given + /// input data. + #[test] + fn test_abi_encode() { + let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + let expected = hex::decode(&expected[2..]).expect("Test failed"); + let got = AbiEncode::encode(&[ + Token::Uint(U256::from(42u64)), + Token::String("test".into()), + ]); + assert_eq!(expected, got); + } + + /// Sanity check our keccak hash implementation. + #[test] + fn test_keccak_hash_impl() { + let expected = + "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; + assert_eq!( + expected, + &hex::encode({ + let mut st = Keccak::v256(); + let mut output = [0; 32]; + st.update(b"hello"); + st.finalize(&mut output); + output + }) + ); + } + + /// Test that the methods for converting a keccak hash to/from + /// a string type are inverses. + #[test] + fn test_hex_roundtrip() { + let original = "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8"; + let keccak_hash: KeccakHash = original.try_into().expect("Test failed"); + assert_eq!(keccak_hash.to_string().as_str(), original); + } + } +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4f491d0a99..72db38b769 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -10,6 +10,7 @@ pub mod hash; pub mod ibc; pub mod intent; pub mod internal; +pub mod keccak; pub mod key; pub mod matchmaker; pub mod nft; diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 767444f30a..096092f916 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,19 +1,17 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. - -pub mod encoding; - use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use encoding::{AbiEncode, Encode, Token}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, KeccakHash}; +use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::{AbiEncode, Encode, Token}; +use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; #[allow(unused_imports)] @@ -308,10 +306,10 @@ fn compute_hash( mod tag { use serde::{Deserialize, Serialize}; - use super::encoding::{AbiEncode, Encode, Token}; use super::{bheight_to_token, Vext, VotingPowersMapExt}; use crate::proto::SignedSerialize; - use crate::types::ethereum_events::KeccakHash; + use crate::types::keccak::encode::{AbiEncode, Encode, Token}; + use crate::types::keccak::KeccakHash; /// Tag type that indicates we should use [`AbiEncode`] /// to sign data in a [`crate::proto::Signed`] wrapper. diff --git a/shared/src/types/vote_extensions/validator_set_update/encoding.rs b/shared/src/types/vote_extensions/validator_set_update/encoding.rs deleted file mode 100644 index 0ba253ee23..0000000000 --- a/shared/src/types/vote_extensions/validator_set_update/encoding.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! This module defines encoding methods compatible with Ethereum -//! smart contracts. -// TODO: probably move this module elsewhere - -#[doc(inline)] -pub use ethabi::token::Token; -use tiny_keccak::{Hasher, Keccak}; - -use crate::types::ethereum_events::KeccakHash; - -/// Contains a method to encode data to a format compatible with Ethereum. -pub trait Encode { - /// Returns the encoded [`Token`] instances. - fn encode(tokens: &[Token]) -> Vec; - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string. - fn keccak256(tokens: &[Token]) -> KeccakHash { - let mut output = [0; 32]; - - let mut state = Keccak::v256(); - state.update(&Self::encode(tokens)); - state.finalize(&mut output); - - KeccakHash(output) - } - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string appended to an Ethereum - /// signature header. - fn signed_keccak256(tokens: &[Token]) -> KeccakHash { - let mut output = [0; 32]; - - let eth_message = { - let message = Self::encode(tokens); - - let mut eth_message = - format!("\x19Ethereum Signed Message:\n{}", message.len()) - .into_bytes(); - eth_message.extend_from_slice(&message); - eth_message - }; - - let mut state = Keccak::v256(); - state.update(ð_message); - state.finalize(&mut output); - - KeccakHash(output) - } -} - -/// Represents an Ethereum encoding method equivalent -/// to `abi.encode`. -pub struct AbiEncode; - -impl Encode for AbiEncode { - #[inline] - fn encode(tokens: &[Token]) -> Vec { - ethabi::encode(tokens) - } -} - -// TODO: test signatures here once we merge secp keys -#[cfg(test)] -mod tests { - use ethabi::ethereum_types::U256; - - use super::*; - - /// Checks if we get the same result as `abi.encode`, for some given - /// input data. - #[test] - fn test_abi_encode() { - let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; - let expected = hex::decode(&expected[2..]).expect("Test failed"); - let got = AbiEncode::encode(&[ - Token::Uint(U256::from(42u64)), - Token::String("test".into()), - ]); - assert_eq!(expected, got); - } - - /// Sanity check our keccak hash implementation. - #[test] - fn test_keccak_hash_impl() { - let expected = - "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; - assert_eq!( - expected, - &hex::encode({ - let mut st = Keccak::v256(); - let mut output = [0; 32]; - st.update(b"hello"); - st.finalize(&mut output); - output - }) - ); - } -} From 3e01baf863389dca29a111a7279909c2d806f476 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 17:51:53 +0200 Subject: [PATCH 0690/1995] [feat]: Added membership proofs for eth bridge pool --- Cargo.lock | 701 ++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 37 +- shared/src/types/keccak.rs | 19 +- 3 files changed, 268 insertions(+), 489 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 69d5ab3f90..17d28aa12b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,12 +656,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.9.3" @@ -1137,28 +1131,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "chrono-tz" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - [[package]] name = "chunked_transfer" version = "1.4.0" @@ -1321,12 +1293,6 @@ 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" @@ -1504,18 +1470,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 0.14.5", - "rand_core 0.6.3", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.3" @@ -1546,16 +1500,6 @@ 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" @@ -1607,36 +1551,6 @@ dependencies = [ "rand 0.7.3", ] -[[package]] -name = "curl" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.4.4", - "winapi 0.3.9", -] - -[[package]] -name = "curl-sys" -version = "0.4.55+curl-7.83.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi 0.3.9", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1738,15 +1652,6 @@ 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" @@ -1802,12 +1707,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - [[package]] name = "diff" version = "0.1.12" @@ -1917,18 +1816,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.5.2" @@ -1976,24 +1863,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 0.14.5", - "group", - "rand_core 0.6.3", - "sec1", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "embed-resource" version = "1.7.2" @@ -2243,16 +2112,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 2.4.1", -] - [[package]] name = "file-lock" version = "2.1.4" @@ -2647,17 +2506,6 @@ dependencies = [ "regex", ] -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags", - "ignore", - "walkdir", -] - [[package]] name = "gloo-timers" version = "0.2.4" @@ -2670,17 +2518,6 @@ 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" @@ -2857,16 +2694,6 @@ 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" @@ -2934,12 +2761,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humansize" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" - [[package]] name = "hyper" version = "0.10.16" @@ -3047,14 +2868,41 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.12.0" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +dependencies = [ + "bytes 1.1.0", + "derive_more", + "flex-error", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "ics23 0.6.7", + "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=bat/abciplus)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "time 0.3.9", + "tracing 0.1.35", +] + +[[package]] +name = "ibc" +version = "0.12.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto", - "ics23", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ics23 0.6.7", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -3064,25 +2912,54 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", + "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)", "time 0.3.9", "tracing 0.1.35", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.16.0" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ - "base64 0.13.0", "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tonic", +] + +[[package]] +name = "ibc-proto" +version = "0.16.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +dependencies = [ + "bytes 1.1.0", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.137", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tonic", +] + +[[package]] +name = "ics23" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +dependencies = [ + "anyhow", + "bytes 1.1.0", + "hex", + "prost 0.9.0", + "ripemd160", + "sha2 0.9.9", + "sha3 0.9.1", + "sp-std", ] [[package]] @@ -3166,24 +3043,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "ignore" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" -dependencies = [ - "crossbeam-utils 0.8.8", - "globset", - "lazy_static 1.4.0", - "log 0.4.17", - "memchr", - "regex", - "same-file", - "thread_local 1.1.4", - "walkdir", - "winapi-util", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -3366,19 +3225,6 @@ 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" @@ -4123,12 +3969,6 @@ dependencies = [ "serde_yaml", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "match_cfg" version = "0.1.0" @@ -4463,9 +4303,11 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc", - "ibc-proto", - "ics23", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ics23 0.6.7", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -4485,16 +4327,16 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tempfile", - "tendermint", - "tendermint-proto", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", "tiny-keccak", "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", - "variant_access_derive", - "variant_access_traits", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4527,7 +4369,6 @@ dependencies = [ "clarity", "color-eyre", "config", - "curl", "derivative", "directories", "ed25519-consensus", @@ -4575,10 +4416,14 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "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=bat/abciplus)", + "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=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", "tokio", @@ -4587,7 +4432,8 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus)", + "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", @@ -5244,15 +5090,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - [[package]] name = "paste" version = "1.0.7" @@ -5319,40 +5156,6 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1 0.8.2", -] - [[package]] name = "petgraph" version = "0.5.1" @@ -5373,45 +5176,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "phf" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_shared" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" -dependencies = [ - "siphasher", - "uncased", -] - [[package]] name = "pin-project" version = "0.4.29" @@ -6190,17 +5954,6 @@ 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" @@ -6522,18 +6275,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 0.14.5", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "secp256k1" version = "0.21.3" @@ -6878,10 +6619,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" @@ -6889,27 +6626,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - [[package]] name = "smallvec" version = "0.6.14" @@ -6994,7 +6716,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23", + "ics23 0.7.0", "sha2 0.9.9", ] @@ -7177,6 +6899,34 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tendermint" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +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=bat/abciplus)", + "time 0.3.9", + "zeroize", +] + [[package]] name = "tendermint" version = "0.23.5" @@ -7188,12 +6938,10 @@ 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", @@ -7202,11 +6950,24 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "time 0.3.9", "zeroize", ] +[[package]] +name = "tendermint-config" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +dependencies = [ + "flex-error", + "serde 1.0.137", + "serde_json", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "toml", + "url 2.2.2", +] + [[package]] name = "tendermint-config" version = "0.23.5" @@ -7215,11 +6976,24 @@ dependencies = [ "flex-error", "serde 1.0.137", "serde_json", - "tendermint", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", "url 2.2.2", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +dependencies = [ + "derive_more", + "flex-error", + "serde 1.0.137", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "time 0.3.9", +] + [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" @@ -7228,8 +7002,25 @@ dependencies = [ "derive_more", "flex-error", "serde 1.0.137", - "tendermint", - "tendermint-rpc", + "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=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +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", ] @@ -7250,6 +7041,39 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "tendermint-rpc" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +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=bat/abciplus)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "thiserror", + "time 0.3.9", + "tokio", + "tracing 0.1.35", + "url 2.2.2", + "uuid", + "walkdir", +] + [[package]] name = "tendermint-rpc" version = "0.23.5" @@ -7271,9 +7095,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", + "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)", "thiserror", "time 0.3.9", "tokio", @@ -7286,7 +7110,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7294,30 +7118,23 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "time 0.3.9", ] [[package]] -name = "tera" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" +name = "tendermint-testgen" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static 1.4.0", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", + "ed25519-dalek", + "gumdrop", "serde 1.0.137", "serde_json", - "slug", - "unic-segment", + "simple-error", + "tempfile", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "time 0.3.9", ] [[package]] @@ -7790,6 +7607,24 @@ dependencies = [ "tracing 0.1.35", ] +[[package]] +name = "tower-abci" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus#21623a99bdca5b006d53752a1967849bef3b89ea" +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=bat/abciplus)", + "tokio", + "tokio-stream", + "tokio-util 0.6.10", + "tower", + "tracing 0.1.30", + "tracing-tower", +] + [[package]] name = "tower-abci" version = "0.1.0" @@ -7799,7 +7634,7 @@ dependencies = [ "futures 0.3.21", "pin-project 1.0.10", "prost 0.9.0", - "tendermint-proto", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -8126,65 +7961,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "version_check 0.9.4", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "1.4.2" @@ -8345,25 +8121,6 @@ dependencies = [ "version_check 0.9.4", ] -[[package]] -name = "variant_access_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd235ffafb854ed81b49217ce411e850a39628a5d26740ecfb60421c873d834" -dependencies = [ - "lazy_static 1.4.0", - "quote", - "syn", - "tera", - "variant_access_traits", -] - -[[package]] -name = "variant_access_traits" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d75c5a83bb8912dd9c628adf954c9f9bff74a4e170d2c90242f4e56a0d643e" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 8588ae0550..e15de60cb2 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -9,7 +9,8 @@ use eyre::eyre; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; -use crate::types::ethereum_events::KeccakHash; +use crate::types::keccak::{keccak_hash, KeccakHash}; +use crate::types::keccak::encode::Encode; use crate::types::hash::{Hash, keccak_hash}; use crate::types::storage::{DbKeySeg, Key}; use crate::ledger::storage::{Sha256Hasher, StorageHasher}; @@ -66,10 +67,9 @@ pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, /// The underlying storage - store: BTreeMap + store: BTreeMap, } -/// TODO: Hash ABI instead of Borsh impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool pub fn new(root: KeccakHash, store: BTreeMap) -> Self { @@ -90,7 +90,7 @@ impl BridgePoolTree { /// return an error if the key is malformed. pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { let hash = Self::parse_key(key)?; - if hash != keccak_hash(&value.try_to_vec().unwrap()) { + if hash != value.keccak256() { return eyre!("Key does not match hash of the value")?; } _ = self.store.insert(hash, value); @@ -131,9 +131,28 @@ impl BridgePoolTree { &self.store } + /// Create a batched membership proof for the provided keys pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { - for (key, value) in self.store { - + let mut leaves : std::collections::BTreeSet = Default::default(); + for key in keys { + leaves.insert(Self::parse_key(key)?); + } + let mut proof_leaves = vec![]; + let mut proof_hashes = vec![]; + let mut flags = vec![]; + for (hash, value) in self.store { + if leaves.contains(&hash) { + flags.push(true); + proof_leaves.push(value); + } else { + flags.push(false); + proof_hashes.push(hash); + } + } + BridgePoolProof { + proof: proof_hashes, + leaves: proof_leaves, + flags } } @@ -180,8 +199,7 @@ impl BridgePoolProof { let mut proof_pos = 0usize; let mut computed; if self.flags[0] { - leaf = self.leaves[leaf_pos].clone(); - computed = keccak_hash(leaf.try_to_vec().unwrap()); + computed = self.leaves[leaf_pos].keccak256(); leaf_pos += 1; } else { computed = self.proof[proof_pos].clone(); @@ -190,8 +208,7 @@ impl BridgePoolProof { for flag in 1..self.flages.len() { let mut next_hash; if self.flags[flag] { - leaf = self.leaves[leaf_pos].clone(); - next_hash = keccak_hash(leaf.try_to_vec().unwrap()); + next_hash = self.leaves[leaf_pos].keccak256(); leaf_pos += 1; } else { next_hash = self.proof[proof_pos].clone(); diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 185b89956e..70c3fcea3d 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -96,6 +96,17 @@ impl TryFrom<&str> for KeccakHash { } } +/// Hash bytes using Keccak +pub fn keccak_hash(bytes: &[u8]) -> KeccakHash { + let mut output = [0; 32]; + + let mut hasher = Keccak::v256(); + hasher.update(bytes); + hasher.finalize(&mut output); + + KeccakHash(output) +} + /// This module defines encoding methods compatible with Ethereum /// smart contracts. pub mod encode { @@ -120,13 +131,7 @@ pub mod encode { /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. fn keccak256(&self) -> KeccakHash { - let mut output = [0; 32]; - - let mut state = Keccak::v256(); - state.update(self.encode().as_slice()); - state.finalize(&mut output); - - KeccakHash(output) + keccak_hash(self.encode().as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the From 29ac4d5786240c7f6400125c12187dca26f509ec Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 17:53:22 +0200 Subject: [PATCH 0691/1995] [chore]: formatting --- shared/src/types/keccak.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 185b89956e..c4ca5bd978 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -44,7 +44,6 @@ impl Display for KeccakHash { } } - impl From for Hash { fn from(hash: KeccakHash) -> Self { Hash(hash.0) @@ -208,7 +207,8 @@ pub mod encode { #[test] fn test_hex_roundtrip() { let original = "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8"; - let keccak_hash: KeccakHash = original.try_into().expect("Test failed"); + let keccak_hash: KeccakHash = + original.try_into().expect("Test failed"); assert_eq!(keccak_hash.to_string().as_str(), original); } } From f4e798ab5edfd7d860dff41ff6f7451fbc4bdbee Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 26 Sep 2022 17:53:35 +0200 Subject: [PATCH 0692/1995] Update shared/src/types/keccak.rs Co-authored-by: Tiago Carvalho --- shared/src/types/keccak.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index c4ca5bd978..7fc36ad898 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -1,6 +1,6 @@ -//! This module is for hashing Anoma types using the keccak +//! This module is for hashing Namada types using the keccak256 //! hash function in a way that is compatible with smart contracts -//! on Ethereum +//! on Ethereum. use std::convert::TryFrom; use std::fmt::Display; From 4a4b9bbd9ac975dd73c9839d0ce636e38450ff07 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 26 Sep 2022 17:53:41 +0200 Subject: [PATCH 0693/1995] Update shared/src/types/keccak.rs Co-authored-by: Tiago Carvalho --- shared/src/types/keccak.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 7fc36ad898..9d1d7737da 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -21,7 +21,7 @@ pub enum Error { FromStringError(hex::FromHexError), } -/// A Keccak hash +/// Represents a Keccak hash. #[derive( Clone, Debug, From 2a484eee30cb6420ffec93bc10379c0fb765289f Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 26 Sep 2022 17:53:51 +0200 Subject: [PATCH 0694/1995] Update shared/src/types/keccak.rs Co-authored-by: Tiago Carvalho --- shared/src/types/keccak.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 9d1d7737da..23118cc397 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -43,7 +43,6 @@ impl Display for KeccakHash { Ok(()) } } - impl From for Hash { fn from(hash: KeccakHash) -> Self { Hash(hash.0) From 5beb2f7216fd7bb718f07e63ee46c5a01cfe9fa9 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 17:54:37 +0200 Subject: [PATCH 0695/1995] [chore]: added docstring --- shared/src/types/keccak.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 23118cc397..28a8138c21 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -10,7 +10,7 @@ use thiserror::Error; use crate::types::hash::{Hash, HASH_LENGTH}; -#[allow(missing_docs)] +/// Errors for converting / parsing Keccak hashes #[derive(Error, Debug)] pub enum Error { #[error("TEMPORARY error: {error}")] From 49fbf91d03f958490aa83217c88919d3359f2cf2 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 21:09:47 +0100 Subject: [PATCH 0696/1995] Ignore failing end-to-end tests --- tests/src/e2e/eth_bridge_tests.rs | 2 ++ tests/src/e2e/ledger_tests.rs | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index b276dd409e..aa5f657659 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -19,6 +19,8 @@ fn storage_key(path: &str) -> String { } #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn everything() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1f19433ca1..ca81f006e1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -35,6 +35,8 @@ use crate::{run, run_as}; /// combinations from fresh state, the node starts-up successfully for both a /// validator and non-validator user. #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; @@ -63,6 +65,8 @@ fn run_ledger() -> Result<()> { /// 2. Submit a valid token transfer tx /// 3. Check that all the nodes processed the tx with the same result #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes let test = @@ -131,6 +135,8 @@ fn test_node_connectivity() -> Result<()> { /// 3. Check that the node detects this /// 4. Check that the node shuts down #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; @@ -167,6 +173,8 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { /// 5. Reset the ledger's state /// 6. Run the ledger again, it should start from fresh state #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; @@ -234,6 +242,8 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 6. Query token balance /// 7. Query the raw bytes of a storage key #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn ledger_txs_and_queries() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; @@ -406,6 +416,8 @@ fn ledger_txs_and_queries() -> Result<()> { /// 4. Restart the ledger /// 5. Submit and invalid transactions (malformed) #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn invalid_transactions() -> Result<()> { let test = setup::single_node_net()?; @@ -536,6 +548,8 @@ fn invalid_transactions() -> Result<()> { /// 7. Submit a withdrawal of the self-bond /// 8. Submit a withdrawal of the delegation #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn pos_bonds() -> Result<()> { let unbonding_len = 2; let test = setup::network( @@ -729,6 +743,8 @@ fn pos_bonds() -> Result<()> { /// 6. Wait for the pipeline epoch /// 7. Check the new validator's voting power #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn pos_init_validator() -> Result<()> { let pipeline_len = 1; let test = setup::network( @@ -908,6 +924,8 @@ fn pos_init_validator() -> Result<()> { /// 1. Run the ledger node with 10s consensus timeout /// 2. Spawn threads each submitting token transfer tx #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn ledger_many_txs_in_a_block() -> Result<()> { let test = Arc::new(setup::network( |genesis| genesis, @@ -994,6 +1012,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 12. Wait proposal grace and check proposal author funds /// 13. Check governance address funds are 0 #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn proposal_submission() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; @@ -1347,6 +1367,8 @@ fn proposal_submission() -> Result<()> { /// 3. Create an offline vote /// 4. Tally offline #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn proposal_offline() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; @@ -1508,6 +1530,8 @@ fn generate_proposal_json( /// 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 #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn test_genesis_validators() -> Result<()> { use std::collections::HashMap; use std::net::SocketAddr; From e3b56a3edb29b0e2655c94e92a4e245a8da41c6d Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 27 Sep 2022 10:37:16 +0200 Subject: [PATCH 0697/1995] Update shared/src/types/keccak.rs Co-authored-by: James --- shared/src/types/keccak.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 28a8138c21..c8f2ab0a42 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -78,9 +78,7 @@ impl TryFrom for KeccakHash { type Error = Error; fn try_from(string: String) -> Result { - let bytes: Vec = - Vec::from_hex(string).map_err(Error::FromStringError)?; - Self::try_from(bytes.as_slice()) + string.as_str().try_into() } } From 518acc02cda5433f8b3685e03f36d76c7fefc509 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 27 Sep 2022 10:37:22 +0200 Subject: [PATCH 0698/1995] Update shared/src/types/keccak.rs Co-authored-by: James --- shared/src/types/keccak.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index c8f2ab0a42..7dd89dcdc0 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -68,7 +68,7 @@ impl TryFrom<&[u8]> for KeccakHash { ), }); } - let hash: [u8; 32] = + let hash: [u8; HASH_LENGTH] = TryFrom::try_from(value).map_err(Error::ConversionFailed)?; Ok(KeccakHash(hash)) } From 0074a7a6edb51ff6f3fc2fd43a3afa391895a2b0 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 27 Sep 2022 10:53:48 +0200 Subject: [PATCH 0699/1995] [fix]: renamed error type --- shared/src/types/keccak.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 7dd89dcdc0..0c0e16b855 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -1,7 +1,7 @@ //! This module is for hashing Namada types using the keccak256 //! hash function in a way that is compatible with smart contracts //! on Ethereum. -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -12,9 +12,9 @@ use crate::types::hash::{Hash, HASH_LENGTH}; /// Errors for converting / parsing Keccak hashes #[derive(Error, Debug)] -pub enum Error { - #[error("TEMPORARY error: {error}")] - Temporary { error: String }, +pub enum TryFromError { + #[error("Unexpected tx hash length {0}, expected {1}")] + WrongLength(usize, usize), #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), #[error("Failed to convert string into a hash: {0}")] @@ -56,17 +56,11 @@ impl From for KeccakHash { } impl TryFrom<&[u8]> for KeccakHash { - type Error = Error; + type Error = TryFromError; fn try_from(value: &[u8]) -> Result { if value.len() != HASH_LENGTH { - return Err(Error::Temporary { - error: format!( - "Unexpected tx hash length {}, expected {}", - value.len(), - HASH_LENGTH - ), - }); + return Err(TryFromError::WrongLength(value.len(),HASH_LENGTH)); } let hash: [u8; HASH_LENGTH] = TryFrom::try_from(value).map_err(Error::ConversionFailed)?; @@ -75,19 +69,19 @@ impl TryFrom<&[u8]> for KeccakHash { } impl TryFrom for KeccakHash { - type Error = Error; + type Error = TryFromError; - fn try_from(string: String) -> Result { + fn try_from(string: String) -> Result { string.as_str().try_into() } } impl TryFrom<&str> for KeccakHash { - type Error = Error; + type Error = TryFromError;; - fn try_from(string: &str) -> Result { + fn try_from(string: &str) -> Result { let bytes: Vec = - Vec::from_hex(string).map_err(Error::FromStringError)?; + Vec::from_hex(string).map_err(TryFromError::FromStringError)?; Self::try_from(bytes.as_slice()) } } From 8a7ef74996e5a71585370c5ad3629e8e045b05fa Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 27 Sep 2022 10:57:42 +0200 Subject: [PATCH 0700/1995] [fix]: formatting --- shared/src/types/keccak.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 0c0e16b855..8d43ecf9c6 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -11,6 +11,7 @@ use thiserror::Error; use crate::types::hash::{Hash, HASH_LENGTH}; /// Errors for converting / parsing Keccak hashes +#[allow(missing_docs)] #[derive(Error, Debug)] pub enum TryFromError { #[error("Unexpected tx hash length {0}, expected {1}")] @@ -60,10 +61,10 @@ impl TryFrom<&[u8]> for KeccakHash { fn try_from(value: &[u8]) -> Result { if value.len() != HASH_LENGTH { - return Err(TryFromError::WrongLength(value.len(),HASH_LENGTH)); + return Err(TryFromError::WrongLength(value.len(), HASH_LENGTH)); } let hash: [u8; HASH_LENGTH] = - TryFrom::try_from(value).map_err(Error::ConversionFailed)?; + TryFrom::try_from(value).map_err(TryFromError::ConversionFailed)?; Ok(KeccakHash(hash)) } } @@ -77,7 +78,7 @@ impl TryFrom for KeccakHash { } impl TryFrom<&str> for KeccakHash { - type Error = TryFromError;; + type Error = TryFromError; fn try_from(string: &str) -> Result { let bytes: Vec = From 08b4f5d6ab5e237b41f881aa2a9f9a35e99877a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 27 Sep 2022 12:28:30 +0000 Subject: [PATCH 0701/1995] [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 29ccd4fd2b..64c1120efb 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.de38def08090ac1a912d24a1cf0dd03b0b1ad70eca06dcd6745af9c7175be450.wasm", - "tx_ibc.wasm": "tx_ibc.8fd090bdd02727239c63ff6a2a880124320d744972723315983ce855201c605e.wasm", - "tx_init_account.wasm": "tx_init_account.abfeeca2aeecdacf3ff3db022c9aaf85040b38164703a4ee7ad6e42afacdd961.wasm", - "tx_init_nft.wasm": "tx_init_nft.0be8aba244a529f8bb65692bbf0c939550ca9bdd00de6b8df68f1780dd8d7c2d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.67cc820c203ed6f379110c94b7ad16544c03a4d94c77fbbacba1c02dd0869b0b.wasm", - "tx_init_validator.wasm": "tx_init_validator.c9d864bac4cc3090b456f07deb956b2763d8c9fbda38762953398332182ac791.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.453eed7e258e8bda5ff3136ca89e90f3dcc601d1e62dd0991c1d18e404e96d73.wasm", - "tx_transfer.wasm": "tx_transfer.cdad1f1189e5a56aa9cbd542fc983397fd24172c99368623b28001b6c440d2e1.wasm", - "tx_unbond.wasm": "tx_unbond.7498c085a34e8507d46e4eebbd9dcb442d3431685aba15da406f3b74d214e4cf.wasm", - "tx_update_vp.wasm": "tx_update_vp.0e6fe53decf185f6c8715b70a610177c035827370fe472e71a55ff5bf04d7273.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.cfa3277e9be0442f20496a115b31ec8ea96fbc98a26ea65129a68166464a4c40.wasm", - "tx_withdraw.wasm": "tx_withdraw.63b3513b411659e4ab6c63c61bb56aff33f6f39cb42d9f8618cd373320dcc087.wasm", - "vp_nft.wasm": "vp_nft.2f21e99208c9c0842106b67fa4f113e9aa76ce009a486788e5458247fd7ef1ba.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7135a8139a99f1e6cdb3c44e5a6824ddfb8c3db158b3284191d4f53d18f38710.wasm", - "vp_token.wasm": "vp_token.0cd22c3ba1c234bfbfc3a0d0913a9dc230c22ec7f629ba74907e8af40de687a4.wasm", - "vp_user.wasm": "vp_user.112577f72fcaf46c25389f67a4a643e71d61f7fb01447970acdf96a8ad72e9ec.wasm" + "tx_from_intent.wasm": "tx_from_intent.4840e88eed56a67a208dddaf6dbceec86e4e9611743f31bbbb9b03a316c08a73.wasm", + "tx_ibc.wasm": "tx_ibc.b019b09487f5b67a15ec82ab9ad6b124552aca2aca633d41c26b9d8c92335bb5.wasm", + "tx_init_account.wasm": "tx_init_account.7036f646916e5a7881b63e35c5013440af911aa3b2203239887c528d471f5086.wasm", + "tx_init_nft.wasm": "tx_init_nft.0d2700e73a91c4ef4937bdf8b27fdc7b8f35315691cf269596b6e9c1122a81b1.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.79e9b3cdf4361abb537c6773cee2d95ae7c3c9a2383945147d71e2b1c848e9e2.wasm", + "tx_init_validator.wasm": "tx_init_validator.3a84149e20001aefc7cc9518b5ced08887a99795c714db6151546abc4f28eb78.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.4e396d0992281a9e6a7e7ec26e78a2d094a89277968c8c045ab40b51680ae5ac.wasm", + "tx_transfer.wasm": "tx_transfer.0d6acdf8fb16785a4ff88c124b687393e55197db24a1c152193b2f221e69d6ac.wasm", + "tx_unbond.wasm": "tx_unbond.c22ef36ee25763cb039f63902afd1a926c97696d1528e0f87713363dbd00cf97.wasm", + "tx_update_vp.wasm": "tx_update_vp.5c15f73b0cdbea3a8de97855535f4013f969249bbf87e33291609cbdfba536a5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d16c745445ccb8edf0080faefa6b573b704ad14159e028bfec1852f484105212.wasm", + "tx_withdraw.wasm": "tx_withdraw.9634d84b2257d438bde3ffccf1742097582c996c4a3f4bc7aaa2a3729b057b16.wasm", + "vp_nft.wasm": "vp_nft.9885f3976c876722219b13ed47babf4dee798fd07c8f89df90a9e52a3e9d7859.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9e7803e38edf8103387a3bcc9709cceb76bc1e0170bbaba0db52461fe6cc7b5a.wasm", + "vp_token.wasm": "vp_token.8b6ea63c0f9cacb96fc7ec2855ec559701d7ed324827963ea7b908ccf5b12db6.wasm", + "vp_user.wasm": "vp_user.6ef2298f4a4e2d1537e418366fb7626479ffb8c01a6a94e311dfecf3ee40d6df.wasm" } \ No newline at end of file From 9945b335714b5adb5fda55e39bd88af87a9495f0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 19:18:20 +0100 Subject: [PATCH 0702/1995] Add semver crate as a dependency --- Cargo.lock | 7 ++++--- apps/Cargo.toml | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd762c657f..0a74ebb880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4411,6 +4411,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "semver 1.0.14", "serde 1.0.144", "serde_bytes", "serde_json", @@ -6152,7 +6153,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.13", + "semver 1.0.14", ] [[package]] @@ -6385,9 +6386,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 76d808d637..f85e8b2ba8 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -117,6 +117,7 @@ serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" serde_json = {version = "1.0.62", features = ["raw_value"]} serde_regex = "1.1.0" +semver = "1.0.14" 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"]} From 484b074559ac0f57f5bb873e7d2864dbf6b8d24f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 19:22:15 +0100 Subject: [PATCH 0703/1995] Check and log the Tendermint version when the ledger starts --- apps/src/lib/node/ledger/tendermint_node.rs | 100 +++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 324fd8f6a4..5e265a489b 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -4,10 +4,12 @@ use std::process::Stdio; use std::str::FromStr; use borsh::BorshSerialize; +use eyre::{eyre, Context}; use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::time::DateTimeUtc; +use semver::{Version, VersionReq}; use serde_json::json; use thiserror::Error; use tokio::fs::{self, File, OpenOptions}; @@ -24,6 +26,44 @@ use crate::facade::tendermint_config::{ /// Env. var to output Tendermint log to stdout pub const ENV_VAR_TM_STDOUT: &str = "ANOMA_TM_STDOUT"; +#[cfg(feature = "abciplus")] +pub const VERSION_REQUIREMENTS: &str = ">= 0.37.0-alpha.2, <0.38.0"; +#[cfg(feature = "abcipp")] +// TODO: update from our v0.36-based fork to v0.38 for full ABCI++ +pub const VERSION_REQUIREMENTS: &str = "= 0.1.1-abcipp"; + +/// Return the Tendermint version requirements for this build of Namada +fn version_requirements() -> VersionReq { + VersionReq::parse(VERSION_REQUIREMENTS) + .expect("Unable to parse Tendermint version requirements!") +} + +/// Return the [`Version`] of the Tendermint binary specified at +/// `tendermint_path` +async fn get_version(tendermint_path: &str) -> eyre::Result { + let version = run_version_command(tendermint_path).await?; + parse_version(&version) +} + +/// Runs `tendermint version` and returns the output as a string +async fn run_version_command(tendermint_path: &str) -> eyre::Result { + let output = Command::new(&tendermint_path) + .arg("version") + .output() + .await?; + let output = String::from_utf8(output.stdout)?; + Ok(output) +} + +/// Parses the raw output of `tendermint version` (e.g. "v0.37.0-alpha.2\n") +/// into a [`Version`] +fn parse_version(version_cmd_output: &str) -> eyre::Result { + let version_str = version_cmd_output.trim_end().trim_start_matches('v'); + Version::parse(version_str).wrap_err_with(|| { + eyre!("Failed to parse Tendermint version from string: {version_str}") + }) +} + #[derive(Error, Debug)] pub enum Error { #[error("Failed to initialize Tendermint: {0}")] @@ -74,8 +114,29 @@ pub async fn run( tokio::sync::oneshot::Sender<()>, >, ) -> Result<()> { - let home_dir_string = home_dir.to_string_lossy().to_string(); let tendermint_path = from_env_or_default()?; + + let version = get_version(&tendermint_path).await.map_err(|err| { + Error::Runtime(format!("Failed to check Tendermint version: {:?}", err)) + })?; + let version_reqs = version_requirements(); + if version_reqs.matches(&version) { + tracing::info!( + %tendermint_path, + %version, + %version_reqs, + "Running with supported Tendermint version", + ); + } else { + tracing::warn!( + %tendermint_path, + %version, + %version_reqs, + "Running with a Tendermint version which is not supported" + ); + } + + let home_dir_string = home_dir.to_string_lossy().to_string(); let mode = config.tendermint_mode.to_str().to_owned(); #[cfg(feature = "dev")] @@ -415,3 +476,40 @@ async fn write_tm_genesis( .await .expect("Couldn't write the Tendermint genesis file"); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + /// This is really just a smoke test to make sure the + /// [`VERSION_REQUIREMENTS`] constant is always parseable to a + /// [`VersionReq`] + fn test_version_requirements() { + _ = version_requirements(); + } + + #[test] + fn test_parse_version() { + let version_str = "v0.37.0-alpha.2\n"; + let version = parse_version(version_str).unwrap(); + assert_eq!(version.major, 0); + assert_eq!(version.minor, 37); + assert_eq!(version.patch, 0); + + let version_str = "v0.1.1-abcipp\n"; + let version = parse_version(version_str).unwrap(); + assert_eq!(version.major, 0); + assert_eq!(version.minor, 1); + assert_eq!(version.patch, 1); + + let version_str = "v0.38.1\n"; + let version = parse_version(version_str).unwrap(); + assert_eq!(version.major, 0); + assert_eq!(version.minor, 38); + assert_eq!(version.patch, 1); + + let version_str = "unparseable"; + assert!(parse_version(version_str).is_err()); + } +} From 46b626d2d3a32ad571751b5aa21aee7c87242fc2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 13:04:02 +0100 Subject: [PATCH 0704/1995] Fix validation of block height in vote extensions --- .../lib/node/ledger/shell/vote_extensions.rs | 1 - .../shell/vote_extensions/eth_events.rs | 27 +++++++++++++++---- .../shell/vote_extensions/val_set_update.rs | 19 ++++++++++--- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index afbb707847..51409dec4b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -26,7 +26,6 @@ const VALIDATOR_EXPECT_MSG: &str = "Only validators receive this method call."; pub enum VoteExtensionError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, - #[cfg(feature = "abcipp")] #[error( "The vote extension has an unexpected sequence number (e.g. block \ height)." diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index fe1bcd20f9..c440302073 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -78,20 +78,36 @@ where if have_dupes_or_non_sorted { tracing::error!( %validator, - "Found duplicate or non-sorted Ethereum events in a vote extension from some validator" + "Found duplicate or non-sorted Ethereum events in a vote extension from \ + some validator" ); return Err(VoteExtensionError::HaveDupesOrNonSorted); } // get the public key associated with this validator - let epoch = self.storage.get_epoch(last_height); + // + // NOTE(not(feature = "abciplus")): for ABCI++, we should pass + // `last_height` here, instead of `ext.data.block_height` + let ext_height_epoch = + match self.storage.get_epoch(ext.data.block_height) { + Some(epoch) => epoch, + _ => { + tracing::error!( + block_height = ?ext.data.block_height, + "The epoch of the Ethereum events vote extension's \ + block height should always be known", + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } + }; let (voting_power, pk) = self .storage - .get_validator_from_address(validator, epoch) + .get_validator_from_address(validator, Some(ext_height_epoch)) .map_err(|err| { tracing::error!( ?err, %validator, - "Could not get public key from Storage for some validator, while validating Ethereum events vote extension" + "Could not get public key from Storage for some validator, \ + while validating Ethereum events vote extension" ); VoteExtensionError::PubKeyNotInStorage })?; @@ -101,7 +117,8 @@ where tracing::error!( ?err, %validator, - "Failed to verify the signature of an Ethereum events vote extension issued by some validator" + "Failed to verify the signature of an Ethereum events vote \ + extension issued by some validator" ); VoteExtensionError::VerifySigFailed }) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index fc07a19f76..528e22a5c4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -73,12 +73,23 @@ where } // get the public key associated with this validator let validator = &ext.data.validator_addr; - let last_height_epoch = self.storage.get_epoch(last_height).expect( - "The epoch of the last block height should always be known", - ); + // NOTE(not(feature = "abciplus")): for ABCI++, we should pass + // `last_height` here, instead of `ext.data.block_height` + let ext_height_epoch = + match self.storage.get_epoch(ext.data.block_height) { + Some(epoch) => epoch, + _ => { + tracing::error!( + block_height = ?ext.data.block_height, + "The epoch of the Ethereum events vote extension's \ + block height should always be known", + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } + }; let (voting_power, pk) = self .storage - .get_validator_from_address(validator, Some(last_height_epoch)) + .get_validator_from_address(validator, Some(ext_height_epoch)) .map_err(|err| { tracing::error!( ?err, From 66cf3853f57f1481d03298a8808d2249e290569c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 13:06:37 +0100 Subject: [PATCH 0705/1995] Shorten log line length in the code --- .../lib/node/ledger/shell/vote_extensions/val_set_update.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 528e22a5c4..c81363577f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -94,7 +94,8 @@ where tracing::error!( ?err, %validator, - "Could not get public key from Storage for some validator, while validating validator set update vote extension" + "Could not get public key from Storage for some validator, \ + while validating validator set update vote extension" ); VoteExtensionError::PubKeyNotInStorage })?; @@ -104,7 +105,8 @@ where tracing::error!( ?err, %validator, - "Failed to verify the signature of a validator set update vote extension issued by some validator" + "Failed to verify the signature of a validator set update vote \ + extension issued by some validator" ); VoteExtensionError::VerifySigFailed }) From d970b5fd29e38e1e1441bf454cd955995cdc770a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 13:12:13 +0100 Subject: [PATCH 0706/1995] Fix log output --- .../shell/vote_extensions/val_set_update.rs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index c81363577f..c0028fea10 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -75,18 +75,20 @@ where let validator = &ext.data.validator_addr; // NOTE(not(feature = "abciplus")): for ABCI++, we should pass // `last_height` here, instead of `ext.data.block_height` - let ext_height_epoch = - match self.storage.get_epoch(ext.data.block_height) { - Some(epoch) => epoch, - _ => { - tracing::error!( - block_height = ?ext.data.block_height, - "The epoch of the Ethereum events vote extension's \ - block height should always be known", - ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); - } - }; + let ext_height_epoch = match self + .storage + .get_epoch(ext.data.block_height) + { + Some(epoch) => epoch, + _ => { + tracing::error!( + block_height = ?ext.data.block_height, + "The epoch of the validator set update vote extension's \ + block height should always be known", + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } + }; let (voting_power, pk) = self .storage .get_validator_from_address(validator, Some(ext_height_epoch)) From 1f6badd58b386ea8c7a8b02cd82ddd45bf6d69f6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 15:25:47 +0100 Subject: [PATCH 0707/1995] Shim out voting power checks --- .../shell/vote_extensions/eth_events.rs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index c440302073..7b38f2e8a5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -10,6 +10,7 @@ use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; +#[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; use super::*; @@ -193,14 +194,16 @@ where "The epoch of the last block height should always be known", ); + #[cfg(feature = "abcipp")] let total_voting_power = u64::from(self.storage.get_total_voting_power(Some(vexts_epoch))); + #[cfg(feature = "abcipp")] let mut voting_power = FractionalVotingPower::default(); let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); - for (validator_voting_power, vote_extension) in + for (_validator_voting_power, vote_extension) in self.filter_invalid_eth_events_vexts(vote_extensions) { let validator_addr = vote_extension.data.validator_addr; @@ -208,15 +211,18 @@ where let block_height = vote_extension.data.block_height; // update voting power - let validator_voting_power = u64::from(validator_voting_power); - voting_power += FractionalVotingPower::new( - validator_voting_power, - total_voting_power, - ) - .expect( - "The voting power we obtain from storage should always be \ - valid", - ); + #[cfg(feature = "abcipp")] + { + let validator_voting_power = u64::from(validator_voting_power); + voting_power += FractionalVotingPower::new( + validator_voting_power, + total_voting_power, + ) + .expect( + "The voting power we obtain from storage should always be \ + valid", + ); + } // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { From b4d3e951917b7106ca4242537274cee810f3ecaa Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 15:26:45 +0100 Subject: [PATCH 0708/1995] Filter out extensions containing Eth events with repeated votes --- .../lib/node/ledger/shell/vote_extensions.rs | 2 ++ .../shell/vote_extensions/eth_events.rs | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 51409dec4b..9f1f1de9eb 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -42,6 +42,8 @@ pub enum VoteExtensionError { PubKeyNotInStorage, #[error("The vote extension's signature is invalid.")] VerifySigFailed, + #[error("A validator tried voting more than once for the same event.")] + VotedMoreThanOnce, } impl Shell diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 7b38f2e8a5..f7e89ad525 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -1,6 +1,6 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; @@ -156,9 +156,24 @@ where VoteExtensionError, >, > + 'iter { - vote_extensions.into_iter().map(|vote_extension| { + // NOTE(not(feature = "abcipp")): we can remove this `BTreeSet` + // and its associated code once we purge `abciplus` from the ledger + let mut observed = BTreeSet::new(); + let contains = |set: &mut BTreeSet<_>, elem| !set.insert(elem); + vote_extensions.into_iter().map(move |ext| { + for event in ext.data.ethereum_events.iter().cloned() { + let validator_event = (ext.data.validator_addr.clone(), event); + if contains(&mut observed, validator_event) { + tracing::error!( + validator = %ext.data.validator_addr, + "A validator tried voting more than once on the same \ + event, at different block heights." + ); + return Err(VoteExtensionError::VotedMoreThanOnce); + } + } self.validate_eth_events_vext_and_get_it_back( - vote_extension, + ext, self.storage.last_height, ) }) From c08772865fee21ed7c6c2cb6991616d719f29a2a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 15:26:59 +0100 Subject: [PATCH 0709/1995] Fix abcipp shimming --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index f7e89ad525..03c85b16c9 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -204,6 +204,7 @@ where return None; } + #[cfg(feature = "abcipp")] let vexts_epoch = self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", From 5abf14d9c28f3a0b0fd6d76d0ea729413cf88b88 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 15:57:44 +0100 Subject: [PATCH 0710/1995] WIP: Test filtering of eth events vexts containing events with duped votes --- .../lib/node/ledger/shell/prepare_proposal.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index de33ebc38b..7b67cb24b0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1047,4 +1047,63 @@ mod test_prepare_proposal { assert_eq!(received, expected_txs); } } + + /// Test if we are filtering out duped votes for the + /// same Ethereum event at different block heights. + #[cfg(not(feature = "abcipp"))] + #[test] + fn test_prepare_proposal_filter_duped_eth_event_votes() { + use assert_matches::assert_matches; + + const LAST_HEIGHT: BlockHeight = BlockHeight(5); + const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + + let (mut shell, _recv, _) = test_utils::setup(); + + // artificially change the block height + shell.storage.last_height = LAST_HEIGHT; + + let (protocol_key, _) = wallet::defaults::validator_keys(); + let validator_addr = wallet::defaults::validator_address(); + + let ethereum_event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let signed_ext_1 = { + let ext = ethereum_events::Vext { + validator_addr: validator_addr.clone(), + block_height: PRED_LAST_HEIGHT, + ethereum_events: vec![ethereum_event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let signed_ext_2 = { + let ext = ethereum_events::Vext { + validator_addr, + block_height: LAST_HEIGHT, + ethereum_events: vec![ethereum_event], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + + let signatures = shell + .compress_ethereum_events(vec![signed_ext_1, signed_ext_2]) + .expect("Test failed") + .signatures; + + assert_eq!(signatures.len(), 1); + + let height = signatures + .into_iter() + .map(|(_, height)| height) + .by_ref() + .next(); + + assert_matches!(height, BlockHeight(PRED_LAST_HEIGHT + } } From f83bd08a1bc501608356974bc3d7e03085d447bd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 16:28:01 +0100 Subject: [PATCH 0711/1995] Disable duped votes test temporarily --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7b67cb24b0..549faf8369 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1052,9 +1052,8 @@ mod test_prepare_proposal { /// same Ethereum event at different block heights. #[cfg(not(feature = "abcipp"))] #[test] + #[ignore] fn test_prepare_proposal_filter_duped_eth_event_votes() { - use assert_matches::assert_matches; - const LAST_HEIGHT: BlockHeight = BlockHeight(5); const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); @@ -1104,6 +1103,9 @@ mod test_prepare_proposal { .by_ref() .next(); - assert_matches!(height, BlockHeight(PRED_LAST_HEIGHT + // ``` + // use assert_matches::assert_matches; + // assert_matches!(height, BlockHeight(PRED_LAST_HEIGHT + // ``` } } From 0cdf569322b0a3fc32e3670f4c01abc4d2484471 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 16:29:05 +0100 Subject: [PATCH 0712/1995] Check if we signed over a block height <= last height in eth events vexts --- .../shell/vote_extensions/eth_events.rs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 03c85b16c9..72905789cd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -23,14 +23,14 @@ where H: StorageHasher + Sync + 'static, { /// Validates an Ethereum events vote extension issued at the provided - /// block height + /// block height. /// /// Checks that at epoch of the provided height: - /// * The Tendermint address corresponds to an active validator - /// * The validator correctly signed the extension - /// * The validator signed over the correct height inside of the extension + /// * The Tendermint address corresponds to an active validator. + /// * The validator correctly signed the extension. + /// * The validator signed over the correct height inside of the extension. /// * There are no duplicate Ethereum events in this vote extension, and - /// the events are sorted in ascending order + /// the events are sorted in ascending order. #[allow(dead_code)] #[inline] pub fn validate_eth_events_vext( @@ -55,10 +55,21 @@ where > { #[cfg(feature = "abcipp")] if ext.data.block_height != last_height { - let ext_height = ext.data.block_height; tracing::error!( + ext_height = ?ext.data.block_height, + ?last_height, "Ethereum events vote extension issued for a block height \ - {ext_height} different from the expected height {last_height}" + different from the expected last height." + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } + #[cfg(not(feature = "abcipp"))] + if ext.data.block_height > last_height { + tracing::error!( + ext_height = ?ext.data.block_height, + ?last_height, + "Ethereum events vote extension issued for a block height \ + higher than the chain's last height." ); return Err(VoteExtensionError::UnexpectedSequenceNumber); } From 6f26b0d00e465aa78d005a7e11b8ffe5786b3fee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 09:35:55 +0100 Subject: [PATCH 0713/1995] Fix clippy error --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 549faf8369..5a8b1882e8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1103,6 +1103,8 @@ mod test_prepare_proposal { .by_ref() .next(); + let _ = height; + // ``` // use assert_matches::assert_matches; // assert_matches!(height, BlockHeight(PRED_LAST_HEIGHT From 73451c83f3b39971e22ef3c4645ce6e2cdab2d93 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 09:37:42 +0100 Subject: [PATCH 0714/1995] Workaround repeated eth events across different block heights in vexts --- .../shell/vote_extensions/eth_events.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 72905789cd..c4bd843e50 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -230,6 +230,27 @@ where let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); + // we sort these extensions such that we keep exts with lower block + // heights when we find repeated events across different block heights + // contained within them + #[cfg(not(feature = "abcipp"))] + let vote_extensions = { + use std::cmp::Ordering; + let mut vote_extensions = vote_extensions; + vote_extensions.sort_by(|ext_1, ext_2| { + let ord = + ext_1.data.validator_addr.cmp(&ext_2.data.validator_addr); + if let Ordering::Equal = ord { + return ext_1 + .data + .block_height + .cmp(&ext_2.data.block_height); + } + ord + }); + vote_extensions + }; + for (_validator_voting_power, vote_extension) in self.filter_invalid_eth_events_vexts(vote_extensions) { From 8d95100e88766a26d5752d30d4e2746054f492a8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:05:00 +0100 Subject: [PATCH 0715/1995] Test filtering of eth events vexts containing events with duped votes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5a8b1882e8..4ffcd96720 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1052,7 +1052,6 @@ mod test_prepare_proposal { /// same Ethereum event at different block heights. #[cfg(not(feature = "abcipp"))] #[test] - #[ignore] fn test_prepare_proposal_filter_duped_eth_event_votes() { const LAST_HEIGHT: BlockHeight = BlockHeight(5); const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); @@ -1099,15 +1098,11 @@ mod test_prepare_proposal { let height = signatures .into_iter() - .map(|(_, height)| height) + .map(|((_, height), _)| height) .by_ref() - .next(); + .next() + .unwrap(); - let _ = height; - - // ``` - // use assert_matches::assert_matches; - // assert_matches!(height, BlockHeight(PRED_LAST_HEIGHT - // ``` + assert_eq!(height, PRED_LAST_HEIGHT); } } From 933b2ddf038a9fd6199020ddf2cbf17fdb73fa64 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:07:34 +0100 Subject: [PATCH 0716/1995] Test sorting in the unit test --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4ffcd96720..ff1aa86a08 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1090,7 +1090,7 @@ mod test_prepare_proposal { }; let signatures = shell - .compress_ethereum_events(vec![signed_ext_1, signed_ext_2]) + .compress_ethereum_events(vec![signed_ext_2, signed_ext_1]) .expect("Test failed") .signatures; From 7111e6805a45745da68faa5e901902fd7e0517f6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:25:33 +0100 Subject: [PATCH 0717/1995] Small fix --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index c4bd843e50..a229226673 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -261,7 +261,7 @@ where // update voting power #[cfg(feature = "abcipp")] { - let validator_voting_power = u64::from(validator_voting_power); + let validator_voting_power = u64::from(_validator_voting_power); voting_power += FractionalVotingPower::new( validator_voting_power, total_voting_power, From 1f4a4180264309807b6bc4d3e2dd2069a5983436 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:27:17 +0100 Subject: [PATCH 0718/1995] Shim out voting power checks in valset upd vexts --- .../shell/vote_extensions/val_set_update.rs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index c0028fea10..e9c93dba1a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -7,6 +7,7 @@ use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; +#[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; use super::*; @@ -164,19 +165,22 @@ where return None; } + #[cfg(feature = "abcipp")] let vexts_epoch = self.storage.get_epoch(self.storage.last_height).expect( "The epoch of the last block height should always be known", ); + #[cfg(feature = "abcipp")] let total_voting_power = u64::from(self.storage.get_total_voting_power(Some(vexts_epoch))); + #[cfg(feature = "abcipp")] let mut voting_power = FractionalVotingPower::default(); let mut voting_powers = None; let mut signatures = HashMap::new(); - for (validator_voting_power, mut vote_extension) in + for (_validator_voting_power, mut vote_extension) in self.filter_invalid_valset_upd_vexts(vote_extensions) { if voting_powers.is_none() { @@ -190,15 +194,18 @@ where let block_height = vote_extension.data.block_height; // update voting power - let validator_voting_power = u64::from(validator_voting_power); - voting_power += FractionalVotingPower::new( - validator_voting_power, - total_voting_power, - ) - .expect( - "The voting power we obtain from storage should always be \ - valid", - ); + #[cfg(feature = "abcipp")] + { + let validator_voting_power = u64::from(_validator_voting_power); + voting_power += FractionalVotingPower::new( + validator_voting_power, + total_voting_power, + ) + .expect( + "The voting power we obtain from storage should always be \ + valid", + ); + } // register the signature of `validator_addr` let addr = validator_addr.clone(); From 2bf288dc0867174002ea645698d849ec7b8106d0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:36:20 +0100 Subject: [PATCH 0719/1995] Some docstring fixes --- .../ledger/shell/vote_extensions/eth_events.rs | 3 +++ .../shell/vote_extensions/val_set_update.rs | 15 ++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index a229226673..e5620d95e8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -156,6 +156,9 @@ where /// and returns another iterator. The latter yields /// valid Ethereum events vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. + /// + /// If `abciplus` is enabled, we check if a validator has not voted more + /// than once for some Ethereum event, at different block heights. #[inline] pub fn validate_eth_events_vext_list<'iter>( &'iter self, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index e9c93dba1a..b66c5f1103 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -19,17 +19,18 @@ where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - /// Validates a validator set update vote extension issued for the new - /// epoch provided as an argument + /// Validates a validator set update vote extension issued for the + /// succeeding epoch of the block height provided as an argument. /// /// Checks that: - /// * The signing validator was active at the preceding epoch - /// * The validator correctly signed the extension - /// * The validator signed over the block height inside of the extension + /// * The signing validator was active at the preceding epoch. + /// * The validator correctly signed the extension, with its Ethereum hot + /// key. + /// * The validator signed over the block height inside of the extension. /// * The voting powers in the vote extension correspond to the voting - /// powers of the validators of the new epoch + /// powers of the validators of the new epoch. /// * The voting powers are normalized to `2^32`, and sorted in descending - /// order + /// order. #[inline] #[allow(dead_code)] pub fn validate_valset_upd_vext( From dff6f63ff8c3b4daa38a7344cd8c4de6acf9949a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 10:39:44 +0100 Subject: [PATCH 0720/1995] Make sure we do not issue valset upd vexts for invalid heights --- .../shell/vote_extensions/val_set_update.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index b66c5f1103..33c7a816cd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -61,11 +61,21 @@ where > { #[cfg(feature = "abcipp")] if ext.data.block_height != last_height { - let ext_height = ext.data.block_height; tracing::error!( + ext_height = ?ext.data.block_height, + ?last_height, "Validator set update vote extension issued for a block \ - height {ext_height} different from the expected height \ - {last_height}" + height different from the expected last height.", + ); + return Err(VoteExtensionError::UnexpectedSequenceNumber); + } + #[cfg(not(feature = "abcipp"))] + if ext.data.block_height > last_height { + tracing::error!( + ext_height = ?ext.data.block_height, + ?last_height, + "Validator set update vote extension issued for a block \ + height higher than the chain's last height.", ); return Err(VoteExtensionError::UnexpectedSequenceNumber); } From 5e06197e889836c401498d0048166d34aa3caba4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 09:07:20 +0100 Subject: [PATCH 0721/1995] Remove duped Eth events votes checking --- .../lib/node/ledger/shell/prepare_proposal.rs | 58 ------------------- .../lib/node/ledger/shell/vote_extensions.rs | 2 - .../shell/vote_extensions/eth_events.rs | 45 +------------- 3 files changed, 3 insertions(+), 102 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ff1aa86a08..de33ebc38b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1047,62 +1047,4 @@ mod test_prepare_proposal { assert_eq!(received, expected_txs); } } - - /// Test if we are filtering out duped votes for the - /// same Ethereum event at different block heights. - #[cfg(not(feature = "abcipp"))] - #[test] - fn test_prepare_proposal_filter_duped_eth_event_votes() { - const LAST_HEIGHT: BlockHeight = BlockHeight(5); - const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); - - let (mut shell, _recv, _) = test_utils::setup(); - - // artificially change the block height - shell.storage.last_height = LAST_HEIGHT; - - let (protocol_key, _) = wallet::defaults::validator_keys(); - let validator_addr = wallet::defaults::validator_address(); - - let ethereum_event = EthereumEvent::TransfersToNamada { - nonce: 1u64.into(), - transfers: vec![], - }; - let signed_ext_1 = { - let ext = ethereum_events::Vext { - validator_addr: validator_addr.clone(), - block_height: PRED_LAST_HEIGHT, - ethereum_events: vec![ethereum_event.clone()], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - ext - }; - let signed_ext_2 = { - let ext = ethereum_events::Vext { - validator_addr, - block_height: LAST_HEIGHT, - ethereum_events: vec![ethereum_event], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - ext - }; - - let signatures = shell - .compress_ethereum_events(vec![signed_ext_2, signed_ext_1]) - .expect("Test failed") - .signatures; - - assert_eq!(signatures.len(), 1); - - let height = signatures - .into_iter() - .map(|((_, height), _)| height) - .by_ref() - .next() - .unwrap(); - - assert_eq!(height, PRED_LAST_HEIGHT); - } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 9f1f1de9eb..51409dec4b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -42,8 +42,6 @@ pub enum VoteExtensionError { PubKeyNotInStorage, #[error("The vote extension's signature is invalid.")] VerifySigFailed, - #[error("A validator tried voting more than once for the same event.")] - VotedMoreThanOnce, } impl Shell diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index e5620d95e8..21a86e06b6 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -1,6 +1,6 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; @@ -156,9 +156,6 @@ where /// and returns another iterator. The latter yields /// valid Ethereum events vote extensions, or the reason why these /// are invalid, in the form of a [`VoteExtensionError`]. - /// - /// If `abciplus` is enabled, we check if a validator has not voted more - /// than once for some Ethereum event, at different block heights. #[inline] pub fn validate_eth_events_vext_list<'iter>( &'iter self, @@ -170,24 +167,9 @@ where VoteExtensionError, >, > + 'iter { - // NOTE(not(feature = "abcipp")): we can remove this `BTreeSet` - // and its associated code once we purge `abciplus` from the ledger - let mut observed = BTreeSet::new(); - let contains = |set: &mut BTreeSet<_>, elem| !set.insert(elem); - vote_extensions.into_iter().map(move |ext| { - for event in ext.data.ethereum_events.iter().cloned() { - let validator_event = (ext.data.validator_addr.clone(), event); - if contains(&mut observed, validator_event) { - tracing::error!( - validator = %ext.data.validator_addr, - "A validator tried voting more than once on the same \ - event, at different block heights." - ); - return Err(VoteExtensionError::VotedMoreThanOnce); - } - } + vote_extensions.into_iter().map(move |vote_extension| { self.validate_eth_events_vext_and_get_it_back( - ext, + vote_extension, self.storage.last_height, ) }) @@ -233,27 +215,6 @@ where let mut event_observers = BTreeMap::new(); let mut signatures = HashMap::new(); - // we sort these extensions such that we keep exts with lower block - // heights when we find repeated events across different block heights - // contained within them - #[cfg(not(feature = "abcipp"))] - let vote_extensions = { - use std::cmp::Ordering; - let mut vote_extensions = vote_extensions; - vote_extensions.sort_by(|ext_1, ext_2| { - let ord = - ext_1.data.validator_addr.cmp(&ext_2.data.validator_addr); - if let Ordering::Equal = ord { - return ext_1 - .data - .block_height - .cmp(&ext_2.data.block_height); - } - ord - }); - vote_extensions - }; - for (_validator_voting_power, vote_extension) in self.filter_invalid_eth_events_vexts(vote_extensions) { From e245141c5438c0d06df35877643d2ce328eebe07 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 09:27:46 +0100 Subject: [PATCH 0722/1995] Split UnexpectedSequenceNumber into two error kinds --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 9 ++++----- .../lib/node/ledger/shell/vote_extensions/eth_events.rs | 6 +++--- .../node/ledger/shell/vote_extensions/val_set_update.rs | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 51409dec4b..a337b7d15e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -26,11 +26,10 @@ const VALIDATOR_EXPECT_MSG: &str = "Only validators receive this method call."; pub enum VoteExtensionError { #[error("The vote extension was issued at block height 0.")] IssuedAtGenesis, - #[error( - "The vote extension has an unexpected sequence number (e.g. block \ - height)." - )] - UnexpectedSequenceNumber, + #[error("The vote extension was issued for an unexpected block height.")] + UnexpectedBlockHeight, + #[error("The vote extension was issued for an unexpected epoch.")] + UnexpectedEpoch, #[error( "The vote extension contains duplicate or non-sorted Ethereum events." )] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 21a86e06b6..11172d5865 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -61,7 +61,7 @@ where "Ethereum events vote extension issued for a block height \ different from the expected last height." ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedBlockHeight); } #[cfg(not(feature = "abcipp"))] if ext.data.block_height > last_height { @@ -71,7 +71,7 @@ where "Ethereum events vote extension issued for a block height \ higher than the chain's last height." ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedBlockHeight); } if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); @@ -108,7 +108,7 @@ where "The epoch of the Ethereum events vote extension's \ block height should always be known", ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedEpoch); } }; let (voting_power, pk) = self diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 33c7a816cd..aafaf1ed4d 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -67,7 +67,7 @@ where "Validator set update vote extension issued for a block \ height different from the expected last height.", ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedBlockHeight); } #[cfg(not(feature = "abcipp"))] if ext.data.block_height > last_height { @@ -77,7 +77,7 @@ where "Validator set update vote extension issued for a block \ height higher than the chain's last height.", ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedBlockHeight); } if last_height.0 == 0 { tracing::error!("Dropping vote extension issued at genesis"); @@ -98,7 +98,7 @@ where "The epoch of the validator set update vote extension's \ block height should always be known", ); - return Err(VoteExtensionError::UnexpectedSequenceNumber); + return Err(VoteExtensionError::UnexpectedEpoch); } }; let (voting_power, pk) = self From 72b8803ec5fe063e1a9ac50066c26b82581ba8db Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 09:28:19 +0100 Subject: [PATCH 0723/1995] Fix test_reject_incorrect_block_height() unit test --- .../src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index aafaf1ed4d..474be66787 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -344,7 +344,7 @@ mod test_vote_extensions { } #[cfg(not(feature = "abcipp"))] { - assert!(shell.validate_valset_upd_vext( + assert!(!shell.validate_valset_upd_vext( validator_set_update.unwrap(), shell.storage.get_current_decision_height() )) From 6fb04286c81b3e15129e5b5cc8c3fc7c4ce7a07c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Wed, 28 Sep 2022 11:09:02 +0200 Subject: [PATCH 0724/1995] fix: attempting to fix deps --- shared/src/ledger/ibc/handler.rs | 205 +++++++++++++------------------ 1 file changed, 82 insertions(+), 123 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index cb4e3212ea..1d72b99b7b 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -2,6 +2,7 @@ use std::str::FromStr; +use prost::Message; use sha2::Digest; use thiserror::Error; @@ -36,16 +37,15 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics03_connection::version::Version as ConnVersion; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, CloseConfirm as ChanCloseConfirm, - CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, - OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, - OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, + AcknowledgePacket, Attributes as ChannelAttributes, + CloseConfirm as ChanCloseConfirm, CloseInit as ChanCloseInit, + OpenAck as ChanOpenAck, OpenConfirm as ChanOpenConfirm, + OpenInit as ChanOpenInit, OpenTry as ChanOpenTry, SendPacket, + TimeoutPacket, WriteAcknowledgement, }; use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; @@ -406,7 +406,8 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); + let port_channel_id = + port_channel_id(msg.port_id.clone(), channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -427,7 +428,8 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); + let port_channel_id = + port_channel_id(msg.port_id.clone(), channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -445,7 +447,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenAck fn ack_channel(&self, msg: &MsgChannelOpenAck) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.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(|| { Error::Channel(format!( @@ -455,16 +457,15 @@ pub trait IbcActions { })?; let mut channel = ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel.set_counterparty_channel_id(msg.counterparty_channel_id); + channel + .set_counterparty_channel_id(msg.counterparty_channel_id.clone()); open_channel(&mut channel); self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_open_ack_channel_event(msg, &channel)? - .try_into() - .unwrap(); + let event = make_open_ack_channel_event(msg).try_into().unwrap(); self.emit_ibc_event(event); Ok(()) @@ -473,7 +474,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenConfirm fn confirm_channel(&self, msg: &MsgChannelOpenConfirm) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.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(|| { Error::Channel(format!( @@ -489,9 +490,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_open_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); + let event = make_open_confirm_channel_event(msg).try_into().unwrap(); self.emit_ibc_event(event); Ok(()) @@ -500,7 +499,7 @@ pub trait IbcActions { /// Close the channel for ChannelCloseInit fn close_init_channel(&self, msg: &MsgChannelCloseInit) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.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(|| { Error::Channel(format!( @@ -516,9 +515,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_init_channel_event(msg, &channel)? - .try_into() - .unwrap(); + let event = make_close_init_channel_event(msg).try_into().unwrap(); self.emit_ibc_event(event); Ok(()) @@ -530,7 +527,7 @@ pub trait IbcActions { msg: &MsgChannelCloseConfirm, ) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.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(|| { Error::Channel(format!( @@ -546,9 +543,7 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); + let event = make_close_confirm_channel_event(msg).try_into().unwrap(); self.emit_ibc_event(event); Ok(()) @@ -579,11 +574,12 @@ pub trait IbcActions { let packet = Packet { sequence, source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id, + source_channel: port_channel_id.channel_id.clone(), destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty + destination_channel: counterparty .channel_id() - .expect("the counterparty channel should exist"), + .expect("the counterparty channel should exist") + .clone(), data, timeout_height, timeout_timestamp, @@ -595,7 +591,11 @@ pub trait IbcActions { packet.sequence, ); let commitment = commitment(&packet); - self.write_ibc_data(&commitment_key, commitment.into_vec()); + let mut commitment_bytes = vec![]; + commitment + .encode(&mut commitment_bytes) + .expect("encoding shouldn't fail"); + self.write_ibc_data(&commitment_key, commitment_bytes); let event = make_send_packet_event(packet).try_into().unwrap(); self.emit_ibc_event(event); @@ -625,13 +625,12 @@ pub trait IbcActions { msg.packet.sequence, ); let ack = PacketAck::default().encode_to_vec(); - let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); - self.write_ibc_data(&ack_key, ack_commitment); + self.write_ibc_data(&ack_key, ack.clone()); // increment the next sequence receive let port_channel_id = port_channel_id( msg.packet.destination_port.clone(), - msg.packet.destination_channel, + msg.packet.destination_channel.clone(), ); let seq_key = storage::next_sequence_recv_key(&port_channel_id); self.get_and_inc_sequence(&seq_key)?; @@ -677,7 +676,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel, + 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(|| { @@ -720,7 +719,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel, + 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(|| { @@ -865,8 +864,10 @@ pub trait IbcActions { } // send a packet - let port_channel_id = - port_channel_id(msg.source_port.clone(), msg.source_channel); + let port_channel_id = port_channel_id( + msg.source_port.clone(), + msg.source_channel.clone(), + ); let packet_data = serde_json::to_vec(&data) .expect("encoding the packet data shouldn't fail"); self.send_packet( @@ -1030,9 +1031,7 @@ pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { ConnState::Init, msg.client_id.clone(), msg.counterparty.clone(), - msg.version - .clone() - .map_or_else(|| vec![ConnVersion::default()], |v| vec![v]), + vec![msg.version.clone()], msg.delay_period, ) } @@ -1098,11 +1097,12 @@ pub fn packet_from_message( Packet { sequence, source_port: msg.source_port.clone(), - source_channel: msg.source_channel, + source_channel: msg.source_channel.clone(), destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty + destination_channel: counterparty .channel_id() - .expect("the counterparty channel should exist"), + .expect("the counterparty channel should exist") + .clone(), data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) .expect("encoding the packet data shouldn't fail"), timeout_height: msg.timeout_height, @@ -1111,19 +1111,13 @@ pub fn packet_from_message( } /// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> PacketCommitment { - let mut input = packet - .timeout_timestamp - .nanoseconds() - .to_be_bytes() - .to_vec(); - let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - input.append(&mut revision_number.to_vec()); - let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - input.append(&mut revision_height.to_vec()); - let data = sha2::Sha256::digest(&packet.data); - input.append(&mut data.to_vec()); - sha2::Sha256::digest(&input).to_vec().into() +pub fn commitment(packet: &Packet) -> String { + let input = format!( + "{:?},{:?},{:?}", + packet.timeout_timestamp, packet.timeout_height, packet.data, + ); + let r = sha2::Sha256::digest(input.as_bytes()); + format!("{:x}", r) } /// Returns a counterparty of a connection @@ -1202,7 +1196,7 @@ pub fn make_open_init_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - ConnOpenInit::from(attributes).into() + IbcEvent::OpenInitConnection(ConnOpenInit::from(attributes)) } /// Makes OpenTryConnection event @@ -1217,7 +1211,7 @@ pub fn make_open_try_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - ConnOpenTry::from(attributes).into() + IbcEvent::OpenTryConnection(ConnOpenTry::from(attributes)) } /// Makes OpenAckConnection event @@ -1229,7 +1223,7 @@ pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { ), ..Default::default() }; - ConnOpenAck::from(attributes).into() + IbcEvent::OpenAckConnection(ConnOpenAck::from(attributes)) } /// Makes OpenConfirmConnection event @@ -1240,7 +1234,7 @@ pub fn make_open_confirm_connection_event( connection_id: Some(msg.connection_id.clone()), ..Default::default() }; - ConnOpenConfirm::from(attributes).into() + IbcEvent::OpenConfirmConnection(ConnOpenConfirm::from(attributes)) } /// Makes OpenInitChannel event @@ -1252,10 +1246,9 @@ pub fn make_open_init_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChanOpenInit { - height: Height::default(), + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), + channel_id: Some(channel_id.clone()), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1263,8 +1256,9 @@ pub fn make_open_init_channel_event( .counterparty() .channel_id() .cloned(), + ..Default::default() }; - attributes.into() + IbcEvent::OpenInitChannel(ChanOpenInit::from(attributes)) } /// Makes OpenTryChannel event @@ -1276,10 +1270,9 @@ pub fn make_open_try_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChanOpenTry { - height: Height::default(), + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), + channel_id: Some(channel_id.clone()), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1287,88 +1280,54 @@ pub fn make_open_try_channel_event( .counterparty() .channel_id() .cloned(), + ..Default::default() }; - attributes.into() + IbcEvent::OpenTryChannel(ChanOpenTry::from(attributes)) } /// Makes OpenAckChannel event -pub fn make_open_ack_channel_event( - msg: &MsgChannelOpenAck, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenAck { - height: Height::default(), +pub fn make_open_ack_channel_event(msg: &MsgChannelOpenAck) -> IbcEvent { + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - counterparty_channel_id: Some(msg.counterparty_channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), + channel_id: Some(msg.channel_id.clone()), + counterparty_channel_id: Some(msg.counterparty_channel_id.clone()), + ..Default::default() }; - Ok(attributes.into()) + IbcEvent::OpenAckChannel(ChanOpenAck::from(attributes)) } /// Makes OpenConfirmChannel event pub fn make_open_confirm_channel_event( msg: &MsgChannelOpenConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenConfirm { - height: Height::default(), +) -> IbcEvent { + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), + channel_id: Some(msg.channel_id.clone()), + ..Default::default() }; - Ok(attributes.into()) + IbcEvent::OpenConfirmChannel(ChanOpenConfirm::from(attributes)) } /// Makes CloseInitChannel event -pub fn make_close_init_channel_event( - msg: &MsgChannelCloseInit, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseInit { - height: Height::default(), +pub fn make_close_init_channel_event(msg: &MsgChannelCloseInit) -> IbcEvent { + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: msg.channel_id, - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), + channel_id: Some(msg.channel_id.clone()), + ..Default::default() }; - Ok(attributes.into()) + IbcEvent::CloseInitChannel(ChanCloseInit::from(attributes)) } /// Makes CloseConfirmChannel event pub fn make_close_confirm_channel_event( msg: &MsgChannelCloseConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseConfirm { - height: Height::default(), +) -> IbcEvent { + let attributes = ChannelAttributes { port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id.clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), + channel_id: Some(msg.channel_id.clone()), + ..Default::default() }; - Ok(attributes.into()) -} - -fn get_connection_id_from_channel( - channel: &ChannelEnd, -) -> Result<&ConnectionId> { - channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel("No connection for the channel".to_owned()) - }) + IbcEvent::CloseConfirmChannel(ChanCloseConfirm::from(attributes)) } /// Makes SendPacket event From c9f41835732889e9d72d1055db5d038c8e40bbad Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 28 Sep 2022 13:21:13 +0100 Subject: [PATCH 0725/1995] Update warn log message when Tendermint version isn't supported --- apps/src/lib/node/ledger/tendermint_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 5e265a489b..de6d56d8c2 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -132,7 +132,7 @@ pub async fn run( %tendermint_path, %version, %version_reqs, - "Running with a Tendermint version which is not supported" + "Running with a Tendermint version which may not be supported - run at your own risk!", ); } From 5202221f8b4161730f927607716c65b19ef138ce Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 14:05:42 +0100 Subject: [PATCH 0726/1995] Use HttpClient for broadcast_tx_sync() --- apps/src/lib/client/tx.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f759f06a37..860ed2483a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -31,13 +31,11 @@ use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::{find_keypair, sign_tx}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::tendermint_websocket_client::{ - Error as WsError, TendermintWebsocketClient, WebSocketAddress, -}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::query::{EventType, Query}; -use crate::facade::tendermint_rpc::{Client, HttpClient}; +use crate::facade::tendermint_rpc::{Client, HttpClient, WebSocketClient}; use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; @@ -1130,7 +1128,7 @@ async fn save_initialized_accounts( pub async fn broadcast_tx( address: TendermintAddress, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { TxBroadcastData::Wrapper { tx, @@ -1140,7 +1138,7 @@ pub async fn broadcast_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let websocket_timeout = + 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) @@ -1151,17 +1149,13 @@ pub async fn broadcast_tx( Duration::new(300, 0) }; - let mut wrapper_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; + let wrapper_tx_cli = HttpClient::new(address)?; - let response = wrapper_tx_subscription + let response = wrapper_tx_cli .broadcast_tx_sync(tx.to_bytes().into()) - .await - .map_err(|err| WsError::Response(format!("{:?}", err)))?; + .await?; - wrapper_tx_subscription.close(); + drop(wrapper_tx_cli); if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); @@ -1173,7 +1167,7 @@ pub async fn broadcast_tx( } Ok(response) } else { - Err(WsError::Response(serde_json::to_string(&response).unwrap())) + Err(RpcError::server(serde_json::to_string(&response).unwrap())) } } @@ -1188,7 +1182,7 @@ pub async fn broadcast_tx( pub async fn submit_tx( address: TendermintAddress, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Wrapper { tx, From 3d361130a842c79d91e8149024e5d1e91897fbca Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 14:25:33 +0100 Subject: [PATCH 0727/1995] WIP: Use tendermint-rs websocket client --- apps/src/lib/client/tx.rs | 81 +++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 860ed2483a..76768f24f7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -35,7 +35,9 @@ use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::query::{EventType, Query}; -use crate::facade::tendermint_rpc::{Client, HttpClient, WebSocketClient}; +use crate::facade::tendermint_rpc::{ + Client, HttpClient, SubscriptionClient, WebSocketClient, +}; use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; @@ -1138,16 +1140,18 @@ pub async fn broadcast_tx( _ => 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 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 wrapper_tx_cli = HttpClient::new(address)?; @@ -1192,42 +1196,45 @@ pub async fn submit_tx( _ => 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 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) + // }; + // ``` tracing::debug!("Tenderming address: {:?}", address); - let mut wrapper_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; + let (wrapper_tx_cli, driver) = + WebSocketClient::new(address.clone()).await?; + tokio::spawn(async move { + let _ = driver.run().await; + }); // 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) + let wrapper_query = Query::from(EventType::NewBlock) .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - wrapper_tx_subscription.subscribe(query)?; + let mut wrapper_tx_subscription = + wrapper_tx_cli.subscribe(wrapper_query.clone()).await?; // We also subscribe to the event emitted when the encrypted // payload makes its way onto the blockchain - let mut decrypted_tx_subscription = { - let mut decrypted_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; - let query = Query::from(EventType::NewBlock) - .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); - decrypted_tx_subscription.subscribe(query)?; - decrypted_tx_subscription - }; + let (decrypted_tx_cli, driver) = + WebSocketClient::new(address.clone()).await?; + tokio::spawn(async move { + let _ = driver.run().await; + }); + let decrypted_query = Query::from(EventType::NewBlock) + .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); + let mut decrypted_tx_subscription = + decrypted_tx_cli.subscribe(decrypted_query.clone()).await?; // Broadcast the supplied transaction broadcast_tx(address, &to_broadcast).await?; From 01bb00446f37bc608a87ef4f2ab8610543fab945 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 14:59:08 +0100 Subject: [PATCH 0728/1995] WIP: Replace our ws client with tendermint-rs's client --- apps/src/lib/client/tx.rs | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 76768f24f7..7f37ecc201 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,11 +1,9 @@ use std::borrow::Cow; -use std::convert::TryFrom; -use std::env; use std::fs::File; -use std::time::Duration; use async_std::io::{self, WriteExt}; use borsh::BorshSerialize; +use futures::stream::StreamExt; use itertools::Either::*; use namada::ledger::governance::storage as gov_storage; use namada::ledger::pos::{BondId, Bonds, Unbonds}; @@ -1222,7 +1220,7 @@ pub async fn submit_tx( let wrapper_query = Query::from(EventType::NewBlock) .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); let mut wrapper_tx_subscription = - wrapper_tx_cli.subscribe(wrapper_query.clone()).await?; + wrapper_tx_cli.subscribe(wrapper_query).await?; // We also subscribe to the event emitted when the encrypted // payload makes its way onto the blockchain @@ -1234,17 +1232,18 @@ pub async fn submit_tx( let decrypted_query = Query::from(EventType::NewBlock) .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); let mut decrypted_tx_subscription = - decrypted_tx_cli.subscribe(decrypted_query.clone()).await?; + decrypted_tx_cli.subscribe(decrypted_query).await?; // Broadcast the supplied transaction broadcast_tx(address, &to_broadcast).await?; let parsed = { - let parsed = TxResponse::parse( - wrapper_tx_subscription.receive_response()?, - NamadaEventType::Accepted, - wrapper_hash, - ); + let event = wrapper_tx_subscription.next().await.transpose()?; + let event = event.ok_or_else(|| { + RpcError::server("failed to get wrapper tx event".to_string()) + })?; + let parsed = + TxResponse::parse(event, NamadaEventType::Accepted, wrapper_hash); println!( "Transaction accepted with result: {}", @@ -1253,8 +1252,12 @@ pub async fn submit_tx( // The transaction is now on chain. We wait for it to be decrypted // and applied if parsed.code == 0.to_string() { + let event = decrypted_tx_subscription.next().await.transpose()?; + let event = event.ok_or_else(|| { + RpcError::server("failed to get decrypted tx event".to_string()) + })?; let parsed = TxResponse::parse( - decrypted_tx_subscription.receive_response()?, + event, NamadaEventType::Applied, decrypted_hash.as_str(), ); @@ -1268,9 +1271,17 @@ pub async fn submit_tx( } }; - wrapper_tx_subscription.unsubscribe()?; - wrapper_tx_subscription.close(); - decrypted_tx_subscription.unsubscribe()?; - decrypted_tx_subscription.close(); + wrapper_tx_cli + .unsubscribe(wrapper_tx_subscription.query().clone()) + .await?; + drop(wrapper_tx_subscription); + drop(wrapper_tx_cli); + + decrypted_tx_cli + .unsubscribe(decrypted_tx_subscription.query().clone()) + .await?; + drop(decrypted_tx_subscription); + drop(decrypted_tx_cli); + parsed } From 46016545ed2cfb84fc02de234ac89edab86d37f4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 15:46:53 +0100 Subject: [PATCH 0729/1995] WIP: Getting events from tendermint-rpc type --- apps/Cargo.toml | 1 - apps/src/lib/client/tendermint_rpc_types.rs | 66 ++++++++------------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 76d808d637..bb50136c7e 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -91,7 +91,6 @@ file-lock = "2.0.2" futures = "0.3" hex = "0.4.3" itertools = "0.10.1" -jsonpath_lib = "0.3.0" libc = "0.2.97" libloading = "0.7.2" libp2p = "0.38.0" diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 32454c0440..9019d721ba 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,9 +1,9 @@ -use jsonpath_lib as jsonpath; use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; +use crate::facade::tendermint_rpc::event::Event; use crate::node::ledger::events::EventType as NamadaEventType; /// Data needed for broadcasting a tx and @@ -40,51 +40,37 @@ impl TxResponse { /// Searches for custom events emitted from the ledger and converts /// them back to thin wrapper around a hashmap for further parsing. pub fn parse( - json: serde_json::Value, + event: Event, event_type: NamadaEventType, tx_hash: &str, ) -> Self { - let tx_hash_json = serde_json::Value::String(tx_hash.to_string()); - let mut selector = jsonpath::selector(&json); - let mut index = 0; + let events = event.events.expect( + "We should have obtained Tx events from the websocket subscription", + ); let evt_key = event_type.to_string(); // Find the tx with a matching hash - let hash = loop { - if let Ok(hash) = - selector(&format!("$.events.['{}.hash'][{}]", evt_key, index)) - { - let hash = hash[0].clone(); - if hash == tx_hash_json { - break hash; - } else { - index += 1; - } - } else { - eprintln!( - "Couldn't find tx with hash {} in the event string {}", - tx_hash, json - ); - safe_exit(1) - } + let tx_error = || { + eprintln!( + "Couldn't find tx with hash {tx_hash} in events {events:?}", + ); + safe_exit(1) }; - let info = - selector(&format!("$.events.['{}.info'][{}]", evt_key, index)) - .unwrap(); - let log = selector(&format!("$.events.['{}.log'][{}]", evt_key, index)) - .unwrap(); - let height = - selector(&format!("$.events.['{}.height'][{}]", evt_key, index)) - .unwrap(); - let code = - selector(&format!("$.events.['{}.code'][{}]", evt_key, index)) - .unwrap(); + let (index, _) = events + .get(&format!("{evt_key}.hash")) + .unwrap_or_else(tx_error) + .iter() + .enumerate() + .find(|(_, hash)| hash == tx_hash) + .unwrap_or_else(tx_error); + let info = events.get(&format!("{evt_key}.info")).unwrap()[index]; + let log = events.get(&format!("{evt_key}.log")).unwrap()[index]; + let height = events.get(&format!("{evt_key}.height")).unwrap()[index]; + let code = events.get(&format!("{evt_key}.code")).unwrap()[index]; let gas_used = - selector(&format!("$.events.['{}.gas_used'][{}]", evt_key, index)) - .unwrap(); - let initialized_accounts = selector(&format!( - "$.events.['{}.initialized_accounts'][{}]", - evt_key, index - )); + events.get(&format!("{evt_key}.gas_used")).unwrap()[index]; + let initialized_accounts = events + .get(&format!("{evt_key}.initialized_accounts")) + .unwrap()[index]; let initialized_accounts = match initialized_accounts { Ok(values) if !values.is_empty() => { // In a response, the initialized accounts are encoded as e.g.: @@ -107,7 +93,7 @@ impl TxResponse { info: serde_json::from_value(info[0].clone()).unwrap(), log: serde_json::from_value(log[0].clone()).unwrap(), height: serde_json::from_value(height[0].clone()).unwrap(), - hash: serde_json::from_value(hash).unwrap(), + hash: tx_hash.to_string(), code: serde_json::from_value(code[0].clone()).unwrap(), gas_used: serde_json::from_value(gas_used[0].clone()).unwrap(), initialized_accounts, From d96903e89b7b1d82cee1e739e77f59c7b9bfe60c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 16:06:03 +0100 Subject: [PATCH 0730/1995] Add clippy fixes --- apps/src/lib/client/mod.rs | 2 +- apps/src/lib/client/tendermint_rpc_types.rs | 79 +++++++++++---------- apps/src/lib/client/tx.rs | 6 +- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 3fff8d94ec..f139033ba9 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -2,6 +2,6 @@ pub mod gossip; pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; -mod tendermint_websocket_client; +// mod tendermint_websocket_client; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 9019d721ba..084d7cf701 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -49,53 +49,54 @@ impl TxResponse { ); let evt_key = event_type.to_string(); // Find the tx with a matching hash - let tx_error = || { - eprintln!( - "Couldn't find tx with hash {tx_hash} in events {events:?}", - ); - safe_exit(1) - }; + macro_rules! tx_error { + () => { + || { + eprintln!( + "Couldn't find tx with hash {tx_hash} in events \ + {events:?}", + ); + safe_exit(1) + } + }; + } let (index, _) = events .get(&format!("{evt_key}.hash")) - .unwrap_or_else(tx_error) + .unwrap_or_else(tx_error!()) .iter() .enumerate() - .find(|(_, hash)| hash == tx_hash) - .unwrap_or_else(tx_error); - let info = events.get(&format!("{evt_key}.info")).unwrap()[index]; - let log = events.get(&format!("{evt_key}.log")).unwrap()[index]; - let height = events.get(&format!("{evt_key}.height")).unwrap()[index]; - let code = events.get(&format!("{evt_key}.code")).unwrap()[index]; + .find(|(_, hash)| hash == &tx_hash) + .unwrap_or_else(tx_error!()); + let info = &events.get(&format!("{evt_key}.info")).unwrap()[index]; + let log = &events.get(&format!("{evt_key}.log")).unwrap()[index]; + let height = &events.get(&format!("{evt_key}.height")).unwrap()[index]; + let code = &events.get(&format!("{evt_key}.code")).unwrap()[index]; let gas_used = - events.get(&format!("{evt_key}.gas_used")).unwrap()[index]; - let initialized_accounts = events - .get(&format!("{evt_key}.initialized_accounts")) - .unwrap()[index]; - let initialized_accounts = match initialized_accounts { - Ok(values) if !values.is_empty() => { - // In a response, the initialized accounts are encoded as e.g.: - // ``` - // "applied.initialized_accounts": Array([ - // String( - // "[\"atest1...\"]", - // ), - // ]), - // ... - // So we need to decode the inner string first ... - let raw: String = - serde_json::from_value(values[0].clone()).unwrap(); - // ... and then decode the vec from the array inside the string - serde_json::from_str(&raw).unwrap() - } - _ => vec![], + &events.get(&format!("{evt_key}.gas_used")).unwrap()[index]; + let initialized_accounts = { + // In a response, the initialized accounts are encoded as e.g.: + // ``` + // "applied.initialized_accounts": Array([ + // String( + // "[\"atest1...\"]", + // ), + // ]), + // ... + // So we need to decode the inner string first ... + let initialized_accounts = &events + .get(&format!("{evt_key}.initialized_accounts")) + .unwrap()[index]; + let initialized_accounts: String = + serde_json::from_str(initialized_accounts).unwrap(); + serde_json::from_str(&initialized_accounts).unwrap() }; TxResponse { - info: serde_json::from_value(info[0].clone()).unwrap(), - log: serde_json::from_value(log[0].clone()).unwrap(), - height: serde_json::from_value(height[0].clone()).unwrap(), + info: serde_json::from_str(info).unwrap(), + log: serde_json::from_str(log).unwrap(), + height: serde_json::from_str(height).unwrap(), hash: tx_hash.to_string(), - code: serde_json::from_value(code[0].clone()).unwrap(), - gas_used: serde_json::from_value(gas_used[0].clone()).unwrap(), + code: serde_json::from_str(code).unwrap(), + gas_used: serde_json::from_str(gas_used).unwrap(), initialized_accounts, } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 7f37ecc201..d2171e8bdb 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -55,8 +55,10 @@ const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; const VP_NFT: &str = "vp_nft.wasm"; -const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = - "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; +// ``` +// const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = +// "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; +// ``` pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); From e3a0744f879d81ed38f315aeef79812e1d1b210f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 26 Sep 2022 16:06:14 +0100 Subject: [PATCH 0731/1995] Cargo.lock --- Cargo.lock | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd762c657f..72071c79c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3226,17 +3226,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" -dependencies = [ - "log 0.4.17", - "serde 1.0.144", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.2" @@ -4387,7 +4376,6 @@ dependencies = [ "git2", "hex", "itertools 0.10.3", - "jsonpath_lib", "libc", "libloading", "libp2p", @@ -6469,7 +6457,6 @@ version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "indexmap", "itoa", "ryu", "serde 1.0.144", From 02177fb045208a8cb7b5cd7e71178ca4cfc6e749 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 27 Sep 2022 10:11:38 +0100 Subject: [PATCH 0732/1995] Fix initialized accounts deserialization --- apps/src/lib/client/tendermint_rpc_types.rs | 23 +++++---------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 084d7cf701..788fa31af4 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -73,23 +73,9 @@ impl TxResponse { let code = &events.get(&format!("{evt_key}.code")).unwrap()[index]; let gas_used = &events.get(&format!("{evt_key}.gas_used")).unwrap()[index]; - let initialized_accounts = { - // In a response, the initialized accounts are encoded as e.g.: - // ``` - // "applied.initialized_accounts": Array([ - // String( - // "[\"atest1...\"]", - // ), - // ]), - // ... - // So we need to decode the inner string first ... - let initialized_accounts = &events - .get(&format!("{evt_key}.initialized_accounts")) - .unwrap()[index]; - let initialized_accounts: String = - serde_json::from_str(initialized_accounts).unwrap(); - serde_json::from_str(&initialized_accounts).unwrap() - }; + let initialized_accounts = &events + .get(&format!("{evt_key}.initialized_accounts")) + .unwrap()[index]; TxResponse { info: serde_json::from_str(info).unwrap(), log: serde_json::from_str(log).unwrap(), @@ -97,7 +83,8 @@ impl TxResponse { hash: tx_hash.to_string(), code: serde_json::from_str(code).unwrap(), gas_used: serde_json::from_str(gas_used).unwrap(), - initialized_accounts, + initialized_accounts: serde_json::from_str(initialized_accounts) + .unwrap(), } } } From 25de19a675c8ceea45bfb30c1f03b116ceb43ef9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 27 Sep 2022 10:35:32 +0100 Subject: [PATCH 0733/1995] Fix encoding This time hopefully we actually fix it. --- apps/src/lib/client/tendermint_rpc_types.rs | 31 ++++++++++----------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 788fa31af4..0016b8b6ab 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -67,24 +67,23 @@ impl TxResponse { .enumerate() .find(|(_, hash)| hash == &tx_hash) .unwrap_or_else(tx_error!()); - let info = &events.get(&format!("{evt_key}.info")).unwrap()[index]; - let log = &events.get(&format!("{evt_key}.log")).unwrap()[index]; - let height = &events.get(&format!("{evt_key}.height")).unwrap()[index]; - let code = &events.get(&format!("{evt_key}.code")).unwrap()[index]; - let gas_used = - &events.get(&format!("{evt_key}.gas_used")).unwrap()[index]; - let initialized_accounts = &events - .get(&format!("{evt_key}.initialized_accounts")) - .unwrap()[index]; + let info = events[&format!("{evt_key}.info")][index].clone(); + let log = events[&format!("{evt_key}.log")][index].clone(); + let height = events[&format!("{evt_key}.height")][index].clone(); + let code = events[&format!("{evt_key}.code")][index].clone(); + let gas_used = events[&format!("{evt_key}.gas_used")][index].clone(); + let initialized_accounts = serde_json::from_str( + &events[&format!("{evt_key}.initialized_accounts")][index], + ) + .unwrap(); TxResponse { - info: serde_json::from_str(info).unwrap(), - log: serde_json::from_str(log).unwrap(), - height: serde_json::from_str(height).unwrap(), + info, + log, + height, + code, + gas_used, + initialized_accounts, hash: tx_hash.to_string(), - code: serde_json::from_str(code).unwrap(), - gas_used: serde_json::from_str(gas_used).unwrap(), - initialized_accounts: serde_json::from_str(initialized_accounts) - .unwrap(), } } } From ee8dcbe181572e5630e4c49d756be04cd97937db Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 27 Sep 2022 15:08:58 +0100 Subject: [PATCH 0734/1995] Handle Tendermint giving us an empty event field --- apps/src/lib/client/tendermint_rpc_types.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 0016b8b6ab..78f72d096f 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -72,10 +72,19 @@ impl TxResponse { let height = events[&format!("{evt_key}.height")][index].clone(); let code = events[&format!("{evt_key}.code")][index].clone(); let gas_used = events[&format!("{evt_key}.gas_used")][index].clone(); - let initialized_accounts = serde_json::from_str( - &events[&format!("{evt_key}.initialized_accounts")][index], - ) - .unwrap(); + let initialized_accounts = events + [&format!("{evt_key}.initialized_accounts")] + .get(index) + .as_ref() + .map(|initialized_accounts| { + serde_json::from_str(initialized_accounts).unwrap() + }) + .unwrap_or_else(|| { + eprintln!( + "Tendermint omitted one of the expected indices in events" + ); + Vec::new() + }); TxResponse { info, log, From 7a95ce1b7ea4dcc3a3c772dfdc73299bbcbbabd7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 27 Sep 2022 15:23:46 +0100 Subject: [PATCH 0735/1995] Remove second ws cli --- apps/src/lib/client/tx.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d2171e8bdb..62fffd33f9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1209,8 +1209,7 @@ pub async fn submit_tx( // }; // ``` tracing::debug!("Tenderming address: {:?}", address); - let (wrapper_tx_cli, driver) = - WebSocketClient::new(address.clone()).await?; + let (ws_cli, driver) = WebSocketClient::new(address.clone()).await?; tokio::spawn(async move { let _ = driver.run().await; }); @@ -1221,20 +1220,14 @@ pub async fn submit_tx( // created by the shell let wrapper_query = Query::from(EventType::NewBlock) .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - let mut wrapper_tx_subscription = - wrapper_tx_cli.subscribe(wrapper_query).await?; + let mut wrapper_tx_subscription = ws_cli.subscribe(wrapper_query).await?; // We also subscribe to the event emitted when the encrypted // payload makes its way onto the blockchain - let (decrypted_tx_cli, driver) = - WebSocketClient::new(address.clone()).await?; - tokio::spawn(async move { - let _ = driver.run().await; - }); let decrypted_query = Query::from(EventType::NewBlock) .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); let mut decrypted_tx_subscription = - decrypted_tx_cli.subscribe(decrypted_query).await?; + ws_cli.subscribe(decrypted_query).await?; // Broadcast the supplied transaction broadcast_tx(address, &to_broadcast).await?; @@ -1273,17 +1266,16 @@ pub async fn submit_tx( } }; - wrapper_tx_cli + ws_cli .unsubscribe(wrapper_tx_subscription.query().clone()) .await?; drop(wrapper_tx_subscription); - drop(wrapper_tx_cli); - decrypted_tx_cli + ws_cli .unsubscribe(decrypted_tx_subscription.query().clone()) .await?; drop(decrypted_tx_subscription); - drop(decrypted_tx_cli); + drop(ws_cli); parsed } From ab6c32c3645eee9b5a26396f1c10e121c3d49543 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 14:41:39 +0100 Subject: [PATCH 0736/1995] Replace bat/abciplus with tiago/abciplus --- Cargo.lock | 66 ++++++++++++++++++------------------- apps/Cargo.toml | 10 +++--- shared/Cargo.toml | 8 ++--- wasm/wasm_source/Cargo.lock | 16 ++++----- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72071c79c9..eef80f5eb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2897,12 +2897,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes 1.2.1", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2913,10 +2913,10 @@ dependencies = [ "serde_json", "sha2 0.10.5", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", "tracing 0.1.36", ] @@ -2951,13 +2951,13 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.144", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tonic", ] @@ -4297,9 +4297,9 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus)", "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=bat/abciplus)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", "itertools 0.10.3", @@ -4321,9 +4321,9 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "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=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -4409,13 +4409,13 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "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=bat/abciplus)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "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=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "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=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "test-log", "thiserror", @@ -4425,7 +4425,7 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus)", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=tiago/abciplus)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tracing 0.1.36", "tracing-log", @@ -6949,7 +6949,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6969,7 +6969,7 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", "zeroize", ] @@ -7005,12 +7005,12 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "flex-error", "serde 1.0.144", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "toml", "url 2.3.0", ] @@ -7031,13 +7031,13 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "derive_more", "flex-error", "serde 1.0.144", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", ] @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "bytes 1.2.1", "flex-error", @@ -7091,7 +7091,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "async-trait", "async-tungstenite", @@ -7109,9 +7109,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "thiserror", "time 0.3.14", "tokio", @@ -7157,7 +7157,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7165,7 +7165,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", ] @@ -7638,13 +7638,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus#21623a99bdca5b006d53752a1967849bef3b89ea" +source = "git+https://github.com/heliaxdev/tower-abci?branch=tiago/abciplus#c8d75d9d8e6668de8cc593ab57621651aae2f7f7" dependencies = [ "bytes 1.2.1", "futures 0.3.24", "pin-project 1.0.12", "prost 0.9.0", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index bb50136c7e..196793b16b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -127,10 +127,10 @@ tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"], optional = true} -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", features = ["http-client", "websocket-client"], optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", optional = true} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", optional = true} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -139,7 +139,7 @@ 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-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200", optional = true} -tower-abci = {git = "https://github.com/heliaxdev/tower-abci", branch = "bat/abciplus", optional = true} +tower-abci = {git = "https://github.com/heliaxdev/tower-abci", branch = "tiago/abciplus", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index fcdf4e6246..4a3f95c8fb 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,8 +86,8 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} -ibc = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abciplus", default-features = false, optional = true} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", branch = "bat/abciplus", default-features = false, optional = true} +ibc = {git = "https://github.com/heliaxdev/ibc-rs", branch = "tiago/abciplus", default-features = false, optional = true} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", branch = "tiago/abciplus", default-features = false, optional = true} ics23 = "0.6.7" itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} @@ -111,8 +111,8 @@ tempfile = {version = "3.2.0", optional = true} # temporarily using fork work-around for https://github.com/informalsystems/tendermint-rs/issues/971 tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", optional = true} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "tiago/abciplus", optional = true} thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index f1ad3c4e5f..56f9571c8b 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes", "prost", @@ -2673,7 +2673,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "async-trait", "bytes", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "flex-error", "serde", @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "derive_more", "flex-error", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "bytes", "flex-error", @@ -2744,7 +2744,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "bytes", "flex-error", @@ -2768,7 +2768,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" dependencies = [ "ed25519-dalek", "gumdrop", From b664b13044497e4746deabbb0b151d00a38b1d2c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 14:48:14 +0100 Subject: [PATCH 0737/1995] Remove custom Tendermint websockets client --- apps/src/lib/client/mod.rs | 1 - .../lib/client/tendermint_websocket_client.rs | 712 ------------------ 2 files changed, 713 deletions(-) delete mode 100644 apps/src/lib/client/tendermint_websocket_client.rs diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index f139033ba9..18b32889b5 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -2,6 +2,5 @@ pub mod gossip; pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; -// mod tendermint_websocket_client; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/tendermint_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs deleted file mode 100644 index 61bea1d990..0000000000 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ /dev/null @@ -1,712 +0,0 @@ -use std::collections::HashMap; -use std::convert::TryFrom; -use std::fmt::{Display, Formatter}; -use std::net::TcpStream; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use async_trait::async_trait; -use thiserror::Error; -use tokio::time::Instant; -use websocket::result::WebSocketError; -use websocket::{ClientBuilder, Message, OwnedMessage}; - -use crate::facade::tendermint_config::net::Address; -use crate::facade::tendermint_rpc::query::Query; -use crate::facade::tendermint_rpc::{ - Client, Error as RpcError, Request, Response, SimpleRequest, -}; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Could not convert into websocket address: {0:?}")] - Address(Address), - #[error("Websocket Error: {0:?}")] - Websocket(WebSocketError), - #[error("Failed to subscribe to the event: {0}")] - Subscribe(String), - #[error("Failed to unsubscribe to the event: {0}")] - Unsubscribe(String), - #[error("Unexpected response from query: {0:?}")] - UnexpectedResponse(OwnedMessage), - #[error("More then one subscription at a time is not supported")] - AlreadySubscribed, - #[error("Cannot wait on a response if not subscribed to an event")] - NotSubscribed, - #[error("Received an error response: {0}")] - Response(String), - #[error("Encountered JSONRPC request/response without an id")] - MissingId, - #[error("Connection timed out")] - ConnectionTimeout, - #[error("Received malformed JSON from websocket: {0:?}")] - MalformedJson(crate::node::ledger::events::Error), - #[error("Event for transaction {0} was not received")] - MissingEvent(String), -} - -type Json = serde_json::Value; - -/// Module that brings in the basic building blocks from tendermint_rpc -/// and adds the necessary functionality and wrappers to them. -mod rpc_types { - use std::collections::HashMap; - use std::fmt; - use std::str::FromStr; - - use serde::{de, Deserialize, Serialize, Serializer}; - - use super::Json; - use crate::facade::tendermint_rpc::method::Method; - use crate::facade::tendermint_rpc::query::{EventType, Query}; - use crate::facade::tendermint_rpc::{request, response}; - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcRequest { - #[serde(skip_serializing)] - method: Method, - params: HashMap, - } - - #[derive(Debug, Deserialize, Serialize)] - pub enum SubscribeType { - Subscribe, - Unsubscribe, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcSubscription( - #[serde(skip_serializing)] pub SubscribeType, - #[serde(serialize_with = "serialize_query")] - #[serde(deserialize_with = "deserialize_query")] - pub Query, - ); - - pub(super) fn serialize_query( - query: &Query, - serialize: S, - ) -> Result - where - S: Serializer, - { - serialize.serialize_str(&query.to_string()) - } - - pub(super) fn deserialize_query<'de, D>( - deserializer: D, - ) -> Result - where - D: de::Deserializer<'de>, - { - struct QueryVisitor; - - impl<'de> de::Visitor<'de> for QueryVisitor { - type Value = Query; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str( - "a string of params from a valid Tendermint RPC query", - ) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - match EventType::from_str(v) { - Ok(event) => Ok(Query::from(event)), - Err(error) => { - Err(de::Error::custom(format!("{:?}", error))) - } - } - } - } - deserializer.deserialize_any(QueryVisitor) - } - - /// This type is required by the tendermint_rs traits but we - /// cannot use it due to a bug in the RPC responses from - /// tendermint - #[derive(Debug, Deserialize, Serialize)] - #[serde(transparent)] - pub struct RpcResponse(pub Json); - - impl response::Response for RpcResponse {} - - impl request::Request for RpcRequest { - type Response = RpcResponse; - - fn method(&self) -> Method { - self.method - } - } - - impl request::Request for RpcSubscription { - type Response = RpcResponse; - - fn method(&self) -> Method { - match self.0 { - SubscribeType::Subscribe => Method::Subscribe, - SubscribeType::Unsubscribe => Method::Unsubscribe, - } - } - } -} - -pub struct WebSocketAddress { - host: String, - port: u16, -} - -impl TryFrom
for WebSocketAddress { - type Error = Error; - - fn try_from(value: Address) -> Result { - match value { - Address::Tcp { host, port, .. } => Ok(Self { host, port }), - _ => Err(Error::Address(value)), - } - } -} - -impl Display for WebSocketAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "ws://{}:{}/websocket", self.host, self.port) - } -} - -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 -/// Furthermore, TendermintWebsocketClient must be `Send` since it will be -/// used in async methods -type Websocket = Arc>>; -type ResponseQueue = Arc>>; - -struct Subscription { - id: String, - query: Query, -} - -pub struct TendermintWebsocketClient { - websocket: Websocket, - subscribed: Option, - received_responses: ResponseQueue, - connection_timeout: Duration, -} - -impl TendermintWebsocketClient { - /// Open up a new websocket given a specified URL. - /// If no `connection_timeout` is given, defaults to 5 minutes. - pub fn open( - url: WebSocketAddress, - connection_timeout: Option, - ) -> Result { - match ClientBuilder::new(&url.to_string()) - .unwrap() - .connect_insecure() - { - Ok(websocket) => Ok(Self { - websocket: Arc::new(Mutex::new(websocket)), - subscribed: None, - received_responses: Arc::new(Mutex::new(HashMap::new())), - connection_timeout: connection_timeout - .unwrap_or_else(|| Duration::new(300, 0)), - }), - Err(inner) => Err(Error::Websocket(inner)), - } - } - - /// Shutdown the client. Can still be reused afterwards - pub fn close(&mut self) { - // Even in the case of errors, this will be shutdown - let _ = self.websocket.lock().unwrap().shutdown(); - self.subscribed = None; - self.received_responses.lock().unwrap().clear(); - } - - /// Subscribes to an event specified by the query argument. - 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 - 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 - 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. - 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] -impl Client for TendermintWebsocketClient { - async fn perform(&self, request: R) -> Result - where - R: SimpleRequest, - { - // send the subscription request - // Return an empty response if the request fails to send - let req_json = request.into_json(); - let req_id = get_id(&req_json).unwrap(); - if let Err(error) = self - .websocket - .lock() - .unwrap() - .send_message(&Message::text(&req_json)) - { - tracing::info! { - "Unable to send request: {}\nReceived Error: {:?}", - &req_json, - error - }; - return ::Response::from_string(""); - } - - // Return the response if text is returned, else return empty response - let mut websocket = self.websocket.lock().unwrap(); - let start = Instant::now(); - loop { - let duration = Instant::now().duration_since(start); - if duration > self.connection_timeout { - tracing::error!( - "Websocket connection timed out while waiting for response" - ); - return Err(RpcError::web_socket_timeout(duration)); - } - let response = match websocket - .recv_message() - .expect("Failed to receive message from websocket") - { - OwnedMessage::Text(resp) => resp, - 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 => { - tracing::info! { - "Received unexpected response to query: {}\nReceived {:?}", - &req_json, - other - }; - String::from("") - } - }; - // Check that we did not accidentally get a response for a - // subscription. If so, store it for later - if let Ok(resp_id) = get_id(&response) { - if resp_id != req_id { - self.received_responses - .lock() - .unwrap() - .insert(resp_id, response); - } else { - return ::Response::from_string(response); - } - } else { - // got an invalid response, just return nothing - return ::Response::from_string(response); - }; - } - } -} - -fn get_id(req_json: &str) -> Result { - if let serde_json::Value::Object(req) = - serde_json::from_str(req_json).unwrap() - { - req.get("id").ok_or(Error::MissingId).map(|v| v.to_string()) - } else { - 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(test)] -mod test_tendermint_websocket_client { - use std::time::Duration; - - use namada::types::transaction::hash_tx as hash_tx_bytes; - use serde::{Deserialize, Serialize}; - use websocket::sync::Server; - use websocket::{Message, OwnedMessage}; - - use crate::client::tendermint_websocket_client::{ - TendermintWebsocketClient, WebSocketAddress, - }; - use crate::facade::tendermint::abci::transaction; - use crate::facade::tendermint_rpc::endpoint::abci_info::AbciInfo; - use crate::facade::tendermint_rpc::query::{EventType, Query}; - use crate::facade::tendermint_rpc::Client; - - #[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()); - } -} From 93e047fefa4d9e167ee75354b560a2b61d928106 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 28 Sep 2022 15:04:49 +0100 Subject: [PATCH 0738/1995] Remove all websockets code from the cli --- apps/src/lib/client/tx.rs | 110 ++++++++++++-------------------------- 1 file changed, 34 insertions(+), 76 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 62fffd33f9..466c771a63 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,9 +1,10 @@ use std::borrow::Cow; +use std::env; use std::fs::File; +use std::time::Duration; use async_std::io::{self, WriteExt}; use borsh::BorshSerialize; -use futures::stream::StreamExt; use itertools::Either::*; use namada::ledger::governance::storage as gov_storage; use namada::ledger::pos::{BondId, Bonds, Unbonds}; @@ -33,9 +34,7 @@ use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::query::{EventType, Query}; -use crate::facade::tendermint_rpc::{ - Client, HttpClient, SubscriptionClient, WebSocketClient, -}; +use crate::facade::tendermint_rpc::{Client, HttpClient}; use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; @@ -55,10 +54,13 @@ const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; const VP_NFT: &str = "vp_nft.wasm"; -// ``` -// const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = -// "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; -// ``` +/// Timeout for jsonrpc requests to the `/events` endpoint in Tendermint. +const ENV_VAR_ANOMA_TENDERMINT_RPC_TIMEOUT: &str = + "ANOMA_TENDERMINT_RPC_TIMEOUT"; + +/// Default timeout in seconds for jsonrpc requests to the `/events` endpoint in +/// Tendermint. +const DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT: u64 = 30; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); @@ -1140,26 +1142,12 @@ pub async fn broadcast_tx( _ => 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 wrapper_tx_cli = HttpClient::new(address)?; + let rpc_cli = HttpClient::new(address)?; - let response = wrapper_tx_cli - .broadcast_tx_sync(tx.to_bytes().into()) - .await?; + // TODO: timeout? + let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; - drop(wrapper_tx_cli); + drop(rpc_cli); if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); @@ -1196,47 +1184,26 @@ pub async fn submit_tx( _ => 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 rpc_timeout = + if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_RPC_TIMEOUT) { + if let Ok(timeout) = val.parse::() { + Duration::from_secs(timeout) + } else { + Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT) + } + } else { + Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT) + }; tracing::debug!("Tenderming address: {:?}", address); - let (ws_cli, driver) = WebSocketClient::new(address.clone()).await?; - tokio::spawn(async move { - let _ = driver.run().await; - }); - - // 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 wrapper_query = Query::from(EventType::NewBlock) - .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - let mut wrapper_tx_subscription = ws_cli.subscribe(wrapper_query).await?; - - // We also subscribe to the event emitted when the encrypted - // payload makes its way onto the blockchain - let decrypted_query = Query::from(EventType::NewBlock) - .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); - let mut decrypted_tx_subscription = - ws_cli.subscribe(decrypted_query).await?; + let rpc_cli = HttpClient::new(address.clone())?; // Broadcast the supplied transaction broadcast_tx(address, &to_broadcast).await?; let parsed = { - let event = wrapper_tx_subscription.next().await.transpose()?; - let event = event.ok_or_else(|| { - RpcError::server("failed to get wrapper tx event".to_string()) - })?; + let wrapper_query = Query::from(EventType::NewBlock) + .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); + let event = rpc_cli.events(wrapper_query, rpc_timeout).await?.into(); let parsed = TxResponse::parse(event, NamadaEventType::Accepted, wrapper_hash); @@ -1247,10 +1214,12 @@ pub async fn submit_tx( // The transaction is now on chain. We wait for it to be decrypted // and applied if parsed.code == 0.to_string() { - let event = decrypted_tx_subscription.next().await.transpose()?; - let event = event.ok_or_else(|| { - RpcError::server("failed to get decrypted tx event".to_string()) - })?; + // We also listen to the event emitted when the encrypted + // payload makes its way onto the blockchain + let decrypted_query = Query::from(EventType::NewBlock) + .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); + let event = + rpc_cli.events(decrypted_query, rpc_timeout).await?.into(); let parsed = TxResponse::parse( event, NamadaEventType::Applied, @@ -1266,16 +1235,5 @@ pub async fn submit_tx( } }; - ws_cli - .unsubscribe(wrapper_tx_subscription.query().clone()) - .await?; - drop(wrapper_tx_subscription); - - ws_cli - .unsubscribe(decrypted_tx_subscription.query().clone()) - .await?; - drop(decrypted_tx_subscription); - - drop(ws_cli); parsed } From 7330bc6990feffb79e065756b1c58c76938831e3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 28 Sep 2022 17:00:18 +0200 Subject: [PATCH 0739/1995] Revert "fix: attempting to fix deps" This reverts commit 6fb04286c81b3e15129e5b5cc8c3fc7c4ce7a07c. --- shared/src/ledger/ibc/handler.rs | 205 ++++++++++++++++++------------- 1 file changed, 123 insertions(+), 82 deletions(-) diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 1d72b99b7b..cb4e3212ea 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -2,7 +2,6 @@ use std::str::FromStr; -use prost::Message; use sha2::Digest; use thiserror::Error; @@ -37,15 +36,16 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; +use crate::ibc::core::ics03_connection::version::Version as ConnVersion; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, Attributes as ChannelAttributes, - CloseConfirm as ChanCloseConfirm, CloseInit as ChanCloseInit, - OpenAck as ChanOpenAck, OpenConfirm as ChanOpenConfirm, - OpenInit as ChanOpenInit, OpenTry as ChanOpenTry, SendPacket, - TimeoutPacket, WriteAcknowledgement, + AcknowledgePacket, CloseConfirm as ChanCloseConfirm, + CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, + OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, + OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, }; use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; @@ -406,8 +406,7 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = - port_channel_id(msg.port_id.clone(), channel_id.clone()); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -428,8 +427,7 @@ pub trait IbcActions { let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let channel_id = channel_id(counter); - let port_channel_id = - port_channel_id(msg.port_id.clone(), channel_id.clone()); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); let channel_key = storage::channel_key(&port_channel_id); self.write_ibc_data( &channel_key, @@ -447,7 +445,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenAck fn ack_channel(&self, msg: &MsgChannelOpenAck) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -457,15 +455,16 @@ pub trait IbcActions { })?; let mut channel = ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel - .set_counterparty_channel_id(msg.counterparty_channel_id.clone()); + channel.set_counterparty_channel_id(msg.counterparty_channel_id); open_channel(&mut channel); 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(); + let event = make_open_ack_channel_event(msg, &channel)? + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -474,7 +473,7 @@ pub trait IbcActions { /// Open the channel for ChannelOpenConfirm fn confirm_channel(&self, msg: &MsgChannelOpenConfirm) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -490,7 +489,9 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_open_confirm_channel_event(msg).try_into().unwrap(); + let event = make_open_confirm_channel_event(msg, &channel)? + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -499,7 +500,7 @@ pub trait IbcActions { /// Close the channel for ChannelCloseInit fn close_init_channel(&self, msg: &MsgChannelCloseInit) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -515,7 +516,9 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_init_channel_event(msg).try_into().unwrap(); + let event = make_close_init_channel_event(msg, &channel)? + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -527,7 +530,7 @@ pub trait IbcActions { msg: &MsgChannelCloseConfirm, ) -> Result<()> { let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); + port_channel_id(msg.port_id.clone(), msg.channel_id); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { Error::Channel(format!( @@ -543,7 +546,9 @@ pub trait IbcActions { channel.encode_vec().expect("encoding shouldn't fail"), ); - let event = make_close_confirm_channel_event(msg).try_into().unwrap(); + let event = make_close_confirm_channel_event(msg, &channel)? + .try_into() + .unwrap(); self.emit_ibc_event(event); Ok(()) @@ -574,12 +579,11 @@ pub trait IbcActions { let packet = Packet { sequence, source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id.clone(), + source_channel: port_channel_id.channel_id, destination_port: counterparty.port_id.clone(), - destination_channel: counterparty + destination_channel: *counterparty .channel_id() - .expect("the counterparty channel should exist") - .clone(), + .expect("the counterparty channel should exist"), data, timeout_height, timeout_timestamp, @@ -591,11 +595,7 @@ pub trait IbcActions { packet.sequence, ); let commitment = commitment(&packet); - let mut commitment_bytes = vec![]; - 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.into_vec()); let event = make_send_packet_event(packet).try_into().unwrap(); self.emit_ibc_event(event); @@ -625,12 +625,13 @@ pub trait IbcActions { msg.packet.sequence, ); let ack = PacketAck::default().encode_to_vec(); - self.write_ibc_data(&ack_key, ack.clone()); + let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); + self.write_ibc_data(&ack_key, ack_commitment); // increment the next sequence receive let port_channel_id = port_channel_id( msg.packet.destination_port.clone(), - msg.packet.destination_channel.clone(), + msg.packet.destination_channel, ); let seq_key = storage::next_sequence_recv_key(&port_channel_id); self.get_and_inc_sequence(&seq_key)?; @@ -676,7 +677,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel.clone(), + msg.packet.source_channel, ); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { @@ -719,7 +720,7 @@ pub trait IbcActions { // close the channel let port_channel_id = port_channel_id( msg.packet.source_port.clone(), - msg.packet.source_channel.clone(), + msg.packet.source_channel, ); let channel_key = storage::channel_key(&port_channel_id); let value = self.read_ibc_data(&channel_key).ok_or_else(|| { @@ -864,10 +865,8 @@ pub trait IbcActions { } // send a packet - let port_channel_id = port_channel_id( - msg.source_port.clone(), - msg.source_channel.clone(), - ); + let port_channel_id = + port_channel_id(msg.source_port.clone(), msg.source_channel); let packet_data = serde_json::to_vec(&data) .expect("encoding the packet data shouldn't fail"); self.send_packet( @@ -1031,7 +1030,9 @@ pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { ConnState::Init, msg.client_id.clone(), msg.counterparty.clone(), - vec![msg.version.clone()], + msg.version + .clone() + .map_or_else(|| vec![ConnVersion::default()], |v| vec![v]), msg.delay_period, ) } @@ -1097,12 +1098,11 @@ pub fn packet_from_message( Packet { sequence, source_port: msg.source_port.clone(), - source_channel: msg.source_channel.clone(), + source_channel: msg.source_channel, destination_port: counterparty.port_id.clone(), - destination_channel: counterparty + destination_channel: *counterparty .channel_id() - .expect("the counterparty channel should exist") - .clone(), + .expect("the counterparty channel should exist"), data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) .expect("encoding the packet data shouldn't fail"), timeout_height: msg.timeout_height, @@ -1111,13 +1111,19 @@ pub fn packet_from_message( } /// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> String { - let input = format!( - "{:?},{:?},{:?}", - packet.timeout_timestamp, packet.timeout_height, packet.data, - ); - let r = sha2::Sha256::digest(input.as_bytes()); - format!("{:x}", r) +pub fn commitment(packet: &Packet) -> PacketCommitment { + let mut input = packet + .timeout_timestamp + .nanoseconds() + .to_be_bytes() + .to_vec(); + let revision_number = packet.timeout_height.revision_number.to_be_bytes(); + input.append(&mut revision_number.to_vec()); + let revision_height = packet.timeout_height.revision_height.to_be_bytes(); + input.append(&mut revision_height.to_vec()); + let data = sha2::Sha256::digest(&packet.data); + input.append(&mut data.to_vec()); + sha2::Sha256::digest(&input).to_vec().into() } /// Returns a counterparty of a connection @@ -1196,7 +1202,7 @@ pub fn make_open_init_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - IbcEvent::OpenInitConnection(ConnOpenInit::from(attributes)) + ConnOpenInit::from(attributes).into() } /// Makes OpenTryConnection event @@ -1211,7 +1217,7 @@ pub fn make_open_try_connection_event( counterparty_client_id: msg.counterparty.client_id().clone(), ..Default::default() }; - IbcEvent::OpenTryConnection(ConnOpenTry::from(attributes)) + ConnOpenTry::from(attributes).into() } /// Makes OpenAckConnection event @@ -1223,7 +1229,7 @@ pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { ), ..Default::default() }; - IbcEvent::OpenAckConnection(ConnOpenAck::from(attributes)) + ConnOpenAck::from(attributes).into() } /// Makes OpenConfirmConnection event @@ -1234,7 +1240,7 @@ pub fn make_open_confirm_connection_event( connection_id: Some(msg.connection_id.clone()), ..Default::default() }; - IbcEvent::OpenConfirmConnection(ConnOpenConfirm::from(attributes)) + ConnOpenConfirm::from(attributes).into() } /// Makes OpenInitChannel event @@ -1246,9 +1252,10 @@ pub fn make_open_init_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChannelAttributes { + let attributes = ChanOpenInit { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(channel_id.clone()), + channel_id: Some(*channel_id), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1256,9 +1263,8 @@ pub fn make_open_init_channel_event( .counterparty() .channel_id() .cloned(), - ..Default::default() }; - IbcEvent::OpenInitChannel(ChanOpenInit::from(attributes)) + attributes.into() } /// Makes OpenTryChannel event @@ -1270,9 +1276,10 @@ pub fn make_open_try_channel_event( Some(c) => c.clone(), None => ConnectionId::default(), }; - let attributes = ChannelAttributes { + let attributes = ChanOpenTry { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(channel_id.clone()), + channel_id: Some(*channel_id), connection_id, counterparty_port_id: msg.channel.counterparty().port_id().clone(), counterparty_channel_id: msg @@ -1280,54 +1287,88 @@ pub fn make_open_try_channel_event( .counterparty() .channel_id() .cloned(), - ..Default::default() }; - IbcEvent::OpenTryChannel(ChanOpenTry::from(attributes)) + attributes.into() } /// Makes OpenAckChannel event -pub fn make_open_ack_channel_event(msg: &MsgChannelOpenAck) -> IbcEvent { - let attributes = ChannelAttributes { +pub fn make_open_ack_channel_event( + msg: &MsgChannelOpenAck, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanOpenAck { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - counterparty_channel_id: Some(msg.counterparty_channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + counterparty_channel_id: Some(msg.counterparty_channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), }; - IbcEvent::OpenAckChannel(ChanOpenAck::from(attributes)) + Ok(attributes.into()) } /// Makes OpenConfirmChannel event pub fn make_open_confirm_channel_event( msg: &MsgChannelOpenConfirm, -) -> IbcEvent { - let attributes = ChannelAttributes { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanOpenConfirm { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::OpenConfirmChannel(ChanOpenConfirm::from(attributes)) + Ok(attributes.into()) } /// Makes CloseInitChannel event -pub fn make_close_init_channel_event(msg: &MsgChannelCloseInit) -> IbcEvent { - let attributes = ChannelAttributes { +pub fn make_close_init_channel_event( + msg: &MsgChannelCloseInit, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanCloseInit { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: msg.channel_id, + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::CloseInitChannel(ChanCloseInit::from(attributes)) + Ok(attributes.into()) } /// Makes CloseConfirmChannel event pub fn make_close_confirm_channel_event( msg: &MsgChannelCloseConfirm, -) -> IbcEvent { - let attributes = ChannelAttributes { + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanCloseConfirm { + height: Height::default(), port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id.clone()), - ..Default::default() + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id.clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), }; - IbcEvent::CloseConfirmChannel(ChanCloseConfirm::from(attributes)) + Ok(attributes.into()) +} + +fn get_connection_id_from_channel( + channel: &ChannelEnd, +) -> Result<&ConnectionId> { + channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel("No connection for the channel".to_owned()) + }) } /// Makes SendPacket event From 7eafff13f3e62040364519414df4e6733948b964 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 28 Sep 2022 17:05:38 +0200 Subject: [PATCH 0740/1995] change ibc-rs rev --- Cargo.lock | 88 +++++++++++--------- shared/Cargo.toml | 8 +- wasm/wasm_source/Cargo.lock | 162 ++---------------------------------- 3 files changed, 62 insertions(+), 196 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f1115df2c..e937aa80b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,14 +2868,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0", - "ics23 0.6.7", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2885,10 +2885,10 @@ 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", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-testgen", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", "time 0.3.9", "tracing 0.1.35", ] @@ -2901,8 +2901,8 @@ dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1", - "ics23 0.7.0", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2913,24 +2913,24 @@ dependencies = [ "sha2 0.10.2", "subtle-encoding", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-light-client-verifier", + "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", + "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "time 0.3.9", "tracing 0.1.35", ] [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ + "base64 0.13.0", "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?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tonic", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", ] [[package]] @@ -2946,22 +2946,6 @@ dependencies = [ "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", ] -[[package]] -name = "ics23" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" -dependencies = [ - "anyhow", - "bytes 1.1.0", - "hex", - "prost 0.9.0", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", -] - [[package]] name = "ics23" version = "0.7.0" @@ -4303,11 +4287,11 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0", - "ibc 0.14.0", - "ibc-proto 0.16.0", - "ibc-proto 0.17.1", - "ics23 0.7.0", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -6716,7 +6700,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] @@ -6981,6 +6965,19 @@ dependencies = [ "url 2.2.2", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +dependencies = [ + "derive_more", + "flex-error", + "serde 1.0.137", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "time 0.3.9", +] + [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" @@ -7094,6 +7091,21 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tendermint-testgen" +version = "0.23.5" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +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=bat/abciplus)", + "time 0.3.9", +] + [[package]] name = "tendermint-testgen" version = "0.23.5" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6bb5f5618f..08df6d1949 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,10 +86,10 @@ num-rational = "0.4.1" 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-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} -ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} -ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} +ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} +ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} +ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "6255c4e01db11781e5a8cdb89737f55a8ad81d63", default-features = false, optional = true} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "6255c4e01db11781e5a8cdb89737f55a8ad81d63", default-features = false, optional = true} ics23 = "0.7.0" itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index a71a76a13c..a156f2291b 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -193,12 +193,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" @@ -417,12 +411,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" @@ -564,18 +552,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.6" @@ -586,16 +562,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" @@ -656,15 +622,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" @@ -733,18 +690,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.5.2" @@ -787,24 +732,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" -[[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" @@ -928,16 +855,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 = "fixed-hash" version = "0.7.0" @@ -1086,17 +1003,6 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -[[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" @@ -1159,16 +1065,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 = "iana-time-zone" version = "0.1.47" @@ -1186,7 +1082,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1213,7 +1109,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1343,19 +1239,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.2" @@ -2238,17 +2121,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" @@ -2425,18 +2297,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" @@ -2566,10 +2426,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" @@ -2685,7 +2541,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2693,12 +2549,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2715,7 +2569,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2728,7 +2582,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2741,7 +2595,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2758,7 +2612,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2782,7 +2636,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", From 8869973b26fc702369b0a4e5bd9090a5db334d2b Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 28 Sep 2022 23:29:58 +0200 Subject: [PATCH 0741/1995] [fix]: updated ibc --- shared/src/ledger/storage/merkle_tree.rs | 2 +- wasm/tx_template/Cargo.lock | 162 ++--------------------- wasm/vp_template/Cargo.lock | 162 ++--------------------- wasm_for_tests/wasm_source/Cargo.lock | 162 ++--------------------- 4 files changed, 25 insertions(+), 463 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index e7936d69fd..d5a6d11ab5 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -24,8 +24,8 @@ 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::tendermint::merkle::proof::{Proof, ProofOp}; +use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::storage::{ DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, TreeBytes, diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 3afe9398ee..6415ecf977 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -546,18 +534,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" @@ -568,16 +544,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" @@ -639,15 +605,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" @@ -716,18 +673,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" @@ -770,24 +715,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" @@ -911,16 +838,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 = "fixed-hash" version = "0.7.0" @@ -1070,17 +987,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" @@ -1134,20 +1040,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1174,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1305,19 +1201,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" @@ -2181,17 +2064,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" @@ -2368,18 +2240,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" @@ -2509,10 +2369,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" @@ -2634,7 +2490,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2642,12 +2498,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2664,7 +2518,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2677,7 +2531,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2690,7 +2544,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2707,7 +2561,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2731,7 +2585,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 711a7dfa61..3f0160f2e8 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -546,18 +534,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" @@ -568,16 +544,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" @@ -639,15 +605,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" @@ -716,18 +673,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" @@ -770,24 +715,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" @@ -911,16 +838,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 = "fixed-hash" version = "0.7.0" @@ -1070,17 +987,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" @@ -1134,20 +1040,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1174,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1305,19 +1201,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" @@ -2181,17 +2064,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" @@ -2368,18 +2240,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" @@ -2509,10 +2369,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" @@ -2634,7 +2490,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2642,12 +2498,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2664,7 +2518,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2677,7 +2531,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2690,7 +2544,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2707,7 +2561,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2731,7 +2585,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 654c0b9898..7521fbccca 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -547,18 +535,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" @@ -569,16 +545,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" @@ -640,15 +606,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" @@ -717,18 +674,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.1" @@ -771,24 +716,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" @@ -912,16 +839,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 = "fixed-hash" version = "0.7.0" @@ -1071,17 +988,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.1" @@ -1144,20 +1050,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1184,7 +1080,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1315,19 +1211,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" @@ -2212,17 +2095,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" @@ -2399,18 +2271,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" @@ -2540,10 +2400,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" @@ -2665,7 +2521,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2673,12 +2529,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2695,7 +2549,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2708,7 +2562,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2721,7 +2575,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2738,7 +2592,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2762,7 +2616,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", From b74e28a497f8ca469c3157b85e74211b8b80b87a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 29 Sep 2022 08:00:16 +0000 Subject: [PATCH 0742/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index c64d9c2c9d..79da26f124 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.a6551577feaa561bd8c7e65b5f65fb7e77c29fa2ea36b4710d385accb6ce3edf.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.4840e88eed56a67a208dddaf6dbceec86e4e9611743f31bbbb9b03a316c08a73.wasm", - "tx_ibc.wasm": "tx_ibc.b019b09487f5b67a15ec82ab9ad6b124552aca2aca633d41c26b9d8c92335bb5.wasm", - "tx_init_account.wasm": "tx_init_account.7036f646916e5a7881b63e35c5013440af911aa3b2203239887c528d471f5086.wasm", - "tx_init_nft.wasm": "tx_init_nft.0d2700e73a91c4ef4937bdf8b27fdc7b8f35315691cf269596b6e9c1122a81b1.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.79e9b3cdf4361abb537c6773cee2d95ae7c3c9a2383945147d71e2b1c848e9e2.wasm", - "tx_init_validator.wasm": "tx_init_validator.3a84149e20001aefc7cc9518b5ced08887a99795c714db6151546abc4f28eb78.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.4e396d0992281a9e6a7e7ec26e78a2d094a89277968c8c045ab40b51680ae5ac.wasm", - "tx_transfer.wasm": "tx_transfer.0d6acdf8fb16785a4ff88c124b687393e55197db24a1c152193b2f221e69d6ac.wasm", - "tx_unbond.wasm": "tx_unbond.c22ef36ee25763cb039f63902afd1a926c97696d1528e0f87713363dbd00cf97.wasm", - "tx_update_vp.wasm": "tx_update_vp.5c15f73b0cdbea3a8de97855535f4013f969249bbf87e33291609cbdfba536a5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d16c745445ccb8edf0080faefa6b573b704ad14159e028bfec1852f484105212.wasm", - "tx_withdraw.wasm": "tx_withdraw.9634d84b2257d438bde3ffccf1742097582c996c4a3f4bc7aaa2a3729b057b16.wasm", - "vp_nft.wasm": "vp_nft.9885f3976c876722219b13ed47babf4dee798fd07c8f89df90a9e52a3e9d7859.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9e7803e38edf8103387a3bcc9709cceb76bc1e0170bbaba0db52461fe6cc7b5a.wasm", - "vp_token.wasm": "vp_token.8b6ea63c0f9cacb96fc7ec2855ec559701d7ed324827963ea7b908ccf5b12db6.wasm", - "vp_user.wasm": "vp_user.6ef2298f4a4e2d1537e418366fb7626479ffb8c01a6a94e311dfecf3ee40d6df.wasm" + "tx_from_intent.wasm": "tx_from_intent.c5f0ae1fa204d07620e2102632b23306fbeb44ee327f672a370d322f93bd1b9a.wasm", + "tx_ibc.wasm": "tx_ibc.401ab4f167b431e254a32cc09f72379224237e2ae90dffece49e4da97efbfd4c.wasm", + "tx_init_account.wasm": "tx_init_account.9ac1106e47bcacd26647bc939c231d16a8f804f0a4165034923abe5d483673d5.wasm", + "tx_init_nft.wasm": "tx_init_nft.b27740719a3f8bf0de74b6158c659377cf8ecdb6c98c18bfe8dac8343043d815.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3d929b91b9860b69f80c812ed88e04cdf201e87016189065d39645babc2e0934.wasm", + "tx_init_validator.wasm": "tx_init_validator.2d90ebc82e8907a883e0c3ac932a54085823702628efe0822c000965b59b55cb.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.8e085a930024ddd772e087f87a2f7d1d307e134e1181f4f7cf396d77999d8f5c.wasm", + "tx_transfer.wasm": "tx_transfer.454172df7da0d31a6231a031279d54cff6735538c116435ff18bab0fd8a9d8e2.wasm", + "tx_unbond.wasm": "tx_unbond.36ffd754fb6790a8b6f4f2666eb8fdd3ba23a0b6b88b31142d3e294281d70ef2.wasm", + "tx_update_vp.wasm": "tx_update_vp.f5a0f3b46f1c28ed4e26eb8b9c31ab5aa62458bc16490fd37a960757e3a2928c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9bb61c5f4610405a573e233a276df4a8c740ce8689f50c58fc94d460421480ef.wasm", + "tx_withdraw.wasm": "tx_withdraw.4741bb87e8d88ff04589d7e84c7e316d98c35cc5f3759d61e1c31df1b539977f.wasm", + "vp_nft.wasm": "vp_nft.98f7a5915aa9790346827479377aea0cbe2db87ceef925b21db847a2f14e8b73.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.006a91027a36ae62a84a3279fc8f4a2138238c104a1c6b3ccb68e5b8b461ea32.wasm", + "vp_token.wasm": "vp_token.7e48d02781760550f5e8102916acf08a81990dc48cca10acf81bce3269bbc74d.wasm", + "vp_user.wasm": "vp_user.6e896c3d01f13e6a1d20c39b03289ff045835f2ba3f8c06974bcd4b8ebc051d6.wasm" } \ No newline at end of file From 2c63406386f27d8e093e6064cb7d7d533e6a6b2c Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 29 Sep 2022 10:20:30 +0200 Subject: [PATCH 0743/1995] feat: finished base implementation, need to debug --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 3 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 78 +++--- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- shared/src/ledger/storage/merkle_tree.rs | 226 ++++++------------ shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 180 ++++++++++---- shared/src/types/eth_bridge_pool.rs | 14 +- shared/src/types/hash.rs | 10 +- shared/src/types/storage.rs | 38 ++- 9 files changed, 289 insertions(+), 265 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b05b4db7be..12fa7982eb 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -18,7 +18,8 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::traits::StorageHasher; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index e15de60cb2..48266bde17 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -1,19 +1,19 @@ //! Tools for accessing the storage subspaces of the Ethereum //! bridge pool -use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::convert::TryInto; use std::ops::Deref; -use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; +use crate::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; -use crate::types::keccak::{keccak_hash, KeccakHash}; +use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; -use crate::types::hash::{Hash, keccak_hash}; +use crate::types::keccak::{keccak_hash, KeccakHash}; use crate::types::storage::{DbKeySeg, Key}; -use crate::ledger::storage::{Sha256Hasher, StorageHasher}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -67,13 +67,13 @@ pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, /// The underlying storage - store: BTreeMap, + store: BTreeSet, } impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool - pub fn new(root: KeccakHash, store: BTreeMap) -> Self { - Self{ root, store } + pub fn new(root: KeccakHash, store: BTreeSet) -> Self { + Self { root, store } } /// Parse the key to ensure it is of the correct type. @@ -81,25 +81,22 @@ impl BridgePoolTree { /// If it is, it can be converted to a hash. /// Checks if the hash is in the tree. pub fn has_key(&self, key: &Key) -> Result { - Ok(self.store.contains_key(&Self::parse_key(key)?)) + Ok(self.store.contains(&Self::parse_key(key)?)) } /// Update the tree with a new value. /// /// Returns the new root if successful. Will /// return an error if the key is malformed. - pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { + pub fn update(&mut self, key: &Key) -> Result { let hash = Self::parse_key(key)?; - if hash != value.keccak256() { - return eyre!("Key does not match hash of the value")?; - } - _ = self.store.insert(hash, value); + _ = self.store.insert(hash); self.root = self.compute_root(); Ok(self.root()) } /// Delete a key from storage and update the root - pub fn delete(&mut self, key: &Key) -> Result<(), Error>{ + pub fn delete(&mut self, key: &Key) -> Result<(), Error> { let hash = Self::parse_key(key)?; _ = self.store.remove(&hash); self.root = self.compute_root(); @@ -109,13 +106,12 @@ impl BridgePoolTree { /// Compute the root of the merkle tree pub fn compute_root(&self) -> KeccakHash { let mut leaves = self.store.iter(); - let mut root = if let Some((hash, _)) = leaves.next() { + let mut root = if let Some(hash) = leaves.next() { hash.clone() } else { return Default::default(); }; - - for (leaf, _) in leaves { + for leaf in leaves { root = keccak_hash([root.0, leaf.0].concat()); } root @@ -127,33 +123,43 @@ impl BridgePoolTree { } /// Get a reference to the backing store - pub fn store(&self) -> &BTreeMap { + pub fn store(&self) -> &BTreeSet { &self.store } /// Create a batched membership proof for the provided keys - pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { - let mut leaves : std::collections::BTreeSet = Default::default(); + pub fn membership_proof( + &self, + keys: &[Key], + mut values: Vec, + ) -> Result { + if values.len() != keys.len() { + return eyre!( + "The number of leaves and leaf hashes must be equal." + )?; + } + values.sort(); + let mut leaves: std::collections::BTreeSet = + Default::default(); for key in keys { leaves.insert(Self::parse_key(key)?); } - let mut proof_leaves = vec![]; + let mut proof_hashes = vec![]; let mut flags = vec![]; - for (hash, value) in self.store { + for hash in self.store { if leaves.contains(&hash) { flags.push(true); - proof_leaves.push(value); } else { flags.push(false); proof_hashes.push(hash); } } - BridgePoolProof { + Ok(BridgePoolProof { proof: proof_hashes, - leaves: proof_leaves, - flags - } + leaves: values, + flags, + }) } /// Parse a db key to see if it is valid for the @@ -164,13 +170,18 @@ impl BridgePoolTree { fn parse_key(key: &Key) -> Result { if key.segments.len() == 1 { match &key.segments[0] { - DbKeySeg::StringSeg(str) => str.as_str().try_into().ok_or( - eyre!("Could not parse key segment as a hash")? - ), - _ => eyre!("Bridge pool keys should be strings, not addresses")? + DbKeySeg::StringSeg(str) => str + .as_str() + .try_into() + .ok_or(eyre!("Could not parse key segment as a hash")?), + _ => { + eyre!("Bridge pool keys should be strings, not addresses")? + } } } else { - eyre!("Key for the bridge pool should not have more than one segment")? + eyre!( + "Key for the bridge pool should not have more than one segment" + )? } } } @@ -186,7 +197,6 @@ pub struct BridgePoolProof { } impl BridgePoolProof { - /// Verify a membership proof matches the provided root pub fn verify(&self, root: KeccakHash) -> bool { if self.proof.len() + self.leaves.len() != self.flags.len() { diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index cdcd1815ea..0e62f7270f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::StorageHasher; +use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::Amount; diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index cc26b95b5d..58b05faa2f 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -2,6 +2,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::marker::PhantomData; +use std::slice::from_ref; use std::str::FromStr; use arse_merkle_tree::default_store::DefaultStore; @@ -22,16 +23,19 @@ use sha2::{Digest, Sha256}; use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; +use super::traits::{self, Sha256Hasher, StorageHasher}; use super::IBC_KEY_LIMIT; -use super::traits::{self, StorageHasher, Sha256Hasher}; use crate::bytes::ByteBuf; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; -use crate::ledger::storage::types; +use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; use crate::types::ethereum_events::KeccakHash; use crate::types::hash::Hash; -use crate::types::storage::{DbKeySeg, Error as StorageError, Key, StringKey, TreeBytes, MerkleValue}; +use crate::types::storage::{ + DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, + StringKey, TreeBytes, +}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -50,6 +54,10 @@ pub enum Error { NonExistenceProof(String), #[error("Invalid value given to sub-tree storage")] InvalidValue, + #[error("ICS23 commitment proofs do not support multiple leaves")] + Ics23MultiLeaf, + #[error("A Tendermint proof can only be constructed from an ICS23 proof.")] + TendermintProof, } /// Result for functions that may fail @@ -58,7 +66,7 @@ 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 BridgePoolStore = std::collections::BTreeMap; +pub type BridgePoolStore = std::collections::BTreeSet; pub type Smt = ArseMerkleTree; pub type Amt = ArseMerkleTree; @@ -99,7 +107,7 @@ pub enum Store { /// For PoS-related data PoS(SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(BTreeSetStore) + BridgePool(BTreeSetStore), } impl Store { @@ -125,7 +133,7 @@ pub enum StoreRef<'a> { /// For PoS-related data PoS(&'a SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(&'a BTreeSetStore) + BridgePool(&'a BTreeSetStore), } impl<'a> StoreRef<'a> { @@ -268,7 +276,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, @@ -288,7 +297,10 @@ impl MerkleTree { } } - fn tree_mut(&mut self, store_type: &StoreType) -> &mut impl traits::MerkleTree { + fn tree_mut( + &mut self, + store_type: &StoreType, + ) -> &mut impl traits::MerkleTree { match store_type { StoreType::Base => &mut self.base, StoreType::Account => &mut self.account, @@ -320,7 +332,11 @@ impl MerkleTree { } /// Update the tree with the given key and value - pub fn update>(&mut self, key: &Key, value: impl Into>) -> Result<()> { + pub fn update>( + &mut self, + key: &Key, + value: impl Into>, + ) -> Result<()> { let (store_type, sub_key) = StoreType::sub_key(key)?; self.update_tree(&store_type, sub_key.into(), value.into()) } @@ -343,94 +359,70 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(). self.) + bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), } } - /// Get the existence proof - pub fn get_existence_proof( + /// Get the existence proof from a sub-tree + pub fn get_sub_tree_existence_proof>( &self, - key: &Key, - value: Vec, - ) -> Result { - let (store_type, sub_key) = StoreType::sub_key(key)?; - 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!(), - } + keys: &[Key], + values: Vec>, + ) -> Result { + let first_key = keys.iter().next().ok_or(Error::InvalidMerkleKey( + "No keys provided for existence proof.".into(), + ))?; + let (store_type, _) = StoreType::sub_key(first_key)?; + if !keys.iter().all(|k| { + if let Ok((s, _)) = StoreType::sub_key(k) { + s == store_type + } else { + false } - Either::Right(amt) => { - 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 - match cp.proof.expect("The proof should exist") { - Ics23Proof::Exist(ep) => CommitmentProof { - proof: Some(Ics23Proof::Exist(ExistenceProof { - leaf: Some(self.ibc_leaf_spec()), - ..ep - })), - }, - _ => unreachable!(), - } - } - }; - self.get_proof(key, sub_proof) + }) { + return Err(Error::InvalidMerkleKey( + "Cannot construct inclusion proof for keys in separate \ + sub-trees." + .into(), + )); + } + self.tree(&store_type).membership_proof(keys, values) } /// 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 sub_proof = match self.tree(&store_type) { - Either::Left(_) => { - return Err(Error::NonExistenceProof(store_type.to_string())); - } - Either::Right(amt) => { - let key = - StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; - let mut nep = amt.non_membership_proof(&key)?; - // 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(self.ibc_leaf_spec()); - } - _ => unreachable!(), - } + if store_type != StoreType::Ibc { + return Err(Error::NonExistenceProof(store_type.to_string())); + } + + let key = StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = self.ibc.non_membership_proof(&key)?; + // 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(self.ibc_leaf_spec()); } - nep + _ => unreachable!(), } - }; + } + // Get a proof of the sub tree self.get_proof(key, sub_proof) } /// Get the Tendermint proof with the base proof - fn get_proof( + pub fn get_tendermint_proof>( &self, - key: &Key, sub_proof: CommitmentProof, ) -> Result { let mut data = vec![]; @@ -453,7 +445,7 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: base_key.as_bytes().to_vec(), - leaf: Some(self.base_leaf_spec()), + leaf: Some(ics23_specs::base_leaf_spec::()), ..ep })), }, @@ -476,73 +468,6 @@ impl MerkleTree { ops: vec![sub_proof_op, base_proof_op], }) } - - /// Get the proof specs - pub fn 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.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 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: H::hash_op().into(), - prehash_value: HashOp::NoHash.into(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } - - /// Get the leaf spec for the subtree. Non-hashed values are used for the - /// 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(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } } /// The root hash of the merkle tree as bytes @@ -607,7 +532,7 @@ pub struct MerkleTreeStoresWrite<'a> { account: (Hash, &'a SmtStore), ibc: (Hash, &'a AmtStore), pos: (Hash, &'a SmtStore), - bridge_pool: (Hash, &'a BTreeSetStore) + bridge_pool: (Hash, &'a BTreeSetStore), } impl<'a> MerkleTreeStoresWrite<'a> { @@ -618,7 +543,7 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => &self.account.0, StoreType::Ibc => &self.ibc.0, StoreType::PoS => &self.pos.0, - StoreType::BridgePool => &self.bridge_pool.0 + StoreType::BridgePool => &self.bridge_pool.0, } } @@ -629,12 +554,11 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => StoreRef::Account(self.account.1), StoreType::Ibc => StoreRef::Ibc(self.ibc.1), StoreType::PoS => StoreRef::PoS(self.pos.1), - StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1) + StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1), } } } - impl From for Error { fn from(error: StorageError) -> Self { Error::InvalidKey(error) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 4def551e0f..74826fba3c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1,11 +1,12 @@ //! Ledger's state storage with key-value backed store and a merkle tree +mod ics23_specs; mod merkle_tree; #[cfg(any(test, feature = "testing"))] pub mod mockdb; +pub mod traits; pub mod types; pub mod write_log; -pub mod traits; use core::fmt::Debug; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index d5411cc966..c558c7cbe9 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -3,27 +3,36 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; -use arse_merkle_tree::{H256, Hash as SmtHash, Key as TreeKey}; -use arse_merkle_tree::traits::{Value, Hasher}; -use ibc_proto::ics23::CommitmentProof; +use arse_merkle_tree::traits::{Hasher, Value}; +use arse_merkle_tree::{Hash as SmtHash, Key as TreeKey, H256}; +use ics23::commitment_proof::Proof as Ics23Proof; +use ics23::{CommitmentProof, ExistenceProof}; use sha2::{Digest, Sha256}; -use super::IBC_KEY_LIMIT; -use super::merkle_tree::{Smt, Amt, Error}; +use super::merkle_tree::{Amt, Error, Smt}; +use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::storage::{ - MerkleKey, StringKey, TreeBytes, MerkleValue, Key + Key, MembershipProof, MerkleValue, StringKey, TreeBytes, }; pub trait MerkleTree { type Error; fn has_key(&self, key: &Key) -> Result; - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result; + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result; fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; - fn membership_proof(&self, keys: &[Key], proof: Option) -> Option; + fn membership_proof>( + &self, + keys: &[Key], + values: Vec>, + ) -> Result; } impl MerkleTree for Smt { @@ -35,28 +44,56 @@ impl MerkleTree for Smt { .map_error(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { let value = match value { MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_ref()) .map_err(|| Error::InvalidValue)?, - _ => return Err(Error::InvalidValue) + _ => return Err(Error::InvalidValue), }; - self.update( - H::hash(key.to_string()).into(), - value, - ) - .map(Hash::into) - .map_err(|err| Error::MerkleTree(err.to_string())) + self.update(H::hash(key.to_string()).into(), value) + .map(Hash::into) + .map_err(|err| Error::MerkleTree(err.to_string())) } fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { let value = Hash::zero(); - self.update( - H::hash(key.to_string()).into(), - value - ) - .and(Ok(())) - .map_err(|err|Error::MerkleTree(err.to_string())) + self.update(H::hash(key.to_string()).into(), value) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn membership_proof>( + &self, + keys: &[Key], + mut values: Vec>, + ) -> Result { + if keys.len() != 1 || values.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + let key: &Key = &keys[0]; + let value = match values.remove(0) { + MerkleValue::Bytes(b) => b.as_ref().to_vec(), + _ => return Err(Error::InvalidValue), + }; + let cp = self.membership_proof(&H::hash(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) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + key: key.to_string().as_bytes().to_vec(), + value, + leaf: Some(ics23_specs::leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } } } @@ -64,38 +101,60 @@ impl MerkleTree for Amt { type Error = Error; fn has_key(&self, key: &Key) -> Result { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; self.get(&key) .and(Ok(bool)) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: MerkleKey, value: MerkleValue) -> Result { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + fn update>( + &mut self, + key: MerkleKey, + value: MerkleValue, + ) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = match value { - MerkleValue::Bytes(bytes) => TreeBytes::from(bytes.as_ref().to_vec()), - _ => return Err(Error::InvalidValue) + MerkleValue::Bytes(bytes) => { + TreeBytes::from(bytes.as_ref().to_vec()) + } + _ => return Err(Error::InvalidValue), }; - self.update( - key, - value, - ) - .map(Into::into) - .map_err(|err|Error::MerkleTree(err.to_string())) + self.update(key, value) + .map(Into::into) + .map_err(|err| Error::MerkleTree(err.to_string())) } fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = TreeBytes::zero(); - self.update( - key, - value - ) - .and(Ok(())) - .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + self.update(key, value) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + } + + fn membership_proof>( + &self, + keys: &[Key], + _: Vec>, + ) -> Result { + if keys.len() != 1 || values.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + + let key = StringKey::try_from_bytes(&keys[0].to_string().as_bytes())?; + let cp = self.membership_proof(&key)?; + // Replace the values and the leaf op for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + leaf: Some(ics23_specs::ibc_leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } } } @@ -107,10 +166,14 @@ impl MerkleTree for BridgePoolTree { .map_err(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { - if let MerkleValue::Transfer(transfer) = value { - self.update(key, transfer) - .map_err( | err| Error::MerkleTree(err.to_string())) + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + if let MerkleValue::Transfer(_) = value { + self.update(key) + .map_err(|err| Error::MerkleTree(err.to_string())) } else { Err(Error::InvalidValue) } @@ -120,6 +183,23 @@ impl MerkleTree for BridgePoolTree { self.delete(key) .map_err(|err| Error::MerkleTree(err.to_string())) } + + fn membership_proof>( + &self, + keys: &[Key], + values: Vec>, + ) -> Result { + let values = values + .into_iter() + .filter_map(|val| match val { + MerkleValue::Transfer(transfer) => Some(transfer), + _ => None, + }) + .collect(); + self.membership_proof(keys, values) + .map(Into::into) + .map_err(|err| Error::MerkleTree(err.to_string())) + } } impl TreeKey for StringKey { @@ -161,10 +241,6 @@ impl Value for TreeBytes { } } -pub trait MembershipProof { }; - -impl MembershipProof for CommitmentProof; - /// The storage hasher used for the merkle tree. pub trait StorageHasher: Hasher + Default { /// Hash the value to store @@ -211,4 +287,4 @@ impl fmt::Debug for Sha256Hasher { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sha256Hasher") } -} \ No newline at end of file +} diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 17ba1f57e7..19c0f805cd 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,10 +1,10 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool -use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, Uint, KeccakHash}; +use crate::types::ethereum_events::{EthAddress, KeccakHash, Uint}; use crate::types::keccak; use crate::types::token::Amount; @@ -54,24 +54,16 @@ pub struct PendingTransfer { } impl keccak::encode::Encode for PendingTransfer { - fn tokenize(&self) -> Vec { let from = Token::String(self.gas_fee.payer.to_string()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.into()); - vec![ - from, - fee, - to, - amount, - nonce, - ] + vec![from, fee, to, amount, nonce] } } - /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e4ac6e8c2c..f2a84f19eb 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -25,7 +25,7 @@ pub enum Error { #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), #[error("Failed to convert string into a hash: {0}")] - FromStringError(hex::FromHexError) + FromStringError(hex::FromHexError), } /// Result for functions that may fail @@ -93,7 +93,8 @@ impl TryFrom for Hash { type Error = self::Error; fn try_from(string: String) -> HashResult { - let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; Self::try_from(&bytes) } } @@ -102,7 +103,8 @@ impl TryFrom<&str> for Hash { type Error = self::Error; fn try_from(string: &str) -> HashResult { - let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; Self::try_from(&bytes) } } @@ -165,4 +167,4 @@ impl Value for Hash { fn zero() -> Self { Hash([0u8; 32]) } -} \ No newline at end of file +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 7b7ffad7a2..e13aa14dc5 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -9,17 +9,17 @@ use std::str::FromStr; use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ics23::CommitmentProof; use serde::{Deserialize, Serialize}; use thiserror::Error; -use variant_access_derive::*; -use variant_access_traits::*; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; -use crate::types::eth_bridge_pool::{TransferToEthereum, PendingTransfer}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -35,7 +35,7 @@ pub enum Error { #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), #[error("Could not parse string: '{0}' into requested type: {1}")] - ParseError((String, String)) + ParseError(String, String), } /// Result for functions that may fail @@ -248,15 +248,13 @@ impl FromStr for Key { pub enum MerkleValue> { /// raw bytes Bytes(T), - /// a transfer to be put in the Ethereum bridge pool - /// We actually only need the key (which is the hash - /// of the transfer). So this variant contains no data. + /// A transfer to be put in the Ethereum bridge pool. Transfer(PendingTransfer), } impl From for MerkleValue where - T: AsRef<[u8]> + T: AsRef<[u8]>, { fn from(bytes: T) -> Self { Self::Bytes(bytes) @@ -348,6 +346,26 @@ impl From for Vec { } } +/// Type of membership proof from a merkle tree +pub enum MembershipProof { + /// ICS23 compliant membership proof + ICS23(CommitmentProof), + /// Bespoke membership proof for the Ethereum bridge pool + BridgePool(BridgePoolProof), +} + +impl From for MembershipProof { + fn from(proof: CommitmenProof) -> Self { + Self::ICS23(proof) + } +} + +impl From for MembershipProof { + fn from(proof: BridgePoolProof) -> Self { + Self::BridgePool(proof) + } +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { @@ -609,7 +627,8 @@ impl KeySeg for Address { impl KeySeg for Hash { fn parse(seg: String) -> Result { - seg.try_into().map_error(Error::ParseError((seg, "Hash".into()))) + seg.try_into() + .map_error(Error::ParseError((seg, "Hash".into()))) } fn raw(&self) -> String { @@ -670,7 +689,6 @@ impl Add for Epoch { } } - impl Sub for Epoch { type Output = Epoch; From 463089d9b30c2753835842b4b636c38596351224 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 29 Sep 2022 10:26:41 +0200 Subject: [PATCH 0744/1995] [chore]: Rebase on eth-bridge-intregration --- Cargo.lock | 270 +++++++++++++++++- apps/src/lib/node/ledger/protocol/mod.rs | 3 - shared/Cargo.toml | 2 + .../ledger/eth_bridge/storage/bridge_pool.rs | 158 ++++++++++ shared/src/ledger/storage/merkle_tree.rs | 250 +++++----------- shared/src/ledger/storage/mod.rs | 1 + shared/src/ledger/storage/traits.rs | 214 ++++++++++++++ shared/src/types/eth_bridge_pool.rs | 28 +- shared/src/types/hash.rs | 23 +- shared/src/types/storage.rs | 57 +++- 10 files changed, 806 insertions(+), 200 deletions(-) create mode 100644 shared/src/ledger/storage/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 34a1cf2c90..adbc38f162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,6 +1131,28 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chrono-tz" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "chunked_transfer" version = "1.4.0" @@ -1707,6 +1729,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + [[package]] name = "diff" version = "0.1.12" @@ -2506,6 +2534,17 @@ dependencies = [ "regex", ] +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "gloo-timers" version = "0.2.4" @@ -2761,6 +2800,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + [[package]] name = "hyper" version = "0.10.16" @@ -3027,6 +3072,24 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils 0.8.8", + "globset", + "lazy_static 1.4.0", + "log 0.4.17", + "memchr", + "regex", + "same-file", + "thread_local 1.1.4", + "walkdir", + "winapi-util", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -3953,6 +4016,12 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4321,6 +4390,8 @@ dependencies = [ "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", + "variant_access_derive", + "variant_access_traits", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4391,7 +4462,7 @@ dependencies = [ "rocksdb", "rpassword", "semver 1.0.14", - "serde 1.0.144", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_regex", @@ -5075,6 +5146,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.7" @@ -5141,6 +5221,40 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -5161,6 +5275,45 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "0.4.29" @@ -6611,12 +6764,27 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -7122,6 +7290,28 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "tera" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static 1.4.0", + "percent-encoding 2.1.0", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde 1.0.137", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "term_size" version = "0.3.2" @@ -7946,6 +8136,65 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check 0.9.4", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "1.4.2" @@ -8106,6 +8355,25 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "variant_access_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd235ffafb854ed81b49217ce411e850a39628a5d26740ecfb60421c873d834" +dependencies = [ + "lazy_static 1.4.0", + "quote", + "syn", + "tera", + "variant_access_traits", +] + +[[package]] +name = "variant_access_traits" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d75c5a83bb8912dd9c628adf954c9f9bff74a4e170d2c90242f4e56a0d643e" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 9b1b13c859..90bb6994b1 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -204,9 +204,6 @@ where /// containing changed keys and the like should be returned in the normal way. pub(crate) fn apply_protocol_tx<'a, D, H>( tx: ProtocolTxType, - // TODO: eventually this `storage` parameter could be tightened further to - // an impl trait of only the subset of [`Storage`] functionality that we - // need _storage: &'a mut Storage, ) -> Result where diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 08df6d1949..e66f2b5ed7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -117,6 +117,8 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" +variant_access_derive = "0.4.1" +variant_access_traits = "0.4.1" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} wasmer-compiler-singlepass = {version = "=2.2.0", optional = true} diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index b86266f124..8588ae0550 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -1,8 +1,18 @@ //! Tools for accessing the storage subspaces of the Ethereum //! bridge pool +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::ops::Deref; + +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use eyre::eyre; use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::ethereum_events::KeccakHash; +use crate::types::hash::{Hash, keccak_hash}; use crate::types::storage::{DbKeySeg, Key}; +use crate::ledger::storage::{Sha256Hasher, StorageHasher}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -12,6 +22,11 @@ const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error that may be returned by the validity predicate +pub struct Error(#[from] eyre::Error); + /// Get the storage key for the transfers in the pool pub fn get_pending_key() -> Key { Key { @@ -44,3 +59,146 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { pub fn is_protected_storage(key: &Key) -> bool { is_bridge_pool_key(key) && *key != get_pending_key() } + +/// A simple Merkle tree for the Ethereum bridge pool +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct BridgePoolTree { + /// Root of the tree + root: KeccakHash, + /// The underlying storage + store: BTreeMap +} + +/// TODO: Hash ABI instead of Borsh +impl BridgePoolTree { + /// Create a new merkle tree for the Ethereum bridge pool + pub fn new(root: KeccakHash, store: BTreeMap) -> Self { + Self{ root, store } + } + + /// Parse the key to ensure it is of the correct type. + /// + /// If it is, it can be converted to a hash. + /// Checks if the hash is in the tree. + pub fn has_key(&self, key: &Key) -> Result { + Ok(self.store.contains_key(&Self::parse_key(key)?)) + } + + /// Update the tree with a new value. + /// + /// Returns the new root if successful. Will + /// return an error if the key is malformed. + pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { + let hash = Self::parse_key(key)?; + if hash != keccak_hash(&value.try_to_vec().unwrap()) { + return eyre!("Key does not match hash of the value")?; + } + _ = self.store.insert(hash, value); + self.root = self.compute_root(); + Ok(self.root()) + } + + /// Delete a key from storage and update the root + pub fn delete(&mut self, key: &Key) -> Result<(), Error>{ + let hash = Self::parse_key(key)?; + _ = self.store.remove(&hash); + self.root = self.compute_root(); + Ok(()) + } + + /// Compute the root of the merkle tree + pub fn compute_root(&self) -> KeccakHash { + let mut leaves = self.store.iter(); + let mut root = if let Some((hash, _)) = leaves.next() { + hash.clone() + } else { + return Default::default(); + }; + + for (leaf, _) in leaves { + root = keccak_hash([root.0, leaf.0].concat()); + } + root + } + + /// Return the root as a [`Hash`] type. + pub fn root(&self) -> Hash { + self.root.clone().into() + } + + /// Get a reference to the backing store + pub fn store(&self) -> &BTreeMap { + &self.store + } + + pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { + for (key, value) in self.store { + + } + } + + /// Parse a db key to see if it is valid for the + /// bridge pool. + /// + /// It should have one string segment which should + /// parse into a [Hash] + fn parse_key(key: &Key) -> Result { + if key.segments.len() == 1 { + match &key.segments[0] { + DbKeySeg::StringSeg(str) => str.as_str().try_into().ok_or( + eyre!("Could not parse key segment as a hash")? + ), + _ => eyre!("Bridge pool keys should be strings, not addresses")? + } + } else { + eyre!("Key for the bridge pool should not have more than one segment")? + } + } +} + +/// A multi-leaf membership proof +pub struct BridgePoolProof { + /// The hashes other than the provided leaves + pub proof: Vec, + /// The leaves; must be sorted + pub leaves: Vec, + /// flags to indicate how to combine hashes + pub flags: Vec, +} + +impl BridgePoolProof { + + /// Verify a membership proof matches the provided root + pub fn verify(&self, root: KeccakHash) -> bool { + if self.proof.len() + self.leaves.len() != self.flags.len() { + return false; + } + if self.flags.len() == 0 { + return true; + } + let mut leaf_pos = 0usize; + let mut proof_pos = 0usize; + let mut computed; + if self.flags[0] { + leaf = self.leaves[leaf_pos].clone(); + computed = keccak_hash(leaf.try_to_vec().unwrap()); + leaf_pos += 1; + } else { + computed = self.proof[proof_pos].clone(); + proof_pos += 1; + } + for flag in 1..self.flages.len() { + let mut next_hash; + if self.flags[flag] { + leaf = self.leaves[leaf_pos].clone(); + next_hash = keccak_hash(leaf.try_to_vec().unwrap()); + leaf_pos += 1; + } else { + next_hash = self.proof[proof_pos].clone(); + proof_pos += 1; + } + computed = keccak_hash([computed, next_hash].concat()); + } + computed == root + } +} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index d5a6d11ab5..b2598aa210 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -16,20 +16,22 @@ use ics23::{ CommitmentProof, ExistenceProof, HashOp, LeafOp, LengthOp, NonExistenceProof, ProofSpec, }; -use itertools::Either; +use itertools::{Either, Itertools}; use prost::Message; use sha2::{Digest, Sha256}; use thiserror::Error; use super::IBC_KEY_LIMIT; +use super::traits::{self, StorageHasher, Sha256Hasher}; use crate::bytes::ByteBuf; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::ledger::storage::types; use crate::tendermint::merkle::proof::{Proof, ProofOp}; use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::ethereum_events::KeccakHash; use crate::types::hash::Hash; -use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, TreeBytes, -}; +use crate::types::storage::{DbKeySeg, Error as StorageError, Key, StringKey, TreeBytes, MerkleValue}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -41,11 +43,13 @@ pub enum Error { #[error("Empty Key: {0}")] EmptyKey(String), #[error("Merkle Tree error: {0}")] - MerkleTree(MtError), + MerkleTree(String), #[error("Invalid store type: {0}")] StoreType(String), #[error("Non-existence proofs not supported for store type: {0}")] NonExistenceProof(String), + #[error("Invalid value given to sub-tree storage")] + InvalidValue, } /// Result for functions that may fail @@ -54,6 +58,7 @@ 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 BridgePoolStore = std::collections::BTreeMap; pub type Smt = ArseMerkleTree; pub type Amt = ArseMerkleTree; @@ -79,6 +84,8 @@ pub enum StoreType { Ibc, /// For PoS-related data PoS, + /// For the Ethereum bridge Pool transfers + BridgePool, } /// Backing storage for merkle trees @@ -91,6 +98,8 @@ pub enum Store { Ibc(AmtStore), /// For PoS-related data PoS(SmtStore), + /// For the Ethereum bridge Pool transfers + BridgePool(BTreeSetStore) } impl Store { @@ -100,6 +109,7 @@ impl Store { Self::Account(store) => StoreRef::Account(store), Self::Ibc(store) => StoreRef::Ibc(store), Self::PoS(store) => StoreRef::PoS(store), + Self::BridgePool(store) => StoreRef::BridgePool(store), } } } @@ -114,6 +124,8 @@ pub enum StoreRef<'a> { Ibc(&'a AmtStore), /// For PoS-related data PoS(&'a SmtStore), + /// For the Ethereum bridge Pool transfers + BridgePool(&'a BTreeSetStore) } impl<'a> StoreRef<'a> { @@ -123,6 +135,7 @@ impl<'a> StoreRef<'a> { Self::Account(store) => Store::Account(store.to_owned()), Self::Ibc(store) => Store::Ibc(store.to_owned()), Self::PoS(store) => Store::PoS(store.to_owned()), + Self::BridgePool(store) => Store::BridgePool(store.to_owned()), } } @@ -132,6 +145,7 @@ impl<'a> StoreRef<'a> { Self::Account(store) => store.try_to_vec(), Self::Ibc(store) => store.try_to_vec(), Self::PoS(store) => store.try_to_vec(), + Self::BridgePool(store) => store.try_to_vec(), } .expect("Serialization failed") } @@ -140,11 +154,12 @@ impl<'a> StoreRef<'a> { impl StoreType { /// Get an iterator for the base tree and subtrees pub fn iter() -> std::slice::Iter<'static, Self> { - static SUB_TREE_TYPES: [StoreType; 4] = [ + static SUB_TREE_TYPES: [StoreType; 5] = [ StoreType::Base, StoreType::Account, StoreType::PoS, StoreType::Ibc, + StoreType::BridgePool, ]; SUB_TREE_TYPES.iter() } @@ -162,6 +177,9 @@ impl StoreType { InternalAddress::Ibc => { Ok((StoreType::Ibc, key.sub_key()?)) } + InternalAddress::EthBridgePool => { + Ok((StoreType::BridgePool, key.sub_key()?)) + } // use the same key for Parameters _ => Ok((StoreType::Account, key.clone())), } @@ -190,6 +208,9 @@ impl StoreType { Self::PoS => Ok(Store::PoS( types::decode(bytes).map_err(Error::CodingError)?, )), + Self::BridgePool => Ok(Store::BridgePool( + types::decode(bytes).map_err(Error::CodingError)?, + )), } } } @@ -203,6 +224,7 @@ impl FromStr for StoreType { "account" => Ok(StoreType::Account), "ibc" => Ok(StoreType::Ibc), "pos" => Ok(StoreType::PoS), + "eth_bridge_pool" => Ok(StoreType::BridgePool), _ => Err(Error::StoreType(s.to_string())), } } @@ -215,6 +237,7 @@ impl fmt::Display for StoreType { StoreType::Account => write!(f, "account"), StoreType::Ibc => write!(f, "ibc"), StoreType::PoS => write!(f, "pos"), + StoreType::BridgePool => write!(f, "eth_bridge_pool"), } } } @@ -226,6 +249,7 @@ pub struct MerkleTree { account: Smt, ibc: Amt, pos: Smt, + bridge_pool: BridgePoolTree, } impl core::fmt::Debug for MerkleTree { @@ -244,47 +268,43 @@ impl MerkleTree { 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); - + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, ibc, pos, + bridge_pool, + } + } + + fn tree(&self, store_type: &StoreType) -> &impl traits::MerkleTree { + match store_type { + StoreType::Base => &self.base, + StoreType::Account => &self.account, + StoreType::Ibc => &self.ibc, + StoreType::PoS => &self.pos, + StoreType::BridgePool => &self.bridge_pool, } } - fn tree(&self, store_type: &StoreType) -> Either<&Smt, &Amt> { + fn tree_mut(&mut self, store_type: &StoreType) -> &mut impl traits::MerkleTree { match store_type { - StoreType::Base => Either::Left(&self.base), - StoreType::Account => Either::Left(&self.account), - StoreType::Ibc => Either::Right(&self.ibc), - StoreType::PoS => Either::Left(&self.pos), + StoreType::Base => &mut self.base, + StoreType::Account => &mut self.account, + StoreType::Ibc => &mut self.ibc, + StoreType::PoS => &mut self.pos, + StoreType::BridgePool => &mut self.bridge_pool, } } - fn update_tree( + fn update_tree>( &mut self, store_type: &StoreType, - key: MerkleKey, - value: Either, + key: &Key, + value: MerkleValue, ) -> Result<()> { - let sub_root = match store_type { - StoreType::Account => self - .account - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - StoreType::Ibc => self - .ibc - .update(key.try_into()?, value.unwrap_right()) - .map_err(Error::MerkleTree)?, - StoreType::PoS => self - .pos - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - // base tree should not be directly updated - StoreType::Base => unreachable!(), - }; - + let sub_root = self.tree_mut(store_type).update(key, value)?; // 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()); @@ -296,41 +316,19 @@ 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 value = match self.tree(&store_type) { - Either::Left(smt) => { - 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)?.is_zero() - } - }; - Ok(!value) + self.tree(&store_type).has_key(&sub_key) } /// 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; - 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) + pub fn update>(&mut self, key: &Key, value: impl Into>) -> Result<()> { + let (store_type, sub_key) = StoreType::sub_key(key)?; + self.update_tree(&store_type, sub_key.into(), value.into()) } /// 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; - 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) + let (store_type, sub_key) = StoreType::sub_key(key)?; + self.tree_mut(&store_type).delete(&sub_key) } /// Get the root @@ -345,6 +343,7 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), + bridge_pool: (self.bridge_pool.root(). self.) } } @@ -568,45 +567,6 @@ impl fmt::Display for MerkleRoot { } } -impl From<(StoreType, Key)> for MerkleKey { - fn from((store, key): (StoreType, Key)) -> Self { - match store { - StoreType::Base | StoreType::Account | StoreType::PoS => { - MerkleKey::Sha256(key, PhantomData) - } - StoreType::Ibc => MerkleKey::Raw(key), - } - } -} - -impl TryFrom> for SmtHash { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), - _ => Err(Error::InvalidMerkleKey( - "This key is for a sparse merkle tree".into(), - )), - } - } -} - -impl TryFrom> for StringKey { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Raw(key) => { - Self::try_from_bytes(key.to_string().as_bytes()) - } - _ => Err(Error::InvalidMerkleKey( - "This is not an key for the IBC subtree".into(), - )), - } - } -} - /// The root and store pairs to restore the trees #[derive(Default)] pub struct MerkleTreeStoresRead { @@ -614,6 +574,7 @@ pub struct MerkleTreeStoresRead { account: (Hash, SmtStore), ibc: (Hash, AmtStore), pos: (Hash, SmtStore), + bridge_pool: (KeccakHash, BTreeSetStore), } impl MerkleTreeStoresRead { @@ -624,6 +585,7 @@ impl MerkleTreeStoresRead { StoreType::Account => self.account.0 = root, StoreType::Ibc => self.ibc.0 = root, StoreType::PoS => self.pos.0 = root, + StoreType::BridgePool => self.bridge_pool.0 = root.into(), } } @@ -634,6 +596,7 @@ impl MerkleTreeStoresRead { Store::Account(store) => self.account.1 = store, Store::Ibc(store) => self.ibc.1 = store, Store::PoS(store) => self.pos.1 = store, + Store::BridgePool(store) => self.bridge_pool.1 = store, } } } @@ -644,6 +607,7 @@ pub struct MerkleTreeStoresWrite<'a> { account: (Hash, &'a SmtStore), ibc: (Hash, &'a AmtStore), pos: (Hash, &'a SmtStore), + bridge_pool: (Hash, &'a BTreeSetStore) } impl<'a> MerkleTreeStoresWrite<'a> { @@ -654,6 +618,7 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => &self.account.0, StoreType::Ibc => &self.ibc.0, StoreType::PoS => &self.pos.0, + StoreType::BridgePool => &self.bridge_pool.0 } } @@ -664,96 +629,11 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => StoreRef::Account(self.account.1), StoreType::Ibc => StoreRef::Ibc(self.ibc.1), StoreType::PoS => StoreRef::PoS(self.pos.1), + StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1) } } } -impl TreeKey for StringKey { - type Error = Error; - - fn as_slice(&self) -> &[u8] { - &self.original.as_slice()[..self.length] - } - - fn try_from_bytes(bytes: &[u8]) -> Result { - 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 { - return Err(Error::InvalidMerkleKey( - "Input IBC key is too large".into(), - )); - } - original[i] = *byte; - tree_key[i] = byte.wrapping_add(1); - length += 1; - } - Ok(Self { - original, - tree_key: tree_key.into(), - length, - }) - } -} - -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 - fn hash(value: impl AsRef<[u8]>) -> H256; -} - -/// The storage hasher used for the merkle tree. -#[derive(Default)] -pub struct Sha256Hasher(Sha256); - -impl Hasher for Sha256Hasher { - fn write_bytes(&mut self, h: &[u8]) { - self.0.update(h) - } - - 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 { - ics23::HashOp::Sha256 - } -} - -impl StorageHasher for Sha256Hasher { - fn hash(value: impl AsRef<[u8]>) -> H256 { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - let hash = hasher.finalize(); - let bytes: [u8; 32] = hash - .as_slice() - .try_into() - .expect("Sha256 output conversion to fixed array shouldn't fail"); - bytes.into() - } -} - -impl fmt::Debug for Sha256Hasher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Sha256Hasher") - } -} impl From for Error { fn from(error: StorageError) -> Self { diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1df873edd6..4def551e0f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -5,6 +5,7 @@ mod merkle_tree; pub mod mockdb; pub mod types; pub mod write_log; +pub mod traits; use core::fmt::Debug; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs new file mode 100644 index 0000000000..d5411cc966 --- /dev/null +++ b/shared/src/ledger/storage/traits.rs @@ -0,0 +1,214 @@ +//! Traits needed to provide a uniform interface over +//! all the different Merkle trees used for storage +use std::convert::{TryFrom, TryInto}; +use std::fmt; + +use arse_merkle_tree::{H256, Hash as SmtHash, Key as TreeKey}; +use arse_merkle_tree::traits::{Value, Hasher}; +use ibc_proto::ics23::CommitmentProof; +use sha2::{Digest, Sha256}; + +use super::IBC_KEY_LIMIT; +use super::merkle_tree::{Smt, Amt, Error}; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; +use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::hash::Hash; +use crate::types::storage::{ + MerkleKey, StringKey, TreeBytes, MerkleValue, Key +}; + +pub trait MerkleTree { + type Error; + + fn has_key(&self, key: &Key) -> Result; + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result; + fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; + fn membership_proof(&self, keys: &[Key], proof: Option) -> Option; +} + +impl MerkleTree for Smt { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + self.get(&H::hash(key.to_string()).into()) + .and(Ok(true)) + .map_error(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + let value = match value { + MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_ref()) + .map_err(|| Error::InvalidValue)?, + _ => return Err(Error::InvalidValue) + }; + self.update( + H::hash(key.to_string()).into(), + value, + ) + .map(Hash::into) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + let value = Hash::zero(); + self.update( + H::hash(key.to_string()).into(), + value + ) + .and(Ok(())) + .map_err(|err|Error::MerkleTree(err.to_string())) + } +} + +impl MerkleTree for Amt { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + self.get(&key) + .and(Ok(bool)) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: MerkleKey, value: MerkleValue) -> Result { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = match value { + MerkleValue::Bytes(bytes) => TreeBytes::from(bytes.as_ref().to_vec()), + _ => return Err(Error::InvalidValue) + }; + self.update( + key, + value, + ) + .map(Into::into) + .map_err(|err|Error::MerkleTree(err.to_string())) + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + let key = + StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = TreeBytes::zero(); + self.update( + key, + value + ) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + } +} + +impl MerkleTree for BridgePoolTree { + type Error = Error; + + fn has_key(&self, key: &Key) -> Result { + self.has_key(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + if let MerkleValue::Transfer(transfer) = value { + self.update(key, transfer) + .map_err( | err| Error::MerkleTree(err.to_string())) + } else { + Err(Error::InvalidValue) + } + } + + fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + self.delete(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } +} + +impl TreeKey for StringKey { + type Error = Error; + + fn as_slice(&self) -> &[u8] { + &self.original.as_slice()[..self.length] + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + 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 { + return Err(Error::InvalidMerkleKey( + "Input IBC key is too large".into(), + )); + } + original[i] = *byte; + tree_key[i] = byte.wrapping_add(1); + length += 1; + } + Ok(Self { + original, + tree_key: tree_key.into(), + length, + }) + } +} + +impl Value for TreeBytes { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + TreeBytes::zero() + } +} + +pub trait MembershipProof { }; + +impl MembershipProof for CommitmentProof; + +/// The storage hasher used for the merkle tree. +pub trait StorageHasher: Hasher + Default { + /// Hash the value to store + fn hash(value: impl AsRef<[u8]>) -> H256; +} + +/// The storage hasher used for the merkle tree. +#[derive(Default)] +pub struct Sha256Hasher(Sha256); + +impl Hasher for Sha256Hasher { + fn write_bytes(&mut self, h: &[u8]) { + self.0.update(h) + } + + 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 { + ics23::HashOp::Sha256 + } +} + +impl StorageHasher for Sha256Hasher { + fn hash(value: impl AsRef<[u8]>) -> H256 { + let mut hasher = Sha256::new(); + hasher.update(value.as_ref()); + let hash = hasher.finalize(); + let bytes: [u8; 32] = hash + .as_slice() + .try_into() + .expect("Sha256 output conversion to fixed array shouldn't fail"); + bytes.into() + } +} + +impl fmt::Debug for Sha256Hasher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Sha256Hasher") + } +} \ No newline at end of file diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 18c6e7f1a9..17ba1f57e7 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,9 +1,11 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use ethabi::token::Token; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, Uint}; +use crate::types::ethereum_events::{EthAddress, Uint, KeccakHash}; +use crate::types::keccak; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -17,6 +19,7 @@ use crate::types::token::Amount; Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct TransferToEthereum { /// The type of token @@ -40,6 +43,7 @@ pub struct TransferToEthereum { Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct PendingTransfer { /// The message to send to Ethereum to @@ -49,6 +53,25 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } +impl keccak::encode::Encode for PendingTransfer { + + fn tokenize(&self) -> Vec { + let from = Token::String(self.gas_fee.payer.to_string()); + let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); + let to = Token::Address(self.transfer.recipient.0.into()); + let amount = Token::Uint(u64::from(self.transfer.amount).into()); + let nonce = Token::Uint(self.transfer.nonce.into()); + vec![ + from, + fee, + to, + amount, + nonce, + ] + } +} + + /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. @@ -61,6 +84,7 @@ pub struct PendingTransfer { Eq, BorshSerialize, BorshDeserialize, + BorshSchema, )] pub struct GasFee { /// The amount of fees (in NAM) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e8ef5577cb..e4ac6e8c2c 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use arse_merkle_tree::traits::Value; use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex::FromHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -23,6 +24,8 @@ pub enum Error { Temporary { error: String }, #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError) } /// Result for functions that may fail @@ -86,6 +89,24 @@ impl TryFrom<&[u8]> for Hash { } } +impl TryFrom for Hash { + type Error = self::Error; + + fn try_from(string: String) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes) + } +} + +impl TryFrom<&str> for Hash { + type Error = self::Error; + + fn try_from(string: &str) -> HashResult { + let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + Self::try_from(&bytes) + } +} + impl From for transaction::Hash { fn from(hash: Hash) -> Self { Self::new(hash.0) @@ -144,4 +165,4 @@ impl Value for Hash { fn zero() -> Self { Hash([0u8; 32]) } -} +} \ No newline at end of file diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b7e2005bb7..7b7ffad7a2 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -11,12 +11,15 @@ use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use variant_access_derive::*; +use variant_access_traits::*; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; +use crate::types::eth_bridge_pool::{TransferToEthereum, PendingTransfer}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -31,6 +34,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Could not parse string: '{0}' into requested type: {1}")] + ParseError((String, String)) } /// Result for functions that may fail @@ -233,14 +238,35 @@ 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), +/// An enum representing the different types of values +/// that can be passed into Anoma's storage. +/// +/// This is a multi-store organized as +/// several Merkle trees, each of which is +/// responsible for understanding how to parse +/// this value. +pub enum MerkleValue> { + /// raw bytes + Bytes(T), + /// a transfer to be put in the Ethereum bridge pool + /// We actually only need the key (which is the hash + /// of the transfer). So this variant contains no data. + Transfer(PendingTransfer), +} + +impl From for MerkleValue +where + T: AsRef<[u8]> +{ + fn from(bytes: T) -> Self { + Self::Bytes(bytes) + } +} + +impl> From for MerkleValue { + fn from(transfer: PendingTransfer) -> Self { + Self::Transfer(transfer) + } } /// Storage keys that are utf8 encoded strings @@ -581,6 +607,20 @@ impl KeySeg for Address { } } +impl KeySeg for Hash { + fn parse(seg: String) -> Result { + seg.try_into().map_error(Error::ParseError((seg, "Hash".into()))) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, @@ -630,6 +670,7 @@ impl Add for Epoch { } } + impl Sub for Epoch { type Output = Epoch; From 2970208625346f42a6e0e92cb96be9cdaa47c3e4 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 26 Sep 2022 17:51:53 +0200 Subject: [PATCH 0745/1995] [feat]: Added membership proofs for eth bridge pool --- Cargo.lock | 331 +++--------------- .../ledger/eth_bridge/storage/bridge_pool.rs | 37 +- shared/src/types/keccak.rs | 19 +- 3 files changed, 78 insertions(+), 309 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index adbc38f162..17d28aa12b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,28 +1131,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "chrono-tz" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - [[package]] name = "chunked_transfer" version = "1.4.0" @@ -1729,12 +1707,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - [[package]] name = "diff" version = "0.1.12" @@ -2534,17 +2506,6 @@ dependencies = [ "regex", ] -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags", - "ignore", - "walkdir", -] - [[package]] name = "gloo-timers" version = "0.2.4" @@ -2800,12 +2761,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humansize" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" - [[package]] name = "hyper" version = "0.10.16" @@ -2913,14 +2868,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +version = "0.12.0" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", - "ics23", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "ics23 0.6.7", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2940,14 +2895,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.12.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ics23", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ics23 0.6.7", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2967,28 +2922,44 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +version = "0.16.0" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" dependencies = [ - "base64 0.13.0", "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=bat/abciplus)", + "tonic", ] [[package]] name = "ibc-proto" -version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +version = "0.16.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "base64 0.13.0", "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?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tonic", +] + +[[package]] +name = "ics23" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" +dependencies = [ + "anyhow", + "bytes 1.1.0", + "hex", + "prost 0.9.0", + "ripemd160", + "sha2 0.9.9", + "sha3 0.9.1", + "sp-std", ] [[package]] @@ -3072,24 +3043,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "ignore" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" -dependencies = [ - "crossbeam-utils 0.8.8", - "globset", - "lazy_static 1.4.0", - "log 0.4.17", - "memchr", - "regex", - "same-file", - "thread_local 1.1.4", - "walkdir", - "winapi-util", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -4016,12 +3969,6 @@ dependencies = [ "serde_yaml", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "match_cfg" version = "0.1.0" @@ -4356,11 +4303,11 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ics23", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", + "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=bat/abciplus)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ics23 0.6.7", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -4390,8 +4337,6 @@ dependencies = [ "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", - "variant_access_derive", - "variant_access_traits", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4461,7 +4406,6 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "semver 1.0.14", "serde 1.0.137", "serde_bytes", "serde_json", @@ -5146,15 +5090,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - [[package]] name = "paste" version = "1.0.7" @@ -5221,40 +5156,6 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1 0.8.2", -] - [[package]] name = "petgraph" version = "0.5.1" @@ -5275,45 +5176,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "phf" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_shared" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" -dependencies = [ - "siphasher", - "uncased", -] - [[package]] name = "pin-project" version = "0.4.29" @@ -6241,7 +6103,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.14", + "semver 1.0.10", ] [[package]] @@ -6474,9 +6336,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" [[package]] name = "semver-parser" @@ -6764,27 +6626,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - [[package]] name = "smallvec" version = "0.6.14" @@ -6869,7 +6716,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23", + "ics23 0.7.0", "sha2 0.9.9", ] @@ -7290,28 +7137,6 @@ dependencies = [ "time 0.3.9", ] -[[package]] -name = "tera" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static 1.4.0", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", - "serde 1.0.137", - "serde_json", - "slug", - "unic-segment", -] - [[package]] name = "term_size" version = "0.3.2" @@ -8136,65 +7961,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "version_check 0.9.4", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "1.4.2" @@ -8355,25 +8121,6 @@ dependencies = [ "version_check 0.9.4", ] -[[package]] -name = "variant_access_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd235ffafb854ed81b49217ce411e850a39628a5d26740ecfb60421c873d834" -dependencies = [ - "lazy_static 1.4.0", - "quote", - "syn", - "tera", - "variant_access_traits", -] - -[[package]] -name = "variant_access_traits" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d75c5a83bb8912dd9c628adf954c9f9bff74a4e170d2c90242f4e56a0d643e" - [[package]] name = "vcpkg" version = "0.2.15" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 8588ae0550..e15de60cb2 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -9,7 +9,8 @@ use eyre::eyre; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; -use crate::types::ethereum_events::KeccakHash; +use crate::types::keccak::{keccak_hash, KeccakHash}; +use crate::types::keccak::encode::Encode; use crate::types::hash::{Hash, keccak_hash}; use crate::types::storage::{DbKeySeg, Key}; use crate::ledger::storage::{Sha256Hasher, StorageHasher}; @@ -66,10 +67,9 @@ pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, /// The underlying storage - store: BTreeMap + store: BTreeMap, } -/// TODO: Hash ABI instead of Borsh impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool pub fn new(root: KeccakHash, store: BTreeMap) -> Self { @@ -90,7 +90,7 @@ impl BridgePoolTree { /// return an error if the key is malformed. pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { let hash = Self::parse_key(key)?; - if hash != keccak_hash(&value.try_to_vec().unwrap()) { + if hash != value.keccak256() { return eyre!("Key does not match hash of the value")?; } _ = self.store.insert(hash, value); @@ -131,9 +131,28 @@ impl BridgePoolTree { &self.store } + /// Create a batched membership proof for the provided keys pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { - for (key, value) in self.store { - + let mut leaves : std::collections::BTreeSet = Default::default(); + for key in keys { + leaves.insert(Self::parse_key(key)?); + } + let mut proof_leaves = vec![]; + let mut proof_hashes = vec![]; + let mut flags = vec![]; + for (hash, value) in self.store { + if leaves.contains(&hash) { + flags.push(true); + proof_leaves.push(value); + } else { + flags.push(false); + proof_hashes.push(hash); + } + } + BridgePoolProof { + proof: proof_hashes, + leaves: proof_leaves, + flags } } @@ -180,8 +199,7 @@ impl BridgePoolProof { let mut proof_pos = 0usize; let mut computed; if self.flags[0] { - leaf = self.leaves[leaf_pos].clone(); - computed = keccak_hash(leaf.try_to_vec().unwrap()); + computed = self.leaves[leaf_pos].keccak256(); leaf_pos += 1; } else { computed = self.proof[proof_pos].clone(); @@ -190,8 +208,7 @@ impl BridgePoolProof { for flag in 1..self.flages.len() { let mut next_hash; if self.flags[flag] { - leaf = self.leaves[leaf_pos].clone(); - next_hash = keccak_hash(leaf.try_to_vec().unwrap()); + next_hash = self.leaves[leaf_pos].keccak256(); leaf_pos += 1; } else { next_hash = self.proof[proof_pos].clone(); diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 8d43ecf9c6..06beb3adf1 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -87,6 +87,17 @@ impl TryFrom<&str> for KeccakHash { } } +/// Hash bytes using Keccak +pub fn keccak_hash(bytes: &[u8]) -> KeccakHash { + let mut output = [0; 32]; + + let mut hasher = Keccak::v256(); + hasher.update(bytes); + hasher.finalize(&mut output); + + KeccakHash(output) +} + /// This module defines encoding methods compatible with Ethereum /// smart contracts. pub mod encode { @@ -111,13 +122,7 @@ pub mod encode { /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. fn keccak256(&self) -> KeccakHash { - let mut output = [0; 32]; - - let mut state = Keccak::v256(); - state.update(self.encode().as_slice()); - state.finalize(&mut output); - - KeccakHash(output) + keccak_hash(self.encode().as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the From 9700e1a1873a29a3c81e8cece7c96aa7a0b06ef4 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 29 Sep 2022 10:20:30 +0200 Subject: [PATCH 0746/1995] feat: finished base implementation, need to debug --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 3 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 78 +++--- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- shared/src/ledger/storage/merkle_tree.rs | 228 ++++++------------ shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 180 ++++++++++---- shared/src/types/eth_bridge_pool.rs | 14 +- shared/src/types/hash.rs | 10 +- shared/src/types/storage.rs | 38 ++- 9 files changed, 290 insertions(+), 266 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b05b4db7be..12fa7982eb 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -18,7 +18,8 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::traits::StorageHasher; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index e15de60cb2..48266bde17 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -1,19 +1,19 @@ //! Tools for accessing the storage subspaces of the Ethereum //! bridge pool -use std::collections::BTreeMap; +use std::collections::BTreeSet; use std::convert::TryInto; use std::ops::Deref; -use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; +use crate::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; -use crate::types::keccak::{keccak_hash, KeccakHash}; +use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; -use crate::types::hash::{Hash, keccak_hash}; +use crate::types::keccak::{keccak_hash, KeccakHash}; use crate::types::storage::{DbKeySeg, Key}; -use crate::ledger::storage::{Sha256Hasher, StorageHasher}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -67,13 +67,13 @@ pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, /// The underlying storage - store: BTreeMap, + store: BTreeSet, } impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool - pub fn new(root: KeccakHash, store: BTreeMap) -> Self { - Self{ root, store } + pub fn new(root: KeccakHash, store: BTreeSet) -> Self { + Self { root, store } } /// Parse the key to ensure it is of the correct type. @@ -81,25 +81,22 @@ impl BridgePoolTree { /// If it is, it can be converted to a hash. /// Checks if the hash is in the tree. pub fn has_key(&self, key: &Key) -> Result { - Ok(self.store.contains_key(&Self::parse_key(key)?)) + Ok(self.store.contains(&Self::parse_key(key)?)) } /// Update the tree with a new value. /// /// Returns the new root if successful. Will /// return an error if the key is malformed. - pub fn update(&mut self, key: &Key, value: PendingTransfer) -> Result { + pub fn update(&mut self, key: &Key) -> Result { let hash = Self::parse_key(key)?; - if hash != value.keccak256() { - return eyre!("Key does not match hash of the value")?; - } - _ = self.store.insert(hash, value); + _ = self.store.insert(hash); self.root = self.compute_root(); Ok(self.root()) } /// Delete a key from storage and update the root - pub fn delete(&mut self, key: &Key) -> Result<(), Error>{ + pub fn delete(&mut self, key: &Key) -> Result<(), Error> { let hash = Self::parse_key(key)?; _ = self.store.remove(&hash); self.root = self.compute_root(); @@ -109,13 +106,12 @@ impl BridgePoolTree { /// Compute the root of the merkle tree pub fn compute_root(&self) -> KeccakHash { let mut leaves = self.store.iter(); - let mut root = if let Some((hash, _)) = leaves.next() { + let mut root = if let Some(hash) = leaves.next() { hash.clone() } else { return Default::default(); }; - - for (leaf, _) in leaves { + for leaf in leaves { root = keccak_hash([root.0, leaf.0].concat()); } root @@ -127,33 +123,43 @@ impl BridgePoolTree { } /// Get a reference to the backing store - pub fn store(&self) -> &BTreeMap { + pub fn store(&self) -> &BTreeSet { &self.store } /// Create a batched membership proof for the provided keys - pub fn membership_proof(&self, keys: &[Key]) -> BridgePoolProof { - let mut leaves : std::collections::BTreeSet = Default::default(); + pub fn membership_proof( + &self, + keys: &[Key], + mut values: Vec, + ) -> Result { + if values.len() != keys.len() { + return eyre!( + "The number of leaves and leaf hashes must be equal." + )?; + } + values.sort(); + let mut leaves: std::collections::BTreeSet = + Default::default(); for key in keys { leaves.insert(Self::parse_key(key)?); } - let mut proof_leaves = vec![]; + let mut proof_hashes = vec![]; let mut flags = vec![]; - for (hash, value) in self.store { + for hash in self.store { if leaves.contains(&hash) { flags.push(true); - proof_leaves.push(value); } else { flags.push(false); proof_hashes.push(hash); } } - BridgePoolProof { + Ok(BridgePoolProof { proof: proof_hashes, - leaves: proof_leaves, - flags - } + leaves: values, + flags, + }) } /// Parse a db key to see if it is valid for the @@ -164,13 +170,18 @@ impl BridgePoolTree { fn parse_key(key: &Key) -> Result { if key.segments.len() == 1 { match &key.segments[0] { - DbKeySeg::StringSeg(str) => str.as_str().try_into().ok_or( - eyre!("Could not parse key segment as a hash")? - ), - _ => eyre!("Bridge pool keys should be strings, not addresses")? + DbKeySeg::StringSeg(str) => str + .as_str() + .try_into() + .ok_or(eyre!("Could not parse key segment as a hash")?), + _ => { + eyre!("Bridge pool keys should be strings, not addresses")? + } } } else { - eyre!("Key for the bridge pool should not have more than one segment")? + eyre!( + "Key for the bridge pool should not have more than one segment" + )? } } } @@ -186,7 +197,6 @@ pub struct BridgePoolProof { } impl BridgePoolProof { - /// Verify a membership proof matches the provided root pub fn verify(&self, root: KeccakHash) -> bool { if self.proof.len() + self.leaves.len() != self.flags.len() { diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index cdcd1815ea..0e62f7270f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::StorageHasher; +use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::Amount; diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index b2598aa210..58b05faa2f 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -2,6 +2,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::marker::PhantomData; +use std::slice::from_ref; use std::str::FromStr; use arse_merkle_tree::default_store::DefaultStore; @@ -19,19 +20,22 @@ use ics23::{ use itertools::{Either, Itertools}; use prost::Message; use sha2::{Digest, Sha256}; +use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; +use super::traits::{self, Sha256Hasher, StorageHasher}; use super::IBC_KEY_LIMIT; -use super::traits::{self, StorageHasher, Sha256Hasher}; use crate::bytes::ByteBuf; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; -use crate::ledger::storage::types; -use crate::tendermint::merkle::proof::{Proof, ProofOp}; +use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; use crate::types::ethereum_events::KeccakHash; use crate::types::hash::Hash; -use crate::types::storage::{DbKeySeg, Error as StorageError, Key, StringKey, TreeBytes, MerkleValue}; +use crate::types::storage::{ + DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, + StringKey, TreeBytes, +}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -50,6 +54,10 @@ pub enum Error { NonExistenceProof(String), #[error("Invalid value given to sub-tree storage")] InvalidValue, + #[error("ICS23 commitment proofs do not support multiple leaves")] + Ics23MultiLeaf, + #[error("A Tendermint proof can only be constructed from an ICS23 proof.")] + TendermintProof, } /// Result for functions that may fail @@ -58,7 +66,7 @@ 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 BridgePoolStore = std::collections::BTreeMap; +pub type BridgePoolStore = std::collections::BTreeSet; pub type Smt = ArseMerkleTree; pub type Amt = ArseMerkleTree; @@ -99,7 +107,7 @@ pub enum Store { /// For PoS-related data PoS(SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(BTreeSetStore) + BridgePool(BTreeSetStore), } impl Store { @@ -125,7 +133,7 @@ pub enum StoreRef<'a> { /// For PoS-related data PoS(&'a SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(&'a BTreeSetStore) + BridgePool(&'a BTreeSetStore), } impl<'a> StoreRef<'a> { @@ -268,7 +276,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, @@ -288,7 +297,10 @@ impl MerkleTree { } } - fn tree_mut(&mut self, store_type: &StoreType) -> &mut impl traits::MerkleTree { + fn tree_mut( + &mut self, + store_type: &StoreType, + ) -> &mut impl traits::MerkleTree { match store_type { StoreType::Base => &mut self.base, StoreType::Account => &mut self.account, @@ -320,7 +332,11 @@ impl MerkleTree { } /// Update the tree with the given key and value - pub fn update>(&mut self, key: &Key, value: impl Into>) -> Result<()> { + pub fn update>( + &mut self, + key: &Key, + value: impl Into>, + ) -> Result<()> { let (store_type, sub_key) = StoreType::sub_key(key)?; self.update_tree(&store_type, sub_key.into(), value.into()) } @@ -343,94 +359,70 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(). self.) + bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), } } - /// Get the existence proof - pub fn get_existence_proof( + /// Get the existence proof from a sub-tree + pub fn get_sub_tree_existence_proof>( &self, - key: &Key, - value: Vec, - ) -> Result { - let (store_type, sub_key) = StoreType::sub_key(key)?; - 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!(), - } + keys: &[Key], + values: Vec>, + ) -> Result { + let first_key = keys.iter().next().ok_or(Error::InvalidMerkleKey( + "No keys provided for existence proof.".into(), + ))?; + let (store_type, _) = StoreType::sub_key(first_key)?; + if !keys.iter().all(|k| { + if let Ok((s, _)) = StoreType::sub_key(k) { + s == store_type + } else { + false } - Either::Right(amt) => { - 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 - match cp.proof.expect("The proof should exist") { - Ics23Proof::Exist(ep) => CommitmentProof { - proof: Some(Ics23Proof::Exist(ExistenceProof { - leaf: Some(self.ibc_leaf_spec()), - ..ep - })), - }, - _ => unreachable!(), - } - } - }; - self.get_proof(key, sub_proof) + }) { + return Err(Error::InvalidMerkleKey( + "Cannot construct inclusion proof for keys in separate \ + sub-trees." + .into(), + )); + } + self.tree(&store_type).membership_proof(keys, values) } /// 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 sub_proof = match self.tree(&store_type) { - Either::Left(_) => { - return Err(Error::NonExistenceProof(store_type.to_string())); - } - Either::Right(amt) => { - let key = - StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; - let mut nep = amt.non_membership_proof(&key)?; - // 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(self.ibc_leaf_spec()); - } - _ => unreachable!(), - } + if store_type != StoreType::Ibc { + return Err(Error::NonExistenceProof(store_type.to_string())); + } + + let key = StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = self.ibc.non_membership_proof(&key)?; + // 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(self.ibc_leaf_spec()); } - nep + _ => unreachable!(), } - }; + } + // Get a proof of the sub tree self.get_proof(key, sub_proof) } /// Get the Tendermint proof with the base proof - fn get_proof( + pub fn get_tendermint_proof>( &self, - key: &Key, sub_proof: CommitmentProof, ) -> Result { let mut data = vec![]; @@ -453,7 +445,7 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: base_key.as_bytes().to_vec(), - leaf: Some(self.base_leaf_spec()), + leaf: Some(ics23_specs::base_leaf_spec::()), ..ep })), }, @@ -476,73 +468,6 @@ impl MerkleTree { ops: vec![sub_proof_op, base_proof_op], }) } - - /// Get the proof specs - pub fn 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.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 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: H::hash_op().into(), - prehash_value: HashOp::NoHash.into(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } - - /// Get the leaf spec for the subtree. Non-hashed values are used for the - /// 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(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } } /// The root hash of the merkle tree as bytes @@ -607,7 +532,7 @@ pub struct MerkleTreeStoresWrite<'a> { account: (Hash, &'a SmtStore), ibc: (Hash, &'a AmtStore), pos: (Hash, &'a SmtStore), - bridge_pool: (Hash, &'a BTreeSetStore) + bridge_pool: (Hash, &'a BTreeSetStore), } impl<'a> MerkleTreeStoresWrite<'a> { @@ -618,7 +543,7 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => &self.account.0, StoreType::Ibc => &self.ibc.0, StoreType::PoS => &self.pos.0, - StoreType::BridgePool => &self.bridge_pool.0 + StoreType::BridgePool => &self.bridge_pool.0, } } @@ -629,12 +554,11 @@ impl<'a> MerkleTreeStoresWrite<'a> { StoreType::Account => StoreRef::Account(self.account.1), StoreType::Ibc => StoreRef::Ibc(self.ibc.1), StoreType::PoS => StoreRef::PoS(self.pos.1), - StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1) + StoreType::BridgePool => StoreRef::BridgePool(self.bridge_pool.1), } } } - impl From for Error { fn from(error: StorageError) -> Self { Error::InvalidKey(error) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 4def551e0f..74826fba3c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1,11 +1,12 @@ //! Ledger's state storage with key-value backed store and a merkle tree +mod ics23_specs; mod merkle_tree; #[cfg(any(test, feature = "testing"))] pub mod mockdb; +pub mod traits; pub mod types; pub mod write_log; -pub mod traits; use core::fmt::Debug; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index d5411cc966..c558c7cbe9 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -3,27 +3,36 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; -use arse_merkle_tree::{H256, Hash as SmtHash, Key as TreeKey}; -use arse_merkle_tree::traits::{Value, Hasher}; -use ibc_proto::ics23::CommitmentProof; +use arse_merkle_tree::traits::{Hasher, Value}; +use arse_merkle_tree::{Hash as SmtHash, Key as TreeKey, H256}; +use ics23::commitment_proof::Proof as Ics23Proof; +use ics23::{CommitmentProof, ExistenceProof}; use sha2::{Digest, Sha256}; -use super::IBC_KEY_LIMIT; -use super::merkle_tree::{Smt, Amt, Error}; +use super::merkle_tree::{Amt, Error, Smt}; +use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::storage::{ - MerkleKey, StringKey, TreeBytes, MerkleValue, Key + Key, MembershipProof, MerkleValue, StringKey, TreeBytes, }; pub trait MerkleTree { type Error; fn has_key(&self, key: &Key) -> Result; - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result; + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result; fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; - fn membership_proof(&self, keys: &[Key], proof: Option) -> Option; + fn membership_proof>( + &self, + keys: &[Key], + values: Vec>, + ) -> Result; } impl MerkleTree for Smt { @@ -35,28 +44,56 @@ impl MerkleTree for Smt { .map_error(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { let value = match value { MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_ref()) .map_err(|| Error::InvalidValue)?, - _ => return Err(Error::InvalidValue) + _ => return Err(Error::InvalidValue), }; - self.update( - H::hash(key.to_string()).into(), - value, - ) - .map(Hash::into) - .map_err(|err| Error::MerkleTree(err.to_string())) + self.update(H::hash(key.to_string()).into(), value) + .map(Hash::into) + .map_err(|err| Error::MerkleTree(err.to_string())) } fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { let value = Hash::zero(); - self.update( - H::hash(key.to_string()).into(), - value - ) - .and(Ok(())) - .map_err(|err|Error::MerkleTree(err.to_string())) + self.update(H::hash(key.to_string()).into(), value) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn membership_proof>( + &self, + keys: &[Key], + mut values: Vec>, + ) -> Result { + if keys.len() != 1 || values.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + let key: &Key = &keys[0]; + let value = match values.remove(0) { + MerkleValue::Bytes(b) => b.as_ref().to_vec(), + _ => return Err(Error::InvalidValue), + }; + let cp = self.membership_proof(&H::hash(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) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + key: key.to_string().as_bytes().to_vec(), + value, + leaf: Some(ics23_specs::leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } } } @@ -64,38 +101,60 @@ impl MerkleTree for Amt { type Error = Error; fn has_key(&self, key: &Key) -> Result { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; self.get(&key) .and(Ok(bool)) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: MerkleKey, value: MerkleValue) -> Result { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + fn update>( + &mut self, + key: MerkleKey, + value: MerkleValue, + ) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = match value { - MerkleValue::Bytes(bytes) => TreeBytes::from(bytes.as_ref().to_vec()), - _ => return Err(Error::InvalidValue) + MerkleValue::Bytes(bytes) => { + TreeBytes::from(bytes.as_ref().to_vec()) + } + _ => return Err(Error::InvalidValue), }; - self.update( - key, - value, - ) - .map(Into::into) - .map_err(|err|Error::MerkleTree(err.to_string())) + self.update(key, value) + .map(Into::into) + .map_err(|err| Error::MerkleTree(err.to_string())) } fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - let key = - StringKey::try_from_bytes(key.to_string().as_bytes())?; + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = TreeBytes::zero(); - self.update( - key, - value - ) - .and(Ok(())) - .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + self.update(key, value) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + } + + fn membership_proof>( + &self, + keys: &[Key], + _: Vec>, + ) -> Result { + if keys.len() != 1 || values.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + + let key = StringKey::try_from_bytes(&keys[0].to_string().as_bytes())?; + let cp = self.membership_proof(&key)?; + // Replace the values and the leaf op for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + leaf: Some(ics23_specs::ibc_leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } } } @@ -107,10 +166,14 @@ impl MerkleTree for BridgePoolTree { .map_err(|err| Error::MerkleTree(err.to_string())) } - fn update>(&mut self, key: &Key, value: MerkleValue) -> Result { - if let MerkleValue::Transfer(transfer) = value { - self.update(key, transfer) - .map_err( | err| Error::MerkleTree(err.to_string())) + fn update>( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + if let MerkleValue::Transfer(_) = value { + self.update(key) + .map_err(|err| Error::MerkleTree(err.to_string())) } else { Err(Error::InvalidValue) } @@ -120,6 +183,23 @@ impl MerkleTree for BridgePoolTree { self.delete(key) .map_err(|err| Error::MerkleTree(err.to_string())) } + + fn membership_proof>( + &self, + keys: &[Key], + values: Vec>, + ) -> Result { + let values = values + .into_iter() + .filter_map(|val| match val { + MerkleValue::Transfer(transfer) => Some(transfer), + _ => None, + }) + .collect(); + self.membership_proof(keys, values) + .map(Into::into) + .map_err(|err| Error::MerkleTree(err.to_string())) + } } impl TreeKey for StringKey { @@ -161,10 +241,6 @@ impl Value for TreeBytes { } } -pub trait MembershipProof { }; - -impl MembershipProof for CommitmentProof; - /// The storage hasher used for the merkle tree. pub trait StorageHasher: Hasher + Default { /// Hash the value to store @@ -211,4 +287,4 @@ impl fmt::Debug for Sha256Hasher { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sha256Hasher") } -} \ No newline at end of file +} diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 17ba1f57e7..19c0f805cd 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,10 +1,10 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool -use borsh::{BorshDeserialize, BorshSerialize, BorshSchema}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, Uint, KeccakHash}; +use crate::types::ethereum_events::{EthAddress, KeccakHash, Uint}; use crate::types::keccak; use crate::types::token::Amount; @@ -54,24 +54,16 @@ pub struct PendingTransfer { } impl keccak::encode::Encode for PendingTransfer { - fn tokenize(&self) -> Vec { let from = Token::String(self.gas_fee.payer.to_string()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.into()); - vec![ - from, - fee, - to, - amount, - nonce, - ] + vec![from, fee, to, amount, nonce] } } - /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e4ac6e8c2c..f2a84f19eb 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -25,7 +25,7 @@ pub enum Error { #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), #[error("Failed to convert string into a hash: {0}")] - FromStringError(hex::FromHexError) + FromStringError(hex::FromHexError), } /// Result for functions that may fail @@ -93,7 +93,8 @@ impl TryFrom for Hash { type Error = self::Error; fn try_from(string: String) -> HashResult { - let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; Self::try_from(&bytes) } } @@ -102,7 +103,8 @@ impl TryFrom<&str> for Hash { type Error = self::Error; fn try_from(string: &str) -> HashResult { - let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; + let bytes: Vec = + Vec::from_hex(string).map_err(Error::FromStringError)?; Self::try_from(&bytes) } } @@ -165,4 +167,4 @@ impl Value for Hash { fn zero() -> Self { Hash([0u8; 32]) } -} \ No newline at end of file +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 7b7ffad7a2..e13aa14dc5 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -9,17 +9,17 @@ use std::str::FromStr; use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ics23::CommitmentProof; use serde::{Deserialize, Serialize}; use thiserror::Error; -use variant_access_derive::*; -use variant_access_traits::*; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; -use crate::types::eth_bridge_pool::{TransferToEthereum, PendingTransfer}; +use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -35,7 +35,7 @@ pub enum Error { #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), #[error("Could not parse string: '{0}' into requested type: {1}")] - ParseError((String, String)) + ParseError(String, String), } /// Result for functions that may fail @@ -248,15 +248,13 @@ impl FromStr for Key { pub enum MerkleValue> { /// raw bytes Bytes(T), - /// a transfer to be put in the Ethereum bridge pool - /// We actually only need the key (which is the hash - /// of the transfer). So this variant contains no data. + /// A transfer to be put in the Ethereum bridge pool. Transfer(PendingTransfer), } impl From for MerkleValue where - T: AsRef<[u8]> + T: AsRef<[u8]>, { fn from(bytes: T) -> Self { Self::Bytes(bytes) @@ -348,6 +346,26 @@ impl From for Vec { } } +/// Type of membership proof from a merkle tree +pub enum MembershipProof { + /// ICS23 compliant membership proof + ICS23(CommitmentProof), + /// Bespoke membership proof for the Ethereum bridge pool + BridgePool(BridgePoolProof), +} + +impl From for MembershipProof { + fn from(proof: CommitmenProof) -> Self { + Self::ICS23(proof) + } +} + +impl From for MembershipProof { + fn from(proof: BridgePoolProof) -> Self { + Self::BridgePool(proof) + } +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { @@ -609,7 +627,8 @@ impl KeySeg for Address { impl KeySeg for Hash { fn parse(seg: String) -> Result { - seg.try_into().map_error(Error::ParseError((seg, "Hash".into()))) + seg.try_into() + .map_error(Error::ParseError((seg, "Hash".into()))) } fn raw(&self) -> String { @@ -670,7 +689,6 @@ impl Add for Epoch { } } - impl Sub for Epoch { type Output = Epoch; From 5b6e744600610be5bdc0681793373b26e33e3e6a Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 29 Sep 2022 10:27:36 +0200 Subject: [PATCH 0747/1995] [chore]: Rebase on eth-bridge-intregration --- Cargo.lock | 331 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 292 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17d28aa12b..adbc38f162 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1131,6 +1131,28 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "chrono-tz" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "chunked_transfer" version = "1.4.0" @@ -1707,6 +1729,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + [[package]] name = "diff" version = "0.1.12" @@ -2506,6 +2534,17 @@ dependencies = [ "regex", ] +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "gloo-timers" version = "0.2.4" @@ -2761,6 +2800,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + [[package]] name = "hyper" version = "0.10.16" @@ -2868,14 +2913,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", - "ics23 0.6.7", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2895,14 +2940,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ics23 0.6.7", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2922,44 +2967,28 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ + "base64 0.13.0", "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=bat/abciplus)", - "tonic", ] [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ + "base64 0.13.0", "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?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tonic", -] - -[[package]] -name = "ics23" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" -dependencies = [ - "anyhow", - "bytes 1.1.0", - "hex", - "prost 0.9.0", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", ] [[package]] @@ -3043,6 +3072,24 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils 0.8.8", + "globset", + "lazy_static 1.4.0", + "log 0.4.17", + "memchr", + "regex", + "same-file", + "thread_local 1.1.4", + "walkdir", + "winapi-util", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -3969,6 +4016,12 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4303,11 +4356,11 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", - "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=bat/abciplus)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ics23 0.6.7", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -4337,6 +4390,8 @@ dependencies = [ "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", + "variant_access_derive", + "variant_access_traits", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4406,6 +4461,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "semver 1.0.14", "serde 1.0.137", "serde_bytes", "serde_json", @@ -5090,6 +5146,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.7" @@ -5156,6 +5221,40 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pest_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +dependencies = [ + "maplit", + "pest", + "sha-1 0.8.2", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -5176,6 +5275,45 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "0.4.29" @@ -6103,7 +6241,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.10", + "semver 1.0.14", ] [[package]] @@ -6336,9 +6474,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" @@ -6626,12 +6764,27 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "0.6.14" @@ -6716,7 +6869,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] @@ -7137,6 +7290,28 @@ dependencies = [ "time 0.3.9", ] +[[package]] +name = "tera" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static 1.4.0", + "percent-encoding 2.1.0", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde 1.0.137", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "term_size" version = "0.3.2" @@ -7961,6 +8136,65 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check 0.9.4", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "1.4.2" @@ -8121,6 +8355,25 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "variant_access_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd235ffafb854ed81b49217ce411e850a39628a5d26740ecfb60421c873d834" +dependencies = [ + "lazy_static 1.4.0", + "quote", + "syn", + "tera", + "variant_access_traits", +] + +[[package]] +name = "variant_access_traits" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d75c5a83bb8912dd9c628adf954c9f9bff74a4e170d2c90242f4e56a0d643e" + [[package]] name = "vcpkg" version = "0.2.15" From 5c3855c15540b7e2e94bb2d42aecdd30cf8d1908 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 29 Sep 2022 16:18:29 +0100 Subject: [PATCH 0748/1995] Update Cargo.lock --- Cargo.lock | 16 ++++++++-------- wasm_for_tests/wasm_source/Cargo.lock | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eef80f5eb3..3c85af78a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2897,7 +2897,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" dependencies = [ "bytes 1.2.1", "derive_more", @@ -2951,7 +2951,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" dependencies = [ "bytes 1.2.1", "prost 0.9.0", @@ -6949,7 +6949,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "async-trait", "bytes 1.2.1", @@ -7005,7 +7005,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "flex-error", "serde 1.0.144", @@ -7031,7 +7031,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "derive_more", "flex-error", @@ -7057,7 +7057,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "bytes 1.2.1", "flex-error", @@ -7091,7 +7091,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "async-trait", "async-tungstenite", @@ -7157,7 +7157,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#8e8e3d8e4903349bd539c123946d074525b52981" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1aeb3185e4..3128090166 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1163,7 +1163,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes", "derive_more", @@ -1190,7 +1190,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" dependencies = [ "bytes", "prost", @@ -2679,7 +2679,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "async-trait", "bytes", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "flex-error", "serde", @@ -2720,7 +2720,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "derive_more", "flex-error", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "bytes", "flex-error", @@ -2750,7 +2750,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "bytes", "flex-error", @@ -2774,7 +2774,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" dependencies = [ "ed25519-dalek", "gumdrop", From c4070153effcfc86bf7fc8d13e6b3f7911912f67 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 09:46:10 +0100 Subject: [PATCH 0749/1995] Improve logging of namadac in e2e tests --- tests/src/e2e/setup.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..0434d049a4 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -652,10 +652,10 @@ where S: AsRef, { // Root cargo workspace manifest path - let bin_name = match bin { - Bin::Node => "namadan", - Bin::Client => "namadac", - Bin::Wallet => "namadaw", + let (bin_name, log_level) = match bin { + Bin::Node => ("namadan", "info"), + Bin::Client => ("namadac", "tendermint_rpc=debug"), + Bin::Wallet => ("namadaw", "info"), }; let mut run_cmd = generate_bin_command( @@ -664,7 +664,7 @@ where ); run_cmd - .env("ANOMA_LOG", "info") + .env("ANOMA_LOG", log_level) .env("TM_LOG_LEVEL", "info") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) From 7b04badd7ef4c30d06b91bb4a6775f2ad3a56073 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 09:47:01 +0100 Subject: [PATCH 0750/1995] Re-enable ledger_txs_and_queries() e2e test --- tests/src/e2e/ledger_tests.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f196061f99..824fef4707 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -243,7 +243,6 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 6. Query token balance /// 7. Query the raw bytes of a storage key #[test] -#[ignore] // TODO(namada#418): re-enable once working again fn ledger_txs_and_queries() -> Result<()> { use namada_apps::config::Config; @@ -257,9 +256,11 @@ fn ledger_txs_and_queries() -> Result<()> { }; let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); - let validator_0_config = update_config( - Config::load(&validator_0_base_dir, &test.net.chain_id, None), - ); + let validator_0_config = update_config(Config::load( + &validator_0_base_dir, + &test.net.chain_id, + None, + )); validator_0_config .write(&validator_0_base_dir, &test.net.chain_id, true) .unwrap(); From 9e4044b3022607b2d2e2843d948786806c5c7eea Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 09:47:12 +0100 Subject: [PATCH 0751/1995] Update Cargo.lock --- Cargo.lock | 525 +++++++++++++++++++++++++++-------------------------- 1 file changed, 271 insertions(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c85af78a0..5bd12f1ebe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ dependencies = [ "memchr", "pin-project-lite 0.2.9", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -47,7 +47,7 @@ dependencies = [ "local-channel", "log 0.4.17", "mime 0.3.16", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", "rand 0.8.5", "sha-1 0.10.0", @@ -92,7 +92,7 @@ dependencies = [ "openssl", "pin-project-lite 0.2.9", "tokio-openssl", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -341,9 +341,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ascii" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "asn1_der" @@ -413,10 +413,11 @@ dependencies = [ [[package]] name = "async-io" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" dependencies = [ + "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -441,11 +442,12 @@ dependencies = [ [[package]] name = "async-process" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" dependencies = [ "async-io", + "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", @@ -467,7 +469,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "futures-channel", "futures-core", "futures-io", @@ -631,10 +633,10 @@ dependencies = [ "log 0.4.17", "mime 0.3.16", "openssl", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", @@ -698,7 +700,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -770,7 +772,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -805,7 +807,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -1156,9 +1158,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -1208,11 +1210,11 @@ dependencies = [ "num-traits 0.2.15", "num256", "secp256k1", - "serde 1.0.144", + "serde 1.0.145", "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.4", + "sha3 0.10.5", ] [[package]] @@ -1307,7 +1309,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.144", + "serde 1.0.145", "serde-hjson", "serde_json", "toml", @@ -1445,7 +1447,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", ] [[package]] @@ -1456,20 +1458,19 @@ checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "memoffset", - "once_cell", "scopeguard", ] @@ -1486,12 +1487,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] [[package]] @@ -1602,7 +1602,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -1769,9 +1769,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1862,7 +1862,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", "signature", ] @@ -1874,8 +1874,8 @@ checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core 0.6.3", - "serde 1.0.144", + "rand_core 0.6.4", + "serde 1.0.145", "sha2 0.9.9", "thiserror", "zeroize", @@ -1891,7 +1891,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1983,7 +1983,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.144", + "serde 1.0.145", "serde_json", ] @@ -1997,9 +1997,9 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.144", + "serde 1.0.145", "serde_json", - "sha3 0.10.4", + "sha3 0.10.5", "thiserror", "uint", ] @@ -2097,19 +2097,19 @@ dependencies = [ "blake2 0.10.4", "blake2b_simd", "borsh", - "digest 0.10.3", + "digest 0.10.5", "ed25519-dalek", "either", "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "measure_time", "miracl_core", "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subproductdomain", @@ -2126,7 +2126,7 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", ] @@ -2233,12 +2233,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", ] [[package]] @@ -2502,7 +2501,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -2551,10 +2550,10 @@ dependencies = [ "blake2b_simd", "chacha20 0.8.2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "miracl_core", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "subproductdomain", "thiserror", @@ -2595,7 +2594,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tracing 0.1.36", ] @@ -2619,9 +2618,9 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.1" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea9fe3952d32674a14e0975009a3547af9ea364995b5ec1add2e23c2ae523ab" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ "byteorder", "num-traits 0.2.15", @@ -2882,14 +2881,13 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", - "once_cell", "wasm-bindgen", "winapi 0.3.9", ] @@ -2897,7 +2895,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes 1.2.1", "derive_more", @@ -2908,10 +2906,10 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.6", "subtle-encoding", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", @@ -2935,10 +2933,10 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.6", "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)", @@ -2951,12 +2949,12 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tonic", ] @@ -2969,7 +2967,7 @@ dependencies = [ "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tonic", ] @@ -3018,6 +3016,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.6.7" @@ -3079,7 +3087,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3107,7 +3115,7 @@ checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3195,9 +3203,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -3210,18 +3218,18 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -3296,9 +3304,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -3719,7 +3727,7 @@ dependencies = [ "quicksink", "rw-stream-sink", "soketto", - "url 2.3.0", + "url 2.3.1", "webpki-roots", ] @@ -3779,7 +3787,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "sha2 0.9.9", "typenum", ] @@ -3842,7 +3850,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3874,9 +3882,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3958,7 +3966,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_yaml", ] @@ -4043,15 +4051,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "integer-encoding", "lazy_static", "log 0.4.17", "mio 0.7.14", - "serde 1.0.144", + "serde 1.0.145", "strum", "tungstenite 0.16.0", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -4302,7 +4310,7 @@ dependencies = [ "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", - "itertools 0.10.3", + "itertools 0.10.5", "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", @@ -4314,9 +4322,9 @@ dependencies = [ "prost-types 0.9.0", "pwasm-utils", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4375,7 +4383,7 @@ dependencies = [ "futures 0.3.24", "git2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libc", "libloading", "libp2p", @@ -4392,14 +4400,14 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "regex", "reqwest", "rlimit", "rocksdb", "rpassword", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_regex", @@ -4441,7 +4449,7 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", + "itertools 0.10.5", "lazy_static", "madato", "namada", @@ -4480,7 +4488,7 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libp2p", "namada", "namada_apps", @@ -4503,7 +4511,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -4521,7 +4529,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -4705,7 +4713,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -4811,7 +4819,7 @@ dependencies = [ "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", ] @@ -4857,9 +4865,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -4875,9 +4883,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4907,9 +4915,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg 1.1.0", "cc", @@ -4961,25 +4969,25 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding 2.1.0", - "serde 1.0.144", + "percent-encoding 2.2.0", + "serde 1.0.145", "static_assertions", "unsigned-varint 0.7.1", - "url 2.3.0", + "url 2.3.1", ] [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec 0.7.2", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -5030,7 +5038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.8", + "lock_api 0.4.9", "parking_lot_core 0.8.5", ] @@ -5040,7 +5048,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.8", + "lock_api 0.4.9", "parking_lot_core 0.9.3", ] @@ -5139,9 +5147,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" @@ -5276,10 +5284,11 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", @@ -5323,7 +5332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools 0.10.5", "predicates-core", ] @@ -5414,9 +5423,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -5486,7 +5495,7 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.2.1", "heck 0.3.3", - "itertools 0.10.3", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "multimap", @@ -5518,7 +5527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools 0.10.5", "proc-macro2", "quote", "syn", @@ -5663,7 +5672,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5693,7 +5702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5722,9 +5731,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -5815,7 +5824,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5838,7 +5847,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "num_cpus", ] @@ -5946,9 +5955,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", "bytes 1.2.1", @@ -5962,19 +5971,19 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log 0.4.17", "mime 0.3.16", "native-tls", - "percent-encoding 2.1.0", + "once_cell", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", "tower-service", - "url 2.3.0", + "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6095,7 +6104,7 @@ checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6140,7 +6149,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.13", + "semver 1.0.14", ] [[package]] @@ -6168,6 +6177,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -6373,9 +6391,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" @@ -6400,9 +6418,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -6428,7 +6446,7 @@ dependencies = [ "byteorder", "error", "num 0.2.1", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6437,14 +6455,14 @@ version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -6459,7 +6477,7 @@ checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6469,7 +6487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6492,7 +6510,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6503,7 +6521,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.144", + "serde 1.0.145", "yaml-rust", ] @@ -6540,18 +6558,18 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] name = "sha1" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -6581,13 +6599,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -6604,11 +6622,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaedf34ed289ea47c2b741bb72e5357a209512d67bcd4bda44359e5bf0470f56" +checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", "keccak", ] @@ -6654,9 +6672,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" [[package]] name = "simple-error" @@ -6704,7 +6722,7 @@ dependencies = [ "blake2 0.9.2", "chacha20poly1305", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.3.3", "sha2 0.9.9", @@ -6874,9 +6892,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -6949,7 +6967,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6961,7 +6979,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -6989,7 +7007,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -7005,14 +7023,14 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "toml", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7021,21 +7039,21 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", - "url 2.3.0", + "url 2.3.1", ] [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", - "serde 1.0.144", + "serde 1.0.145", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", @@ -7048,7 +7066,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.144", + "serde 1.0.145", "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.14", @@ -7057,7 +7075,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes 1.2.1", "flex-error", @@ -7065,7 +7083,7 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "subtle-encoding", "time 0.3.14", @@ -7082,7 +7100,7 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "subtle-encoding", "time 0.3.14", @@ -7091,7 +7109,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "async-tungstenite", @@ -7105,7 +7123,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project 1.0.12", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7116,7 +7134,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.3.0", + "url 2.3.1", "uuid", "walkdir", ] @@ -7138,7 +7156,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project 1.0.12", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7149,7 +7167,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.3.0", + "url 2.3.1", "uuid", "walkdir", ] @@ -7157,11 +7175,11 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", @@ -7176,7 +7194,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", @@ -7318,7 +7336,7 @@ dependencies = [ "chunked_transfer", "log 0.4.17", "time 0.3.14", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7338,9 +7356,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg 1.1.0", "bytes 1.2.1", @@ -7348,7 +7366,6 @@ dependencies = [ "memchr", "mio 0.8.4", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", @@ -7464,9 +7481,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -7523,15 +7540,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log 0.4.17", - "pin-project 1.0.12", "tokio", - "tungstenite 0.14.0", + "tungstenite 0.17.3", ] [[package]] @@ -7550,9 +7566,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes 1.2.1", "futures-core", @@ -7568,7 +7584,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -7588,7 +7604,7 @@ dependencies = [ "http-body", "hyper 0.14.20", "hyper-timeout", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project 1.0.12", "prost 0.9.0", "prost-derive 0.9.0", @@ -7629,7 +7645,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tower-layer", "tower-service", "tracing 0.1.36", @@ -7865,7 +7881,7 @@ dependencies = [ "smallvec 1.9.0", "thiserror", "tinyvec", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7908,15 +7924,15 @@ dependencies = [ "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.3.0", + "url 2.3.1", "utf-8", ] [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", @@ -7927,15 +7943,15 @@ dependencies = [ "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.3.0", + "url 2.3.1", "utf-8", ] [[package]] name = "tungstenite" -version = "0.16.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.0", "byteorder", @@ -7944,9 +7960,9 @@ dependencies = [ "httparse", "log 0.4.17", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1 0.10.0", "thiserror", - "url 2.3.0", + "url 2.3.1", "utf-8", ] @@ -7979,9 +7995,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -8015,36 +8031,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -8093,13 +8109,13 @@ dependencies = [ [[package]] name = "url" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.2.3", - "percent-encoding 2.1.0", + "idna 0.3.0", + "percent-encoding 2.2.0", ] [[package]] @@ -8207,9 +8223,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ "bytes 1.2.1", "futures-channel", @@ -8221,16 +8237,17 @@ dependencies = [ "mime 0.3.16", "mime_guess", "multipart", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project 1.0.12", + "rustls-pemfile", "scoped-tls", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.6.10", + "tokio-util 0.7.4", "tower-service", "tracing 0.1.36", ] @@ -8255,9 +8272,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8265,9 +8282,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log 0.4.17", @@ -8280,9 +8297,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8292,9 +8309,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8302,9 +8319,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -8315,15 +8332,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" dependencies = [ "leb128", ] @@ -8390,7 +8407,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "smallvec 1.9.0", "target-lexicon", @@ -8465,7 +8482,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "target-lexicon", "thiserror", @@ -8488,7 +8505,7 @@ dependencies = [ "loupe", "object 0.28.4", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "tempfile", "tracing 0.1.36", "wasmer-compiler", @@ -8540,7 +8557,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "thiserror", ] @@ -8561,7 +8578,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -8581,9 +8598,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "46.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", @@ -8593,9 +8610,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] @@ -8621,9 +8638,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -8642,7 +8659,7 @@ dependencies = [ "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", "tokio", From 301edab6082141f79e2730dad0384ed66b3dbe18 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 10:26:19 +0100 Subject: [PATCH 0752/1995] Remove TODO from ledger_txs_and_queries() e2e test --- tests/src/e2e/ledger_tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 824fef4707..4b7aca83bd 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -243,7 +243,6 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 6. Query token balance /// 7. Query the raw bytes of a storage key #[test] -// TODO(namada#418): re-enable once working again fn ledger_txs_and_queries() -> Result<()> { use namada_apps::config::Config; From 0ccc3465f332bc72dea8ec51e0fb52b22b2f4cbb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 10:52:00 +0100 Subject: [PATCH 0753/1995] Re-enable and fix run_ledger() e2e test --- tests/src/e2e/ledger_tests.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 4b7aca83bd..c45dcfb648 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -18,10 +18,10 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use namada::types::token; -use namada_apps::config::ethereum; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; +use namada_apps::config::{ethereum, Config}; use serde_json::json; use setup::constants::*; @@ -36,10 +36,25 @@ use crate::{run, run_as}; /// combinations from fresh state, the node starts-up successfully for both a /// validator and non-validator user. #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; + + let update_config = |mut config: Config| { + // disable eth full node + config.ledger.ethereum.mode = ethereum::Mode::Off; + config + }; + + let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); + let validator_0_config = update_config(Config::load( + &validator_0_base_dir, + &test.net.chain_id, + None, + )); + validator_0_config + .write(&validator_0_base_dir, &test.net.chain_id, true) + .unwrap(); + let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; // Start the ledger as a validator @@ -244,8 +259,6 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 7. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { - use namada_apps::config::Config; - let test = setup::network(|genesis| genesis, None)?; let update_config = |mut config: Config| { From ef4cada7294e70d4839a0e67f2be059c73729f4e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 12:36:36 +0100 Subject: [PATCH 0754/1995] Refactor update config --- tests/src/e2e/ledger_tests.rs | 37 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c45dcfb648..cf8cb480d4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -17,6 +17,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; +use namada::types::chain::ChainId; use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, @@ -32,28 +33,32 @@ use crate::e2e::helpers::{ use crate::e2e::setup::{self, sleep, Bin, Who}; use crate::{run, run_as}; +fn update_actor_config(test: &Test, chain_id: &ChainId, who: &Who, update: F) +where + F: FnOnce(&mut Config), +{ + let validator_base_dir = test.get_base_dir(who); + let mut validator_config = + Config::load(&validator_base_dir, chain_id, None); + update(&mut validator_config); + validator_config + .write(&validator_base_dir, chain_id, true) + .unwrap(); +} + +fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { + update_actor_config(test, chain_id, who, |config| { + config.ledger.ethereum.mode = ethereum::Mode::Off; + }); +} + /// Test that when we "run-ledger" with all the possible command /// combinations from fresh state, the node starts-up successfully for both a /// validator and non-validator user. #[test] fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; - - let update_config = |mut config: Config| { - // disable eth full node - config.ledger.ethereum.mode = ethereum::Mode::Off; - config - }; - - let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); - let validator_0_config = update_config(Config::load( - &validator_0_base_dir, - &test.net.chain_id, - None, - )); - validator_0_config - .write(&validator_0_base_dir, &test.net.chain_id, true) - .unwrap(); + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; From 482b4d85e9649d5b90794afacbeaf5f8afaf7831 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 30 Sep 2022 12:48:07 +0100 Subject: [PATCH 0755/1995] Sync Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 34a1cf2c90..b4448bd3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4391,7 +4391,7 @@ dependencies = [ "rocksdb", "rpassword", "semver 1.0.14", - "serde 1.0.144", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_regex", From 4e1e8bde836b5c9a8c5b2b59340bf64474e52a09 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 12:52:57 +0100 Subject: [PATCH 0756/1995] Downgraded problematic deps in Cargo.lock --- Cargo.lock | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bd12f1ebe..6a840908b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,11 +413,10 @@ dependencies = [ [[package]] name = "async-io" -version = "1.9.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ - "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -442,12 +441,11 @@ dependencies = [ [[package]] name = "async-process" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", - "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", @@ -5284,11 +5282,10 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", From c7c97548bbc6497c24ffbbab6ee962f8e328a335 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 13:00:36 +0100 Subject: [PATCH 0757/1995] More Cargo.lock madness --- wasm/tx_template/Cargo.lock | 16 +- wasm/vp_template/Cargo.lock | 16 +- wasm_for_tests/wasm_source/Cargo.lock | 761 ++++++++++++++------------ 3 files changed, 420 insertions(+), 373 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index e77113a834..e632831a20 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2675,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2718,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2742,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3bb42f693f..03f849ef67 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2675,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2718,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2742,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 3128090166..f98f38a872 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/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]] @@ -30,18 +30,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -175,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +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", ] @@ -261,7 +270,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -276,9 +285,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -332,9 +341,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-slice-cast" @@ -344,9 +353,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[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", @@ -354,9 +363,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", @@ -371,9 +380,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" @@ -395,14 +404,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -427,11 +438,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -507,9 +524,9 @@ 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", @@ -517,9 +534,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", @@ -528,26 +545,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "lazy_static", ] [[package]] @@ -558,9 +573,9 @@ 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", "typenum", @@ -587,16 +602,16 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] [[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", @@ -604,23 +619,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", @@ -660,11 +674,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -697,22 +711,22 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +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", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2 0.9.9", "thiserror", @@ -733,9 +747,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "enum-iterator" @@ -759,18 +773,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", @@ -790,7 +804,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.2", + "sha3 0.10.5", "thiserror", "uint", ] @@ -840,9 +854,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", ] @@ -850,7 +864,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -874,9 +888,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" @@ -896,11 +910,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -912,9 +925,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -926,9 +939,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -936,33 +949,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", "futures-sink", @@ -973,9 +986,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", @@ -983,13 +996,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]] @@ -1005,9 +1018,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 = "gumdrop" @@ -1031,9 +1044,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -1044,7 +1057,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.4", "tracing", ] @@ -1059,9 +1072,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -1092,9 +1105,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", @@ -1103,9 +1116,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", @@ -1114,9 +1127,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1126,9 +1139,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -1160,10 +1173,23 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1177,20 +1203,20 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "subtle-encoding", "tendermint", "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", + "time 0.3.14", "tracing", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -1224,11 +1250,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -1279,12 +1304,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] @@ -1299,33 +1324,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.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.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 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" @@ -1341,9 +1366,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libloading" @@ -1399,9 +1424,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -1445,23 +1470,17 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -1483,35 +1502,23 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1556,7 +1563,7 @@ dependencies = [ "prost-types", "pwasm-utils", "rand", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", "serde", "serde_json", @@ -1619,7 +1626,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -1637,7 +1644,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -1653,15 +1660,6 @@ dependencies = [ "wee_alloc", ] -[[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" @@ -1686,9 +1684,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", @@ -1708,9 +1706,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", ] @@ -1727,39 +1725,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +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 0.11.2", - "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1769,9 +1767,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec", "bitvec", @@ -1801,9 +1799,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "peg" @@ -1834,24 +1832,25 @@ checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" 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", @@ -1859,18 +1858,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -1879,9 +1878,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" @@ -1954,11 +1953,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2078,9 +2077,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -2099,7 +2098,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2109,7 +2108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2120,9 +2119,9 @@ checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -2133,14 +2132,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] 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", @@ -2150,22 +2149,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.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -2183,9 +2181,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -2203,9 +2201,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" @@ -2250,12 +2248,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -2264,9 +2262,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -2285,9 +2283,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.23.1" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", "num-traits", @@ -2323,9 +2321,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" @@ -2341,9 +2339,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" @@ -2433,27 +2431,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 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.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -2462,9 +2460,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -2473,9 +2471,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", @@ -2497,13 +2495,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -2520,11 +2518,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", "keccak", ] @@ -2539,9 +2537,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" [[package]] name = "simple-error" @@ -2551,21 +2549,24 @@ 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" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -2600,12 +2601,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[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" @@ -2629,13 +2624,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.90" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2658,9 +2653,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -2679,7 +2674,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2700,14 +2695,14 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time 0.3.14", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2720,20 +2715,20 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time 0.3.14", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2744,13 +2739,13 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2765,7 +2760,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time 0.3.14", "url", "uuid", "walkdir", @@ -2774,7 +2769,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2783,14 +2778,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.9", + "time 0.3.14", ] [[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", @@ -2799,18 +2794,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2839,9 +2834,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "libc", "num_threads", @@ -2865,9 +2860,9 @@ dependencies = [ [[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", ] @@ -2880,10 +2875,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ + "autocfg", "bytes", "libc", "memchr", @@ -2906,9 +2902,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", @@ -2917,9 +2913,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite", @@ -2928,9 +2924,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", @@ -2942,9 +2938,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -2956,9 +2952,9 @@ dependencies = [ [[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", ] @@ -2986,7 +2982,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -3008,9 +3004,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", @@ -3020,7 +3016,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.4", "tower-layer", "tower-service", "tracing", @@ -3034,15 +3030,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.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -3053,9 +3049,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -3064,12 +3060,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.23" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -3084,12 +3079,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", @@ -3111,15 +3106,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -3129,46 +3124,51 @@ dependencies = [ [[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3178,12 +3178,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" @@ -3234,9 +3228,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3244,13 +3238,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -3259,9 +3253,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3269,9 +3263,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -3282,9 +3276,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wasm-encoder" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3429,7 +3432,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", @@ -3468,7 +3471,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", @@ -3524,20 +3527,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] @@ -3556,13 +3560,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -3596,6 +3600,49 @@ 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 = "wyz" version = "0.5.0" From fec843a074495501af48afd1c0976c00aabdd017 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 13:06:32 +0100 Subject: [PATCH 0758/1995] Add missing import --- tests/src/e2e/ledger_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index cf8cb480d4..815d9d55a6 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -30,7 +30,7 @@ use super::setup::working_dir; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; -use crate::e2e::setup::{self, sleep, Bin, Who}; +use crate::e2e::setup::{self, sleep, Bin, Test, Who}; use crate::{run, run_as}; fn update_actor_config(test: &Test, chain_id: &ChainId, who: &Who, update: F) From e4e67c1fe0a3fd8e58eb5a91600ba0215b2fd45d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 13:08:18 +0100 Subject: [PATCH 0759/1995] Cargo.lock madness --- Cargo.lock | 510 ++++++++--------- wasm/tx_template/Cargo.lock | 16 +- wasm/vp_template/Cargo.lock | 16 +- wasm_for_tests/wasm_source/Cargo.lock | 761 ++++++++++++++------------ 4 files changed, 682 insertions(+), 621 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c85af78a0..6a840908b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ dependencies = [ "memchr", "pin-project-lite 0.2.9", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -47,7 +47,7 @@ dependencies = [ "local-channel", "log 0.4.17", "mime 0.3.16", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", "rand 0.8.5", "sha-1 0.10.0", @@ -92,7 +92,7 @@ dependencies = [ "openssl", "pin-project-lite 0.2.9", "tokio-openssl", - "tokio-util 0.7.3", + "tokio-util 0.7.4", ] [[package]] @@ -195,9 +195,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -341,9 +341,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ascii" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "asn1_der" @@ -467,7 +467,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "futures-channel", "futures-core", "futures-io", @@ -631,10 +631,10 @@ dependencies = [ "log 0.4.17", "mime 0.3.16", "openssl", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", @@ -698,7 +698,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -770,7 +770,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -805,7 +805,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -1156,9 +1156,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -1208,11 +1208,11 @@ dependencies = [ "num-traits 0.2.15", "num256", "secp256k1", - "serde 1.0.144", + "serde 1.0.145", "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.4", + "sha3 0.10.5", ] [[package]] @@ -1307,7 +1307,7 @@ dependencies = [ "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.144", + "serde 1.0.145", "serde-hjson", "serde_json", "toml", @@ -1445,7 +1445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", ] [[package]] @@ -1456,20 +1456,19 @@ checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "memoffset", - "once_cell", "scopeguard", ] @@ -1486,12 +1485,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "once_cell", ] [[package]] @@ -1602,7 +1600,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -1769,9 +1767,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1862,7 +1860,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", "signature", ] @@ -1874,8 +1872,8 @@ checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core 0.6.3", - "serde 1.0.144", + "rand_core 0.6.4", + "serde 1.0.145", "sha2 0.9.9", "thiserror", "zeroize", @@ -1891,7 +1889,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1983,7 +1981,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.144", + "serde 1.0.145", "serde_json", ] @@ -1997,9 +1995,9 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.144", + "serde 1.0.145", "serde_json", - "sha3 0.10.4", + "sha3 0.10.5", "thiserror", "uint", ] @@ -2097,19 +2095,19 @@ dependencies = [ "blake2 0.10.4", "blake2b_simd", "borsh", - "digest 0.10.3", + "digest 0.10.5", "ed25519-dalek", "either", "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "measure_time", "miracl_core", "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subproductdomain", @@ -2126,7 +2124,7 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", ] @@ -2233,12 +2231,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", ] [[package]] @@ -2502,7 +2499,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -2551,10 +2548,10 @@ dependencies = [ "blake2b_simd", "chacha20 0.8.2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "miracl_core", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "subproductdomain", "thiserror", @@ -2595,7 +2592,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tracing 0.1.36", ] @@ -2619,9 +2616,9 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.1" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea9fe3952d32674a14e0975009a3547af9ea364995b5ec1add2e23c2ae523ab" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ "byteorder", "num-traits 0.2.15", @@ -2882,14 +2879,13 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", - "once_cell", "wasm-bindgen", "winapi 0.3.9", ] @@ -2897,7 +2893,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes 1.2.1", "derive_more", @@ -2908,10 +2904,10 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.6", "subtle-encoding", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", @@ -2935,10 +2931,10 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.5", + "sha2 0.10.6", "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)", @@ -2951,12 +2947,12 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#1e572fdc00df9470ec6298af00ff8f88685a1ca4" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tonic", ] @@ -2969,7 +2965,7 @@ dependencies = [ "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "tonic", ] @@ -3018,6 +3014,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.6.7" @@ -3079,7 +3085,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3107,7 +3113,7 @@ checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", "hashbrown 0.12.3", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3195,9 +3201,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] @@ -3210,18 +3216,18 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -3296,9 +3302,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -3719,7 +3725,7 @@ dependencies = [ "quicksink", "rw-stream-sink", "soketto", - "url 2.3.0", + "url 2.3.1", "webpki-roots", ] @@ -3779,7 +3785,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.144", + "serde 1.0.145", "sha2 0.9.9", "typenum", ] @@ -3842,7 +3848,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -3874,9 +3880,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3958,7 +3964,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_yaml", ] @@ -4043,15 +4049,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "integer-encoding", "lazy_static", "log 0.4.17", "mio 0.7.14", - "serde 1.0.144", + "serde 1.0.145", "strum", "tungstenite 0.16.0", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -4302,7 +4308,7 @@ dependencies = [ "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus)", "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "ics23", - "itertools 0.10.3", + "itertools 0.10.5", "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", @@ -4314,9 +4320,9 @@ dependencies = [ "prost-types 0.9.0", "pwasm-utils", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4375,7 +4381,7 @@ dependencies = [ "futures 0.3.24", "git2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libc", "libloading", "libp2p", @@ -4392,14 +4398,14 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "regex", "reqwest", "rlimit", "rocksdb", "rpassword", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_regex", @@ -4441,7 +4447,7 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", + "itertools 0.10.5", "lazy_static", "madato", "namada", @@ -4480,7 +4486,7 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libp2p", "namada", "namada_apps", @@ -4503,7 +4509,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -4521,7 +4527,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.5", + "sha2 0.10.6", ] [[package]] @@ -4705,7 +4711,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -4811,7 +4817,7 @@ dependencies = [ "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", ] @@ -4857,9 +4863,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -4875,9 +4881,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4907,9 +4913,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg 1.1.0", "cc", @@ -4961,25 +4967,25 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding 2.1.0", - "serde 1.0.144", + "percent-encoding 2.2.0", + "serde 1.0.145", "static_assertions", "unsigned-varint 0.7.1", - "url 2.3.0", + "url 2.3.1", ] [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec 0.7.2", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -5030,7 +5036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.8", + "lock_api 0.4.9", "parking_lot_core 0.8.5", ] @@ -5040,7 +5046,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.8", + "lock_api 0.4.9", "parking_lot_core 0.9.3", ] @@ -5139,9 +5145,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" @@ -5323,7 +5329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools 0.10.5", "predicates-core", ] @@ -5414,9 +5420,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -5486,7 +5492,7 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.2.1", "heck 0.3.3", - "itertools 0.10.3", + "itertools 0.10.5", "lazy_static", "log 0.4.17", "multimap", @@ -5518,7 +5524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools 0.10.5", "proc-macro2", "quote", "syn", @@ -5663,7 +5669,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5693,7 +5699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5722,9 +5728,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.7", ] @@ -5815,7 +5821,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5838,7 +5844,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.11", + "crossbeam-utils 0.8.12", "num_cpus", ] @@ -5946,9 +5952,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", "bytes 1.2.1", @@ -5962,19 +5968,19 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log 0.4.17", "mime 0.3.16", "native-tls", - "percent-encoding 2.1.0", + "once_cell", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", "tower-service", - "url 2.3.0", + "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -6095,7 +6101,7 @@ checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6140,7 +6146,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.13", + "semver 1.0.14", ] [[package]] @@ -6168,6 +6174,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -6373,9 +6388,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" @@ -6400,9 +6415,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -6428,7 +6443,7 @@ dependencies = [ "byteorder", "error", "num 0.2.1", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6437,14 +6452,14 @@ version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -6459,7 +6474,7 @@ checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6469,7 +6484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6492,7 +6507,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -6503,7 +6518,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.144", + "serde 1.0.145", "yaml-rust", ] @@ -6540,18 +6555,18 @@ checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] name = "sha1" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -6581,13 +6596,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9db03534dff993187064c4e0c05a5708d2a9728ace9a8959b77bedf415dac5" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.5", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -6604,11 +6619,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.4" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaedf34ed289ea47c2b741bb72e5357a209512d67bcd4bda44359e5bf0470f56" +checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", "keccak", ] @@ -6654,9 +6669,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" [[package]] name = "simple-error" @@ -6704,7 +6719,7 @@ dependencies = [ "blake2 0.9.2", "chacha20poly1305", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.3.3", "sha2 0.9.9", @@ -6874,9 +6889,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -6949,7 +6964,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes 1.2.1", @@ -6961,7 +6976,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -6989,7 +7004,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -7005,14 +7020,14 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "toml", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7021,21 +7036,21 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", "toml", - "url 2.3.0", + "url 2.3.1", ] [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", - "serde 1.0.144", + "serde 1.0.145", "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus)", "time 0.3.14", @@ -7048,7 +7063,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.144", + "serde 1.0.145", "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.14", @@ -7057,7 +7072,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes 1.2.1", "flex-error", @@ -7065,7 +7080,7 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "subtle-encoding", "time 0.3.14", @@ -7082,7 +7097,7 @@ dependencies = [ "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "subtle-encoding", "time 0.3.14", @@ -7091,7 +7106,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "async-tungstenite", @@ -7105,7 +7120,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project 1.0.12", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7116,7 +7131,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.3.0", + "url 2.3.1", "uuid", "walkdir", ] @@ -7138,7 +7153,7 @@ dependencies = [ "hyper-rustls", "peg", "pin-project 1.0.12", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -7149,7 +7164,7 @@ dependencies = [ "time 0.3.14", "tokio", "tracing 0.1.36", - "url 2.3.0", + "url 2.3.1", "uuid", "walkdir", ] @@ -7157,11 +7172,11 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", @@ -7176,7 +7191,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", @@ -7318,7 +7333,7 @@ dependencies = [ "chunked_transfer", "log 0.4.17", "time 0.3.14", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7338,9 +7353,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg 1.1.0", "bytes 1.2.1", @@ -7348,7 +7363,6 @@ dependencies = [ "memchr", "mio 0.8.4", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", @@ -7464,9 +7478,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -7523,15 +7537,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log 0.4.17", - "pin-project 1.0.12", "tokio", - "tungstenite 0.14.0", + "tungstenite 0.17.3", ] [[package]] @@ -7550,9 +7563,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes 1.2.1", "futures-core", @@ -7568,7 +7581,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.144", + "serde 1.0.145", ] [[package]] @@ -7588,7 +7601,7 @@ dependencies = [ "http-body", "hyper 0.14.20", "hyper-timeout", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project 1.0.12", "prost 0.9.0", "prost-derive 0.9.0", @@ -7629,7 +7642,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tower-layer", "tower-service", "tracing 0.1.36", @@ -7865,7 +7878,7 @@ dependencies = [ "smallvec 1.9.0", "thiserror", "tinyvec", - "url 2.3.0", + "url 2.3.1", ] [[package]] @@ -7908,15 +7921,15 @@ dependencies = [ "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.3.0", + "url 2.3.1", "utf-8", ] [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", @@ -7927,15 +7940,15 @@ dependencies = [ "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.3.0", + "url 2.3.1", "utf-8", ] [[package]] name = "tungstenite" -version = "0.16.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.0", "byteorder", @@ -7944,9 +7957,9 @@ dependencies = [ "httparse", "log 0.4.17", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1 0.10.0", "thiserror", - "url 2.3.0", + "url 2.3.1", "utf-8", ] @@ -7979,9 +7992,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -8015,36 +8028,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -8093,13 +8106,13 @@ dependencies = [ [[package]] name = "url" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.2.3", - "percent-encoding 2.1.0", + "idna 0.3.0", + "percent-encoding 2.2.0", ] [[package]] @@ -8207,9 +8220,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ "bytes 1.2.1", "futures-channel", @@ -8221,16 +8234,17 @@ dependencies = [ "mime 0.3.16", "mime_guess", "multipart", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", "pin-project 1.0.12", + "rustls-pemfile", "scoped-tls", - "serde 1.0.144", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.6.10", + "tokio-util 0.7.4", "tower-service", "tracing 0.1.36", ] @@ -8255,9 +8269,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8265,9 +8279,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log 0.4.17", @@ -8280,9 +8294,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8292,9 +8306,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8302,9 +8316,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -8315,15 +8329,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" dependencies = [ "leb128", ] @@ -8390,7 +8404,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "smallvec 1.9.0", "target-lexicon", @@ -8465,7 +8479,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.144", + "serde 1.0.145", "serde_bytes", "target-lexicon", "thiserror", @@ -8488,7 +8502,7 @@ dependencies = [ "loupe", "object 0.28.4", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "tempfile", "tracing 0.1.36", "wasmer-compiler", @@ -8540,7 +8554,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "thiserror", ] @@ -8561,7 +8575,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.144", + "serde 1.0.145", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -8581,9 +8595,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "46.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", @@ -8593,9 +8607,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] @@ -8621,9 +8635,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -8642,7 +8656,7 @@ dependencies = [ "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.144", + "serde 1.0.145", "serde_derive", "serde_json", "tokio", diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index e77113a834..e632831a20 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2675,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2718,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2742,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3bb42f693f..03f849ef67 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#c3bdd9100094ad80e259617184e813a7c2e2db76" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2675,7 +2675,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", @@ -2701,7 +2701,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2718,7 +2718,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2742,7 +2742,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#4c7ba08825559cdac12c9acae2bf63b396d8b0ca" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 3128090166..f98f38a872 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/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]] @@ -30,18 +30,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -175,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +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", ] @@ -261,7 +270,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -276,9 +285,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -332,9 +341,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-slice-cast" @@ -344,9 +353,9 @@ checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[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", @@ -354,9 +363,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", @@ -371,9 +380,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" @@ -395,14 +404,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -427,11 +438,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -507,9 +524,9 @@ 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", @@ -517,9 +534,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", @@ -528,26 +545,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "lazy_static", ] [[package]] @@ -558,9 +573,9 @@ 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", "typenum", @@ -587,16 +602,16 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] [[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", @@ -604,23 +619,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", @@ -660,11 +674,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -697,22 +711,22 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +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", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "sha2 0.9.9", "thiserror", @@ -733,9 +747,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "enum-iterator" @@ -759,18 +773,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", @@ -790,7 +804,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.2", + "sha3 0.10.5", "thiserror", "uint", ] @@ -840,9 +854,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", ] @@ -850,7 +864,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -874,9 +888,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" @@ -896,11 +910,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", "percent-encoding", ] @@ -912,9 +925,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -926,9 +939,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -936,33 +949,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", "futures-sink", @@ -973,9 +986,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", @@ -983,13 +996,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]] @@ -1005,9 +1018,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 = "gumdrop" @@ -1031,9 +1044,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ "bytes", "fnv", @@ -1044,7 +1057,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.4", "tracing", ] @@ -1059,9 +1072,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -1092,9 +1105,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", @@ -1103,9 +1116,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", @@ -1114,9 +1127,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -1126,9 +1139,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -1160,10 +1173,23 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "derive_more", @@ -1177,20 +1203,20 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "subtle-encoding", "tendermint", "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", + "time 0.3.14", "tracing", ] [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#17f862484682c8e62d0d61ecb4a16d4f1677c027" +source = "git+https://github.com/heliaxdev/ibc-rs?branch=tiago/abciplus#90a02f8b350bef86bd67ce613e94405eb3594e94" dependencies = [ "bytes", "prost", @@ -1224,11 +1250,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] @@ -1279,12 +1304,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] @@ -1299,33 +1324,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.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.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 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" @@ -1341,9 +1366,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libloading" @@ -1399,9 +1424,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -1445,23 +1470,17 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -1483,35 +1502,23 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1556,7 +1563,7 @@ dependencies = [ "prost-types", "pwasm-utils", "rand", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", "serde", "serde_json", @@ -1619,7 +1626,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -1637,7 +1644,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -1653,15 +1660,6 @@ dependencies = [ "wee_alloc", ] -[[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" @@ -1686,9 +1684,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", @@ -1708,9 +1706,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", ] @@ -1727,39 +1725,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +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 0.11.2", - "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -1769,9 +1767,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" dependencies = [ "arrayvec", "bitvec", @@ -1801,9 +1799,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "peg" @@ -1834,24 +1832,25 @@ checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" 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", @@ -1859,18 +1858,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -1879,9 +1878,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" @@ -1954,11 +1953,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2078,9 +2077,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -2099,7 +2098,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2109,7 +2108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -2120,9 +2119,9 @@ checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] @@ -2133,14 +2132,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] 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", @@ -2150,22 +2149,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.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -2183,9 +2181,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -2203,9 +2201,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" @@ -2250,12 +2248,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -2264,9 +2262,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -2285,9 +2283,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.23.1" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", "num-traits", @@ -2323,9 +2321,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" @@ -2341,9 +2339,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" @@ -2433,27 +2431,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 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.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -2462,9 +2460,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", @@ -2473,9 +2471,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", @@ -2497,13 +2495,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -2520,11 +2518,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +checksum = "e2904bea16a1ae962b483322a1c7b81d976029203aea1f461e51cd7705db7ba9" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", "keccak", ] @@ -2539,9 +2537,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "deb766570a2825fa972bceff0d195727876a9cdf2460ab2e52d455dc2de47fd9" [[package]] name = "simple-error" @@ -2551,21 +2549,24 @@ 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" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -2600,12 +2601,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[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" @@ -2629,13 +2624,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.90" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2658,9 +2653,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -2679,7 +2674,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "async-trait", "bytes", @@ -2700,14 +2695,14 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time 0.3.14", "zeroize", ] [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "flex-error", "serde", @@ -2720,20 +2715,20 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time 0.3.14", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2744,13 +2739,13 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "bytes", "flex-error", @@ -2765,7 +2760,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time 0.3.14", "url", "uuid", "walkdir", @@ -2774,7 +2769,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#d035949ce7129cf54b3d15f29f339de3b643ea24" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=tiago/abciplus#4b64103c372c67c1969c3a681f2746f115d1a5ca" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2783,14 +2778,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.9", + "time 0.3.14", ] [[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", @@ -2799,18 +2794,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -2839,9 +2834,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "libc", "num_threads", @@ -2865,9 +2860,9 @@ dependencies = [ [[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", ] @@ -2880,10 +2875,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ + "autocfg", "bytes", "libc", "memchr", @@ -2906,9 +2902,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", @@ -2917,9 +2913,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite", @@ -2928,9 +2924,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", @@ -2942,9 +2938,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -2956,9 +2952,9 @@ dependencies = [ [[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", ] @@ -2986,7 +2982,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -3008,9 +3004,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", @@ -3020,7 +3016,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util 0.7.4", "tower-layer", "tower-service", "tracing", @@ -3034,15 +3030,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.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -3053,9 +3049,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -3064,12 +3060,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.23" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -3084,12 +3079,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", @@ -3111,15 +3106,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -3129,46 +3124,51 @@ dependencies = [ [[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", "idna", - "matches", "percent-encoding", ] @@ -3178,12 +3178,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" @@ -3234,9 +3228,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -3244,13 +3238,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -3259,9 +3253,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3269,9 +3263,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -3282,9 +3276,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wasm-encoder" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3429,7 +3432,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", @@ -3468,7 +3471,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", @@ -3524,20 +3527,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] @@ -3556,13 +3560,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -3596,6 +3600,49 @@ 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 = "wyz" version = "0.5.0" From 161293ac9d6b86b5551938047c892d91b00967c3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 30 Sep 2022 13:36:12 +0100 Subject: [PATCH 0760/1995] Fix most e2e tests --- tests/src/e2e/ledger_tests.rs | 54 ++++++++++++++--------------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 815d9d55a6..493e65ffa9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -58,6 +58,7 @@ fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { #[test] fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; @@ -86,13 +87,14 @@ fn run_ledger() -> Result<()> { /// 2. Submit a valid token transfer tx /// 3. Check that all the nodes processed the tx with the same result #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes let test = setup::network(|genesis| setup::add_validators(1, genesis), None)?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(1)); + // 1. Run 2 genesis validator ledger nodes and 1 non-validator node let args = ["ledger"]; let mut validator_0 = @@ -156,11 +158,11 @@ fn test_node_connectivity() -> Result<()> { /// 3. Check that the node detects this /// 4. Check that the node shuts down #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -194,11 +196,11 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { /// 5. Reset the ledger's state /// 6. Run the ledger again, it should start from fresh state #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -266,21 +268,7 @@ fn run_ledger_load_state_and_reset() -> Result<()> { fn ledger_txs_and_queries() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; - let update_config = |mut config: Config| { - // disable eth full node - config.ledger.ethereum.mode = ethereum::Mode::Off; - config - }; - - let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); - let validator_0_config = update_config(Config::load( - &validator_0_base_dir, - &test.net.chain_id, - None, - )); - validator_0_config - .write(&validator_0_base_dir, &test.net.chain_id, true) - .unwrap(); + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); // 1. Run the ledger node let mut ledger = @@ -451,11 +439,11 @@ fn ledger_txs_and_queries() -> Result<()> { /// 4. Restart the ledger /// 5. Submit and invalid transactions (malformed) #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn invalid_transactions() -> Result<()> { let test = setup::single_node_net()?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -583,8 +571,6 @@ fn invalid_transactions() -> Result<()> { /// 7. Submit a withdrawal of the self-bond /// 8. Submit a withdrawal of the delegation #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn pos_bonds() -> Result<()> { let unbonding_len = 2; let test = setup::network( @@ -609,6 +595,8 @@ fn pos_bonds() -> Result<()> { None, )?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -778,8 +766,6 @@ fn pos_bonds() -> Result<()> { /// 6. Wait for the pipeline epoch /// 7. Check the new validator's voting power #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn pos_init_validator() -> Result<()> { let pipeline_len = 1; let test = setup::network( @@ -804,6 +790,8 @@ fn pos_init_validator() -> Result<()> { None, )?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -959,8 +947,6 @@ fn pos_init_validator() -> Result<()> { /// 1. Run the ledger node with 10s consensus timeout /// 2. Spawn threads each submitting token transfer tx #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn ledger_many_txs_in_a_block() -> Result<()> { let test = Arc::new(setup::network( |genesis| genesis, @@ -968,6 +954,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { Some("10s"), )?); + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(*test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; @@ -1047,11 +1035,11 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 12. Wait proposal grace and check proposal author funds /// 13. Check governance address funds are 0 #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn proposal_submission() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + let anomac_help = vec!["--help"]; let mut client = run!(test, Bin::Client, anomac_help, Some(40))?; @@ -1402,11 +1390,11 @@ fn proposal_submission() -> Result<()> { /// 3. Create an offline vote /// 4. Tally offline #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn proposal_offline() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + // 1. Run the ledger node let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(20))?; From 5be39000c4587635f8bee8262d71d0112878a5b7 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 30 Sep 2022 16:06:29 +0200 Subject: [PATCH 0761/1995] [feat]: Debugged code to compiling. Written for tests for the merkle tree --- Cargo.lock | 63 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 5 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 622 ++++++++++++++++-- shared/src/ledger/governance/mod.rs | 3 +- shared/src/ledger/governance/parameters.rs | 2 +- shared/src/ledger/governance/utils.rs | 3 +- shared/src/ledger/governance/vp.rs | 3 +- shared/src/ledger/ibc/mod.rs | 3 +- shared/src/ledger/ibc/vp/channel.rs | 3 +- shared/src/ledger/ibc/vp/client.rs | 3 +- shared/src/ledger/ibc/vp/connection.rs | 3 +- shared/src/ledger/ibc/vp/mod.rs | 3 +- shared/src/ledger/ibc/vp/packet.rs | 3 +- shared/src/ledger/ibc/vp/port.rs | 3 +- shared/src/ledger/ibc/vp/sequence.rs | 3 +- shared/src/ledger/ibc/vp/token.rs | 3 +- shared/src/ledger/native_vp.rs | 3 +- shared/src/ledger/parameters/mod.rs | 19 +- shared/src/ledger/pos/mod.rs | 3 +- shared/src/ledger/pos/storage.rs | 3 +- shared/src/ledger/pos/vp.rs | 3 +- shared/src/ledger/storage/ics23_specs.rs | 10 +- shared/src/ledger/storage/merkle_tree.rs | 538 ++++++++------- shared/src/ledger/storage/mod.rs | 25 +- shared/src/ledger/storage/traits.rs | 198 +++--- shared/src/ledger/storage/write_log.rs | 3 +- shared/src/ledger/treasury/mod.rs | 3 +- shared/src/ledger/treasury/parameters.rs | 2 +- shared/src/ledger/vp_env.rs | 3 +- shared/src/types/eth_bridge_pool.rs | 12 +- shared/src/types/hash.rs | 4 +- shared/src/types/keccak.rs | 2 + shared/src/types/storage.rs | 24 +- shared/src/vm/host_env.rs | 6 +- shared/src/vm/wasm/host_env.rs | 3 +- shared/src/vm/wasm/run.rs | 3 +- 36 files changed, 1064 insertions(+), 531 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17d28aa12b..b4448bd3ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2868,14 +2868,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", - "ics23 0.6.7", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2895,14 +2895,14 @@ dependencies = [ [[package]] name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.14.0" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ics23 0.6.7", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", @@ -2922,44 +2922,28 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus#99a761657a51f6e5f074f3217426903e53632934" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ + "base64 0.13.0", "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=bat/abciplus)", - "tonic", ] [[package]] name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" +version = "0.17.1" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ + "base64 0.13.0", "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?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tonic", -] - -[[package]] -name = "ics23" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" -dependencies = [ - "anyhow", - "bytes 1.1.0", - "hex", - "prost 0.9.0", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std", ] [[package]] @@ -4303,11 +4287,11 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=bat/abciplus)", - "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=bat/abciplus)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ics23 0.6.7", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ics23", "itertools 0.10.3", "libsecp256k1 0.7.0", "loupe", @@ -4406,6 +4390,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "semver 1.0.14", "serde 1.0.137", "serde_bytes", "serde_json", @@ -6103,7 +6088,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.10", + "semver 1.0.14", ] [[package]] @@ -6336,9 +6321,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "semver-parser" @@ -6716,7 +6701,7 @@ dependencies = [ "blake2b-rs", "borsh", "cfg-if 1.0.0", - "ics23 0.7.0", + "ics23", "sha2 0.9.9", ] diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 12fa7982eb..b7b8cf1cf2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -18,8 +18,8 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; -use crate::ledger::storage::{DBIter, DB}; use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -192,8 +192,9 @@ mod test_bridge_pool_vp { use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; + use crate::ledger::storage::traits::Sha256Hasher; use crate::ledger::storage::write_log::WriteLog; - use crate::ledger::storage::{Sha256Hasher, Storage}; + use crate::ledger::storage::Storage; use crate::proto::Tx; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 48266bde17..8bf13ab7aa 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -2,14 +2,12 @@ //! bridge pool use std::collections::BTreeSet; use std::convert::TryInto; -use std::ops::Deref; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::eyre; -use crate::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use crate::types::address::{Address, InternalAddress}; -use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; @@ -62,7 +60,9 @@ pub fn is_protected_storage(key: &Key) -> bool { } /// A simple Merkle tree for the Ethereum bridge pool -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +#[derive( + Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, +)] pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, @@ -72,7 +72,7 @@ pub struct BridgePoolTree { impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool - pub fn new(root: KeccakHash, store: BTreeSet) -> Self { + pub fn new(root: KeccakHash, store: BTreeSet) -> Self { Self { root, store } } @@ -80,7 +80,7 @@ impl BridgePoolTree { /// /// If it is, it can be converted to a hash. /// Checks if the hash is in the tree. - pub fn has_key(&self, key: &Key) -> Result { + pub fn contains_key(&self, key: &Key) -> Result { Ok(self.store.contains(&Self::parse_key(key)?)) } @@ -88,7 +88,7 @@ impl BridgePoolTree { /// /// Returns the new root if successful. Will /// return an error if the key is malformed. - pub fn update(&mut self, key: &Key) -> Result { + pub fn update_key(&mut self, key: &Key) -> Result { let hash = Self::parse_key(key)?; _ = self.store.insert(hash); self.root = self.compute_root(); @@ -96,7 +96,7 @@ impl BridgePoolTree { } /// Delete a key from storage and update the root - pub fn delete(&mut self, key: &Key) -> Result<(), Error> { + pub fn delete_key(&mut self, key: &Key) -> Result<(), Error> { let hash = Self::parse_key(key)?; _ = self.store.remove(&hash); self.root = self.compute_root(); @@ -105,16 +105,26 @@ impl BridgePoolTree { /// Compute the root of the merkle tree pub fn compute_root(&self) -> KeccakHash { - let mut leaves = self.store.iter(); - let mut root = if let Some(hash) = leaves.next() { - hash.clone() + let mut hashes: Vec = self.store.iter().cloned().collect(); + while hashes.len() > 1 { + let mut next_hashes = vec![]; + let left_leaves = hashes.iter().step_by(2); + let mut right_leaves = hashes.iter(); + _ = right_leaves.next(); + let mut right_leaves = right_leaves.step_by(2); + + for left in left_leaves { + let right = right_leaves.next().cloned().unwrap_or_default(); + next_hashes.push(hash_pair(left.clone(), right)); + } + hashes = next_hashes; + } + + if hashes.is_empty() { + Default::default() } else { - return Default::default(); - }; - for leaf in leaves { - root = keccak_hash([root.0, leaf.0].concat()); + hashes.remove(0) } - root } /// Return the root as a [`Hash`] type. @@ -128,33 +138,87 @@ impl BridgePoolTree { } /// Create a batched membership proof for the provided keys - pub fn membership_proof( + pub fn get_membership_proof( &self, keys: &[Key], mut values: Vec, ) -> Result { if values.len() != keys.len() { - return eyre!( + return Err(eyre!( "The number of leaves and leaf hashes must be equal." - )?; + ) + .into()); } - values.sort(); - let mut leaves: std::collections::BTreeSet = - Default::default(); - for key in keys { - leaves.insert(Self::parse_key(key)?); + // sort the values according to their hash values + values.sort_by_key(|transfer| transfer.keccak256()); + + // get the leaf hashes + let mut leaves: BTreeSet = Default::default(); + for (key, value) in keys.iter().zip(values.iter()) { + let hash = Self::parse_key(key)?; + if hash != value.keccak256() { + return Err(eyre!("Hashes of keys did not match hashes of values.").into()); + } + leaves.insert(hash); } let mut proof_hashes = vec![]; let mut flags = vec![]; - for hash in self.store { - if leaves.contains(&hash) { - flags.push(true); - } else { - flags.push(false); - proof_hashes.push(hash); + let mut hashes: Vec<_> = self + .store + .iter() + .cloned() + .map(|hash| { + if leaves.contains(&hash) { + Node::OnPath(hash) + } else { + Node::Sibling(hash) + } + }) + .collect(); + + while hashes.len() > 1 { + let mut next_hashes = vec![]; + let left_leaves = hashes.iter().step_by(2); + let mut right_leaves = hashes.iter(); + _ = right_leaves.next(); + let mut right_leaves = right_leaves.step_by(2); + + for left in left_leaves { + let right = right_leaves.next().cloned().unwrap_or_default(); + match (left, right) { + (Node::OnPath(left), Node::OnPath(right)) => { + flags.push(true); + next_hashes + .push(Node::OnPath(hash_pair(left.clone(), right))); + } + (Node::OnPath(hash), Node::Sibling(sib)) => { + flags.push(false); + proof_hashes.push(sib.clone()); + next_hashes + .push(Node::OnPath(hash_pair(hash.clone(), sib))); + } + (Node::Sibling(sib), Node::OnPath(hash)) => { + flags.push(false); + proof_hashes.push(sib.clone()); + next_hashes + .push(Node::OnPath(hash_pair(hash, sib.clone()))); + } + (Node::Sibling(left), Node::Sibling(right)) => { + next_hashes.push(Node::Sibling(hash_pair( + left.clone(), + right, + ))); + } + } } + hashes = next_hashes; + } + // add the root to the proof + if proof_hashes.is_empty() { + proof_hashes.push(self.root.clone()); } + Ok(BridgePoolProof { proof: proof_hashes, leaves: values, @@ -170,22 +234,52 @@ impl BridgePoolTree { fn parse_key(key: &Key) -> Result { if key.segments.len() == 1 { match &key.segments[0] { - DbKeySeg::StringSeg(str) => str - .as_str() - .try_into() - .ok_or(eyre!("Could not parse key segment as a hash")?), - _ => { - eyre!("Bridge pool keys should be strings, not addresses")? + DbKeySeg::StringSeg(str) => { + str.as_str().try_into().map_err(|_| { + eyre!("Could not parse key segment as a hash").into() + }) } + _ => Err(eyre!( + "Bridge pool keys should be strings, not addresses" + ) + .into()), } } else { - eyre!( + Err(eyre!( "Key for the bridge pool should not have more than one segment" - )? + ) + .into()) } } } +/// Concatenate two keccak hashes and hash the result +#[inline] +fn hash_pair(left: KeccakHash, right: KeccakHash) -> KeccakHash { + if left.0 < right.0 { + keccak_hash([left.0, right.0].concat().as_slice()) + } else { + keccak_hash([right.0, left.0].concat().as_slice()) + } +} + +/// Keeps track if a node is on a path from the +/// root of the merkle tree to one of the leaves +/// being included in a multi-proof. +#[derive(Debug, Clone)] +enum Node { + /// Node is on a path from root to leaf in proof + OnPath(KeccakHash), + /// Node is not on a path from root to leaf in proof + Sibling(KeccakHash), +} + +impl Default for Node { + fn default() -> Self { + Self::Sibling(Default::default()) + } +} + /// A multi-leaf membership proof pub struct BridgePoolProof { /// The hashes other than the provided leaves @@ -199,33 +293,447 @@ pub struct BridgePoolProof { impl BridgePoolProof { /// Verify a membership proof matches the provided root pub fn verify(&self, root: KeccakHash) -> bool { - if self.proof.len() + self.leaves.len() != self.flags.len() { + if self.proof.len() + self.leaves.len() != self.flags.len() + 1 { return false; } - if self.flags.len() == 0 { - return true; + if self.flags.is_empty() { + return match self.proof.last() { + Some(proof_root) => &root == proof_root, + None => false, + }; } + let total_hashes = self.flags.len(); + let leaf_len = self.leaves.len(); + + let mut hashes = vec![KeccakHash::default(); self.flags.len()]; + let mut hash_pos = 0usize; let mut leaf_pos = 0usize; let mut proof_pos = 0usize; - let mut computed; - if self.flags[0] { - computed = self.leaves[leaf_pos].keccak256(); - leaf_pos += 1; - } else { - computed = self.proof[proof_pos].clone(); - proof_pos += 1; - } - for flag in 1..self.flages.len() { - let mut next_hash; - if self.flags[flag] { - next_hash = self.leaves[leaf_pos].keccak256(); + + for i in 0..total_hashes { + let left = if leaf_pos < leaf_len { + let next = self.leaves[leaf_pos].keccak256(); leaf_pos += 1; + next + } else { + let next = hashes[hash_pos].clone(); + hash_pos += 1; + next + }; + let right = if self.flags[i] { + if leaf_pos < leaf_len { + let next = self.leaves[leaf_pos].keccak256(); + leaf_pos += 1; + next + } else { + let next = hashes[hash_pos].clone(); + hash_pos += 1; + next + } } else { - next_hash = self.proof[proof_pos].clone(); + let next = self.proof[proof_pos].clone(); proof_pos += 1; - } - computed = keccak_hash([computed, next_hash].concat()); + next + }; + hashes[i] = hash_pair(left, right); + } + + if let Some(computed) = hashes.last() { + *computed == root + } else { + false } - computed == root } } + +#[cfg(test)] +mod test_bridge_pool_tree { + use std::array; + use super::*; + use crate::types::ethereum_events::EthAddress; + use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + + /// Test that if tree has a single leaf, its root is the hash + /// of that leaf + #[test] + fn test_update_single_key() { + let mut tree = BridgePoolTree::default(); + assert_eq!(tree.root().0, [0; 32]); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1;20]), + recipient: EthAddress([2; 20]), + amount: 1.into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + assert_eq!(root, transfer.keccak256()); + } + + #[test] + fn test_two_keys() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..2 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()).into(); + assert_eq!(tree.root(), expected); + } + + /// This is the first number of keys to use dummy leaves + #[test] + fn test_three_leaves() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..3 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + let hashes: BTreeSet = transfers.iter().map(|t| t.keccak256()).collect(); + assert_eq!(hashes, tree.store); + + let left_hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); + let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); + let expected: Hash = hash_pair(left_hash, right_hash).into(); + assert_eq!(tree.root(), expected); + } + + /// Test removing all keys + #[test] + fn test_delete_all_keys() { + let mut tree = BridgePoolTree::default(); + + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1;20]), + recipient: EthAddress([2; 20]), + amount: 1.into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + assert_eq!(root, transfer.keccak256()); + tree.delete_key(&key).expect("Test failed"); + assert_eq!(tree.root().0, [0; 32]); + } + + /// Test deleting a key + #[test] + fn test_delete_key() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..3 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + tree.delete_key(&Key::from(&transfers[1])) + .expect("Test failed"); + + let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[2].keccak256()).into(); + assert_eq!(tree.root(), expected); + } + + /// Test that parse key works correctly + #[test] + fn test_parse_key() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1; 20]), + recipient: EthAddress([2; 20]), + amount: (1 as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let expected = transfer.keccak256(); + let key = Key::from(&transfer); + assert_eq!(BridgePoolTree::parse_key(&key).expect("Test failed"), expected); + } + + /// Test that parsing a key with multiple segments fails + #[test] + fn test_key_multiple_segments() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1; 20]), + recipient: EthAddress([2; 20]), + amount: (1 as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let hash = transfer.keccak256().to_string(); + let key = Key{segments: vec![DbKeySeg::AddressSeg(bertha_address()), DbKeySeg::StringSeg(hash)]}; + assert!(BridgePoolTree::parse_key(&key).is_err()); + } + + /// Test that parsing a key that is not a hash fails + #[test] + fn test_key_not_hash() { + let key = Key{segments: vec![DbKeySeg::StringSeg("bloop".into())]}; + assert!(BridgePoolTree::parse_key(&key).is_err()); + } + + /// Test that [`contains_key`] works correctly + #[test] + fn test_contains_key() { + let mut tree = BridgePoolTree::default(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1; 20]), + recipient: EthAddress([2; 20]), + amount: (1 as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + tree.update_key(&Key::from(&transfer)).expect("Test failed"); + assert!(tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([1; 20]), + recipient: EthAddress([0; 20]), + amount: (1 as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + assert!(!tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + } + + /// Test that the empty proof works + #[test] + fn test_empty_proof() { + let tree = BridgePoolTree::default(); + let keys = vec![]; + let values = vec![]; + let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + assert!(proof.verify(Default::default())); + } + + #[test] + fn test_one_leaf_of_two_proof() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..2 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + let key = Key::from(&transfers[0]); + let proof = tree.get_membership_proof( + array::from_ref(&key), + vec![transfers.remove(0)] + ) + .expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + + /// Test that a multiproof works for leaves who are siblings + #[test] + fn test_proof_two_out_of_three_leaves() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..3 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + let keys = vec![Key::from(&transfers[0]), Key::from(&transfers[1])]; + let values = vec![transfers[0].clone(), transfers[1].clone()]; + let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + + #[test] + fn test_proof_no_leaves() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..3 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + let keys = vec![]; + let values = vec![]; + let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + assert!(proof.verify(tree.root().into())) + } + + /// Test a proof for all the leaves + #[test] + fn test_proof_all_leaves() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..3 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + let keys: Vec<_> = transfers.iter().map(Key::from).collect(); + let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + + /// Test proofs of large trees + #[test] + fn test_large_proof() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..5 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + let keys: Vec<_> = transfers + .iter() + .step_by(2) + .map(Key::from) + .collect(); + let values: Vec<_> = transfers + .iter() + .step_by(2) + .cloned() + .collect(); + let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } +} \ No newline at end of file diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 72aa7fb3ce..b565970c76 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -16,7 +16,8 @@ pub use vp::Result; use self::storage as gov_storage; use crate::ledger::native_vp::{Ctx, NativeVp}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as m1t, Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token as token_storage; diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 79c3b4d5b6..f860242a74 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -47,7 +47,7 @@ impl GovParams { pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + H: storage::traits::StorageHasher, { let Self { min_proposal_fund, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aaca277f91..e6377e4fa6 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -9,7 +9,8 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; -use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult}; use crate::types::storage::{Epoch, Key}; diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..fbd129b43c 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -6,7 +6,8 @@ use thiserror::Error; 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::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as m1t, Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; diff --git a/shared/src/ledger/ibc/mod.rs b/shared/src/ledger/ibc/mod.rs index 5fb599d979..f2a422236a 100644 --- a/shared/src/ledger/ibc/mod.rs +++ b/shared/src/ledger/ibc/mod.rs @@ -9,7 +9,8 @@ use storage::{ connection_counter_key, }; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage, Storage}; /// Initialize storage in the genesis block. pub fn init_genesis_storage(storage: &mut Storage) diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 3a43834da5..a0dafebc00 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -53,7 +53,8 @@ use crate::ibc::proofs::Proofs; use crate::ibc::timestamp::Timestamp; use crate::ledger::native_vp::Error as NativeVpError; use crate::ledger::parameters; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::tendermint::Time; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{ diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 4b89e1ce30..8b1fa8b73d 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -31,7 +31,8 @@ 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::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::{BlockHeight, Key}; diff --git a/shared/src/ledger/ibc/vp/connection.rs b/shared/src/ledger/ibc/vp/connection.rs index 2e721bed08..4037d1b02a 100644 --- a/shared/src/ledger/ibc/vp/connection.rs +++ b/shared/src/ledger/ibc/vp/connection.rs @@ -27,7 +27,8 @@ 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::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::{BlockHeight, Epoch, Key}; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 7e2baf4666..6797a61054 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -18,7 +18,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::proto::SignedTxData; use crate::types::address::{Address, InternalAddress}; use crate::types::ibc::IbcEvent as WrappedIbcEvent; diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 207727e91c..1182d4b821 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -32,7 +32,8 @@ use crate::ibc::core::ics24_host::identifier::{ }; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; -use crate::ledger::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs index da5ef4e25f..5efade84bd 100644 --- a/shared/src/ledger/ibc/vp/port.rs +++ b/shared/src/ledger/ibc/vp/port.rs @@ -16,7 +16,8 @@ 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::ibc::core::ics26_routing::context::ModuleId; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/sequence.rs b/shared/src/ledger/ibc/vp/sequence.rs index 0e751ea0de..a47bb4c4ca 100644 --- a/shared/src/ledger/ibc/vp/sequence.rs +++ b/shared/src/ledger/ibc/vp/sequence.rs @@ -8,7 +8,8 @@ use crate::ibc::core::ics04_channel::channel::Order; use crate::ibc::core::ics04_channel::context::ChannelReader; use crate::ibc::core::ics24_host::identifier::PortChannelId; use crate::ledger::ibc::handler::packet_from_message; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index f56382b360..806e26711f 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -11,7 +11,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::proto::SignedTxData; use crate::types::address::{Address, Error as AddressError, InternalAddress}; use crate::types::ibc::data::{ diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 1819cde903..594db7f8d1 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -8,8 +8,9 @@ use eyre::Context; use thiserror::Error; use crate::ledger::gas::VpGasMeter; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{Storage, StorageHasher}; +use crate::ledger::storage::Storage; use crate::ledger::{storage, vp_env}; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index fdc2a110d0..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -11,7 +11,8 @@ use super::governance::vp::is_proposal_accepted; use super::storage::types::{decode, encode}; use super::storage::{types, Storage}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::time::DurationSecs; @@ -150,7 +151,7 @@ impl Parameters { pub fn init_storage(&self, storage: &mut Storage) where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // write epoch parameters let epoch_key = storage::get_epoch_storage_key(); @@ -198,7 +199,7 @@ pub fn update_max_expected_time_per_block_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_max_expected_time_per_block_key(); update(storage, value, key) @@ -212,7 +213,7 @@ pub fn update_vp_whitelist_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_vp_whitelist_storage_key(); update(storage, &value, key) @@ -226,7 +227,7 @@ pub fn update_tx_whitelist_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_tx_whitelist_storage_key(); update(storage, &value, key) @@ -240,7 +241,7 @@ pub fn update_epoch_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_epoch_storage_key(); update(storage, value, key) @@ -255,7 +256,7 @@ pub fn update( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, T: BorshSerialize, { let serialized_value = value @@ -273,7 +274,7 @@ pub fn read_epoch_parameter( ) -> std::result::Result<(EpochDuration, u64), ReadError> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // read epoch let epoch_key = storage::get_epoch_storage_key(); @@ -293,7 +294,7 @@ pub fn read( ) -> std::result::Result<(Parameters, u64), ReadError> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // read epoch let (epoch_duration, gas_epoch) = read_epoch_parameter(storage) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index c980da81da..95a4de4ffa 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -13,7 +13,8 @@ use namada_proof_of_stake::PosBase; pub use storage::*; pub use vp::PosVP; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage, Storage}; use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::Epoch; use crate::types::{key, token}; diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index cfe1126b88..9ed2fcbecc 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -10,8 +10,9 @@ use super::{ BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, ADDRESS, }; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::types::{decode, encode}; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::types::address::Address; use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::{key, token}; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..e5cbf9198a 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -32,8 +32,9 @@ use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, }; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::types::decode; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::{key, token}; diff --git a/shared/src/ledger/storage/ics23_specs.rs b/shared/src/ledger/storage/ics23_specs.rs index e1cede8ec2..3bb1680bad 100644 --- a/shared/src/ledger/storage/ics23_specs.rs +++ b/shared/src/ledger/storage/ics23_specs.rs @@ -1,9 +1,7 @@ //! A module that contains use arse_merkle_tree::H256; -use ics23::{ - CommitmentProof, ExistenceProof, HashOp, LeafOp, LengthOp, ProofSpec, -}; +use ics23::{HashOp, LeafOp, LengthOp, ProofSpec}; use super::traits::StorageHasher; @@ -54,7 +52,7 @@ pub fn ibc_proof_specs() -> Vec { ..spec.clone() }; let base_tree_spec = ProofSpec { - leaf_spec: Some(base_leaf_spec()), + leaf_spec: Some(base_leaf_spec::()), ..spec }; vec![sub_tree_spec, base_tree_spec] @@ -64,11 +62,11 @@ pub fn ibc_proof_specs() -> Vec { pub fn proof_specs() -> Vec { let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { - leaf_spec: Some(leaf_spec::()), + leaf_spec: Some(leaf_spec::()), ..spec.clone() }; let base_tree_spec = ProofSpec { - leaf_spec: Some(base_leaf_spec::()), + leaf_spec: Some(base_leaf_spec::()), ..spec }; vec![sub_tree_spec, base_tree_spec] diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 58b05faa2f..92637476b8 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -1,37 +1,28 @@ //! The merkle tree in the storage -use std::convert::{TryFrom, TryInto}; use std::fmt; -use std::marker::PhantomData; -use std::slice::from_ref; 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, Value}; use arse_merkle_tree::{ Hash as SmtHash, Key as TreeKey, 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, Itertools}; +use ics23::{CommitmentProof, ExistenceProof, NonExistenceProof}; use prost::Message; -use sha2::{Digest, Sha256}; use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; -use super::traits::{self, Sha256Hasher, StorageHasher}; +use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; +use crate::ledger::storage::ics23_specs::ibc_leaf_spec; use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; -use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; -use crate::types::ethereum_events::KeccakHash; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::storage::{ DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, StringKey, TreeBytes, @@ -107,7 +98,7 @@ pub enum Store { /// For PoS-related data PoS(SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(BTreeSetStore), + BridgePool(BridgePoolStore), } impl Store { @@ -133,7 +124,7 @@ pub enum StoreRef<'a> { /// For PoS-related data PoS(&'a SmtStore), /// For the Ethereum bridge Pool transfers - BridgePool(&'a BTreeSetStore), + BridgePool(&'a BridgePoolStore), } impl<'a> StoreRef<'a> { @@ -287,40 +278,40 @@ impl MerkleTree { } } - fn tree(&self, store_type: &StoreType) -> &impl traits::MerkleTree { + fn tree(&self, store_type: &StoreType) -> Box { match store_type { - StoreType::Base => &self.base, - StoreType::Account => &self.account, - StoreType::Ibc => &self.ibc, - StoreType::PoS => &self.pos, - StoreType::BridgePool => &self.bridge_pool, + StoreType::Base => Box::new(&self.base), + StoreType::Account => Box::new(&self.account), + StoreType::Ibc => Box::new(&self.ibc), + StoreType::PoS => Box::new(&self.pos), + StoreType::BridgePool => Box::new(&self.bridge_pool), } } fn tree_mut( &mut self, store_type: &StoreType, - ) -> &mut impl traits::MerkleTree { + ) -> Box { match store_type { - StoreType::Base => &mut self.base, - StoreType::Account => &mut self.account, - StoreType::Ibc => &mut self.ibc, - StoreType::PoS => &mut self.pos, - StoreType::BridgePool => &mut self.bridge_pool, + StoreType::Base => Box::new(&mut self.base), + StoreType::Account => Box::new(&mut self.account), + StoreType::Ibc => Box::new(&mut self.ibc), + StoreType::PoS => Box::new(&mut self.pos), + StoreType::BridgePool => Box::new(&mut self.bridge_pool), } } - fn update_tree>( + fn update_tree( &mut self, store_type: &StoreType, key: &Key, - value: MerkleValue, + value: MerkleValue, ) -> Result<()> { - let sub_root = self.tree_mut(store_type).update(key, value)?; + let sub_root = self.tree_mut(store_type).subtree_update(key, value)?; // 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.into(), Hash::from(sub_root))?; + self.base.update(base_key.into(), sub_root)?; } Ok(()) } @@ -328,23 +319,23 @@ 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)?; - self.tree(&store_type).has_key(&sub_key) + self.tree(&store_type).subtree_has_key(&sub_key) } /// Update the tree with the given key and value - pub fn update>( + pub fn update( &mut self, key: &Key, - value: impl Into>, + value: impl Into, ) -> Result<()> { let (store_type, sub_key) = StoreType::sub_key(key)?; - self.update_tree(&store_type, sub_key.into(), value.into()) + self.update_tree(&store_type, &sub_key, 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.tree_mut(&store_type).delete(&sub_key) + self.tree_mut(&store_type).subtree_delete(&sub_key) } /// Get the root @@ -364,14 +355,16 @@ impl MerkleTree { } /// Get the existence proof from a sub-tree - pub fn get_sub_tree_existence_proof>( + pub fn get_sub_tree_existence_proof( &self, keys: &[Key], - values: Vec>, + values: Vec, ) -> Result { - let first_key = keys.iter().next().ok_or(Error::InvalidMerkleKey( - "No keys provided for existence proof.".into(), - ))?; + let first_key = keys.iter().next().ok_or_else(|| { + Error::InvalidMerkleKey( + "No keys provided for existence proof.".into(), + ) + })?; let (store_type, _) = StoreType::sub_key(first_key)?; if !keys.iter().all(|k| { if let Ok((s, _)) = StoreType::sub_key(k) { @@ -386,7 +379,8 @@ impl MerkleTree { .into(), )); } - self.tree(&store_type).membership_proof(keys, values) + self.tree(&store_type) + .subtree_membership_proof(keys, values) } /// Get the non-existence proof @@ -396,8 +390,9 @@ impl MerkleTree { return Err(Error::NonExistenceProof(store_type.to_string())); } - let key = StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; - let mut nep = self.ibc.non_membership_proof(&key)?; + let string_key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = self.ibc.non_membership_proof(&string_key)?; // Replace the values and the leaf op for the verification if let Some(ref mut nep) = nep.proof { match nep { @@ -410,19 +405,20 @@ impl MerkleTree { let ep = left.as_mut().or(right.as_mut()).expect( "A left or right existence proof should exist.", ); - ep.leaf = Some(self.ibc_leaf_spec()); + ep.leaf = Some(ibc_leaf_spec::()); } _ => unreachable!(), } } // Get a proof of the sub tree - self.get_proof(key, sub_proof) + self.get_tendermint_proof(key, nep) } /// Get the Tendermint proof with the base proof - pub fn get_tendermint_proof>( + pub fn get_tendermint_proof( &self, + key: &Key, sub_proof: CommitmentProof, ) -> Result { let mut data = vec![]; @@ -499,7 +495,7 @@ pub struct MerkleTreeStoresRead { account: (Hash, SmtStore), ibc: (Hash, AmtStore), pos: (Hash, SmtStore), - bridge_pool: (KeccakHash, BTreeSetStore), + bridge_pool: (KeccakHash, BridgePoolStore), } impl MerkleTreeStoresRead { @@ -532,7 +528,7 @@ pub struct MerkleTreeStoresWrite<'a> { account: (Hash, &'a SmtStore), ibc: (Hash, &'a AmtStore), pos: (Hash, &'a SmtStore), - bridge_pool: (Hash, &'a BTreeSetStore), + bridge_pool: (Hash, &'a BridgePoolStore), } impl<'a> MerkleTreeStoresWrite<'a> { @@ -567,228 +563,228 @@ impl From for Error { impl From for Error { fn from(error: MtError) -> Self { - Error::MerkleTree(error) + Error::MerkleTree(error.to_string()) } } -#[cfg(test)] -mod test { - use super::*; - use crate::types::storage::KeySeg; - - #[test] - fn test_crud_value() { - 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(); - - assert!(!tree.has_key(&ibc_key).unwrap()); - assert!(!tree.has_key(&pos_key).unwrap()); - - // update IBC tree - tree.update(&ibc_key, [1u8; 8]).unwrap(); - assert!(tree.has_key(&ibc_key).unwrap()); - assert!(!tree.has_key(&pos_key).unwrap()); - // update another tree - tree.update(&pos_key, [2u8; 8]).unwrap(); - assert!(tree.has_key(&pos_key).unwrap()); - - // delete a value on IBC tree - tree.delete(&ibc_key).unwrap(); - assert!(!tree.has_key(&ibc_key).unwrap()); - assert!(tree.has_key(&pos_key).unwrap()); - } - - #[test] - fn test_restore_tree() { - 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(); - - tree.update(&ibc_key, [1u8; 8]).unwrap(); - tree.update(&pos_key, [2u8; 8]).unwrap(); - - 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).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()); - assert!(restored_tree.has_key(&pos_key).unwrap()); - } - - #[test] - fn test_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.clone()).unwrap(); - let pos_val = [2u8; 8].to_vec(); - tree.update(&pos_key, pos_val).unwrap(); - - 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(), store_type.to_string()]; - let mut sub_root = ibc_val.clone(); - let mut value = ibc_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_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_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_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 nep_verification_res = ics23::verify_non_membership( - &nep_commitment_proof, - &specs[0], - &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); - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::types::storage::KeySeg; +// +// #[test] +// fn test_crud_value() { +// 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(); +// +// assert!(!tree.has_key(&ibc_key).unwrap()); +// assert!(!tree.has_key(&pos_key).unwrap()); +// +// update IBC tree +// tree.update(&ibc_key, [1u8; 8]).unwrap(); +// assert!(tree.has_key(&ibc_key).unwrap()); +// assert!(!tree.has_key(&pos_key).unwrap()); +// update another tree +// tree.update(&pos_key, [2u8; 8]).unwrap(); +// assert!(tree.has_key(&pos_key).unwrap()); +// +// delete a value on IBC tree +// tree.delete(&ibc_key).unwrap(); +// assert!(!tree.has_key(&ibc_key).unwrap()); +// assert!(tree.has_key(&pos_key).unwrap()); +// } +// +// #[test] +// fn test_restore_tree() { +// 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(); +// +// tree.update(&ibc_key, [1u8; 8]).unwrap(); +// tree.update(&pos_key, [2u8; 8]).unwrap(); +// +// 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).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()); +// assert!(restored_tree.has_key(&pos_key).unwrap()); +// } +// +// #[test] +// fn test_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.clone()).unwrap(); +// let pos_val = [2u8; 8].to_vec(); +// tree.update(&pos_key, pos_val).unwrap(); +// +// 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(), store_type.to_string()]; +// let mut sub_root = ibc_val.clone(); +// let mut value = ibc_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_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_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_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 nep_verification_res = ics23::verify_non_membership( +// &nep_commitment_proof, +// &specs[0], +// &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 74826fba3c..74bde36b0f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -9,6 +9,7 @@ pub mod types; pub mod write_log; use core::fmt::Debug; +use std::array; use thiserror::Error; @@ -20,9 +21,9 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, Sha256Hasher, - StorageHasher, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, }; +use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; @@ -30,7 +31,7 @@ use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; use crate::types::storage::TxQueue; use crate::types::storage::{ BlockHash, BlockHeight, Epoch, Epochs, Header, Key, KeySeg, - BLOCK_HASH_LENGTH, + MembershipProof, MerkleValue, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; @@ -509,15 +510,21 @@ where pub fn get_existence_proof( &self, key: &Key, - value: Vec, + value: MerkleValue, height: BlockHeight, - ) -> Result { + ) -> Result { if height >= self.get_block_height().0 { - Ok(self.block.tree.get_existence_proof(key, value)?) + Ok(self.block.tree.get_sub_tree_existence_proof( + array::from_ref(key), + vec![value], + )?) } else { match self.db.read_merkle_tree_stores(height)? { Some(stores) => Ok(MerkleTree::::new(stores) - .get_existence_proof(key, value)?), + .get_sub_tree_existence_proof( + array::from_ref(key), + vec![value], + )?), None => Err(Error::NoMerkleTree { height }), } } @@ -696,11 +703,9 @@ impl From for Error { /// Helpers for testing components that depend on storage #[cfg(any(test, feature = "testing"))] pub mod testing { - use merkle_tree::Sha256Hasher; - use super::mockdb::MockDB; use super::*; - + use crate::ledger::storage::traits::Sha256Hasher; /// Storage with a mock DB for testing pub type TestStorage = Storage; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index c558c7cbe9..907aab2ed9 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -4,7 +4,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use arse_merkle_tree::traits::{Hasher, Value}; -use arse_merkle_tree::{Hash as SmtHash, Key as TreeKey, H256}; +use arse_merkle_tree::{Key as TreeKey, H256}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof}; use sha2::{Digest, Sha256}; @@ -12,71 +12,55 @@ use sha2::{Digest, Sha256}; use super::merkle_tree::{Amt, Error, Smt}; use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; -use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::storage::{ Key, MembershipProof, MerkleValue, StringKey, TreeBytes, }; -pub trait MerkleTree { - type Error; - - fn has_key(&self, key: &Key) -> Result; - fn update>( - &mut self, - key: &Key, - value: MerkleValue, - ) -> Result; - fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; - fn membership_proof>( +/// Trait for reading from a merkle tree that is a sub-tree +/// of the global merkle tree. +pub trait SubTreeRead { + /// Check if a key is present in the sub-tree + fn subtree_has_key(&self, key: &Key) -> Result; + /// Get a membership proof for various key-value pairs + fn subtree_membership_proof( &self, keys: &[Key], - values: Vec>, - ) -> Result; + values: Vec, + ) -> Result; } -impl MerkleTree for Smt { - type Error = Error; - - fn has_key(&self, key: &Key) -> Result { - self.get(&H::hash(key.to_string()).into()) - .and(Ok(true)) - .map_error(|err| Error::MerkleTree(err.to_string())) - } - - fn update>( +/// Trait for updating a merkle tree that is a sub-tree +/// of the global merkle tree +pub trait SubTreeWrite { + /// Add a key-value pair to the sub-tree + fn subtree_update( &mut self, key: &Key, - value: MerkleValue, - ) -> Result { - let value = match value { - MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_ref()) - .map_err(|| Error::InvalidValue)?, - _ => return Err(Error::InvalidValue), - }; - self.update(H::hash(key.to_string()).into(), value) - .map(Hash::into) - .map_err(|err| Error::MerkleTree(err.to_string())) - } + value: MerkleValue, + ) -> Result; + /// Delete a key from the sub-tree + fn subtree_delete(&mut self, key: &Key) -> Result<(), Error>; +} - fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - let value = Hash::zero(); - self.update(H::hash(key.to_string()).into(), value) - .and(Ok(())) +impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { + fn subtree_has_key(&self, key: &Key) -> Result { + self.get(&H::hash(key.to_string()).into()) + .and(Ok(true)) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn membership_proof>( + fn subtree_membership_proof( &self, keys: &[Key], - mut values: Vec>, - ) -> Result { + mut values: Vec, + ) -> Result { if keys.len() != 1 || values.len() != 1 { return Err(Error::Ics23MultiLeaf); } let key: &Key = &keys[0]; let value = match values.remove(0) { - MerkleValue::Bytes(b) => b.as_ref().to_vec(), + MerkleValue::Bytes(b) => b, _ => return Err(Error::InvalidValue), }; let cp = self.membership_proof(&H::hash(key.to_string()).into())?; @@ -97,51 +81,48 @@ impl MerkleTree for Smt { } } -impl MerkleTree for Amt { - type Error = Error; - - fn has_key(&self, key: &Key) -> Result { - let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; - self.get(&key) - .and(Ok(bool)) - .map_err(|err| Error::MerkleTree(err.to_string())) - } - - fn update>( +impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { + fn subtree_update( &mut self, - key: MerkleKey, - value: MerkleValue, - ) -> Result { - let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + key: &Key, + value: MerkleValue, + ) -> Result { let value = match value { - MerkleValue::Bytes(bytes) => { - TreeBytes::from(bytes.as_ref().to_vec()) - } + MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_slice()) + .map_err(|_| Error::InvalidValue)?, _ => return Err(Error::InvalidValue), }; - self.update(key, value) - .map(Into::into) + self.update(H::hash(key.to_string()).into(), value) + .map(Hash::from) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; - let value = TreeBytes::zero(); - self.update(key, value) + fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + let value = Hash::zero(); + self.update(H::hash(key.to_string()).into(), value) .and(Ok(())) - .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + .map_err(|err| Error::MerkleTree(err.to_string())) } +} - fn membership_proof>( +impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { + fn subtree_has_key(&self, key: &Key) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + self.get(&key) + .and(Ok(true)) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn subtree_membership_proof( &self, keys: &[Key], - _: Vec>, - ) -> Result { - if keys.len() != 1 || values.len() != 1 { + _: Vec, + ) -> Result { + if keys.len() != 1 { return Err(Error::Ics23MultiLeaf); } - let key = StringKey::try_from_bytes(&keys[0].to_string().as_bytes())?; + let key = StringKey::try_from_bytes(keys[0].to_string().as_bytes())?; let cp = self.membership_proof(&key)?; // Replace the values and the leaf op for the verification match cp.proof.expect("The proof should exist") { @@ -158,37 +139,42 @@ impl MerkleTree for Amt { } } -impl MerkleTree for BridgePoolTree { - type Error = Error; - - fn has_key(&self, key: &Key) -> Result { - self.has_key(key) +impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { + fn subtree_update( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = match value { + MerkleValue::Bytes(bytes) => TreeBytes::from(bytes), + _ => return Err(Error::InvalidValue), + }; + self.update(key, value) + .map(Into::into) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn update>( - &mut self, - key: &Key, - value: MerkleValue, - ) -> Result { - if let MerkleValue::Transfer(_) = value { - self.update(key) - .map_err(|err| Error::MerkleTree(err.to_string())) - } else { - Err(Error::InvalidValue) - } + fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = TreeBytes::zero(); + self.update(key, value) + .and(Ok(())) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) } +} - fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { - self.delete(key) +impl<'a> SubTreeRead for &'a BridgePoolTree { + fn subtree_has_key(&self, key: &Key) -> Result { + self.contains_key(key) .map_err(|err| Error::MerkleTree(err.to_string())) } - fn membership_proof>( + fn subtree_membership_proof( &self, keys: &[Key], - values: Vec>, - ) -> Result { + values: Vec, + ) -> Result { let values = values .into_iter() .filter_map(|val| match val { @@ -196,12 +182,32 @@ impl MerkleTree for BridgePoolTree { _ => None, }) .collect(); - self.membership_proof(keys, values) + self.get_membership_proof(keys, values) .map(Into::into) .map_err(|err| Error::MerkleTree(err.to_string())) } } +impl<'a> SubTreeWrite for &'a mut BridgePoolTree { + fn subtree_update( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + if let MerkleValue::Transfer(_) = value { + self.update_key(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } else { + Err(Error::InvalidValue) + } + } + + fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + self.delete_key(key) + .map_err(|err| Error::MerkleTree(err.to_string())) + } +} + impl TreeKey for StringKey { type Error = Error; diff --git a/shared/src/ledger/storage/write_log.rs b/shared/src/ledger/storage/write_log.rs index 098204b707..45940493a9 100644 --- a/shared/src/ledger/storage/write_log.rs +++ b/shared/src/ledger/storage/write_log.rs @@ -6,7 +6,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use thiserror::Error; use crate::ledger; -use crate::ledger::storage::{Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::Storage; use crate::types::address::{Address, EstablishedAddressGen}; use crate::types::ibc::IbcEvent; use crate::types::storage; diff --git a/shared/src/ledger/treasury/mod.rs b/shared/src/ledger/treasury/mod.rs index 071019059b..35b4ce5022 100644 --- a/shared/src/ledger/treasury/mod.rs +++ b/shared/src/ledger/treasury/mod.rs @@ -12,7 +12,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as nam, Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; diff --git a/shared/src/ledger/treasury/parameters.rs b/shared/src/ledger/treasury/parameters.rs index 2ccb142d09..11d3b6db5a 100644 --- a/shared/src/ledger/treasury/parameters.rs +++ b/shared/src/ledger/treasury/parameters.rs @@ -35,7 +35,7 @@ impl TreasuryParams { pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + H: storage::traits::StorageHasher, { let max_proposal_fund_transfer_key = treasury_storage::get_max_transferable_fund_key(); diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 1f59613e54..aafd4b135a 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -8,8 +8,9 @@ use thiserror::Error; use super::gas::MIN_STORAGE_GAS; use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; +use crate::ledger::storage::{self, write_log, Storage}; use crate::proto::Tx; use crate::types::hash::Hash; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 19c0f805cd..e7c55df8d8 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -4,8 +4,10 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; use crate::types::address::Address; -use crate::types::ethereum_events::{EthAddress, KeccakHash, Uint}; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -59,11 +61,17 @@ impl keccak::encode::Encode for PendingTransfer { let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); - let nonce = Token::Uint(self.transfer.nonce.into()); + let nonce = Token::Uint(self.transfer.nonce.clone().into()); vec![from, fee, to, amount, nonce] } } +impl From<&PendingTransfer> for Key { + fn from(transfer: &PendingTransfer) -> Self { + Key{segments: vec![DbKeySeg::StringSeg(transfer.keccak256().to_string())]} + } +} + /// The amount of NAM to be payed to the relayer of /// a transfer across the Ethereum Bridge to compensate /// for Ethereum gas fees. diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index f2a84f19eb..5a6abd146c 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -95,7 +95,7 @@ impl TryFrom for Hash { fn try_from(string: String) -> HashResult { let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; - Self::try_from(&bytes) + Self::try_from(bytes.as_slice()) } } @@ -105,7 +105,7 @@ impl TryFrom<&str> for Hash { fn try_from(string: &str) -> HashResult { let bytes: Vec = Vec::from_hex(string).map_err(Error::FromStringError)?; - Self::try_from(&bytes) + Self::try_from(bytes.as_slice()) } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 06beb3adf1..a390bff3ce 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -7,6 +7,7 @@ use std::fmt::Display; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; use thiserror::Error; +use tiny_keccak::{Hasher, Keccak}; use crate::types::hash::{Hash, HASH_LENGTH}; @@ -26,6 +27,7 @@ pub enum TryFromError { #[derive( Clone, Debug, + Default, PartialEq, Eq, PartialOrd, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index e13aa14dc5..db7cada8ec 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -2,7 +2,6 @@ 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, Div, Mul, Rem, Sub}; use std::str::FromStr; @@ -17,9 +16,9 @@ use thiserror::Error; use super::transaction::WrapperTx; use crate::bytes::ByteBuf; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; -use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; +use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; -use crate::types::eth_bridge_pool::{PendingTransfer, TransferToEthereum}; +use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -245,23 +244,23 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. -pub enum MerkleValue> { +pub enum MerkleValue { /// raw bytes - Bytes(T), + Bytes(Vec), /// A transfer to be put in the Ethereum bridge pool. Transfer(PendingTransfer), } -impl From for MerkleValue +impl From for MerkleValue where T: AsRef<[u8]>, { fn from(bytes: T) -> Self { - Self::Bytes(bytes) + Self::Bytes(bytes.as_ref().to_owned()) } } -impl> From for MerkleValue { +impl From for MerkleValue { fn from(transfer: PendingTransfer) -> Self { Self::Transfer(transfer) } @@ -354,8 +353,8 @@ pub enum MembershipProof { BridgePool(BridgePoolProof), } -impl From for MembershipProof { - fn from(proof: CommitmenProof) -> Self { +impl From for MembershipProof { + fn from(proof: CommitmentProof) -> Self { Self::ICS23(proof) } } @@ -627,8 +626,9 @@ impl KeySeg for Address { impl KeySeg for Hash { fn parse(seg: String) -> Result { - seg.try_into() - .map_error(Error::ParseError((seg, "Hash".into()))) + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) } fn raw(&self) -> String { diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ac9f89c31e..88bd81b918 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -13,8 +13,9 @@ use super::wasm::TxCache; use super::wasm::VpCache; use super::WasmCacheAccess; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::{self, WriteLog}; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::ledger::vp_env; use crate::proto::Tx; use crate::types::address::{self, Address}; @@ -1759,7 +1760,8 @@ pub mod testing { use std::collections::BTreeSet; use super::*; - use crate::ledger::storage::{self, StorageHasher}; + use crate::ledger::storage::traits::StorageHasher; + use crate::ledger::storage::{self}; use crate::vm::memory::testing::NativeMemory; /// Setup a transaction environment diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 3b6715f383..3736c8a295 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -8,7 +8,8 @@ use wasmer::{ WasmerEnv, }; -use crate::ledger::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::vm::host_env::{TxEnv, VpEnv, VpEvaluator}; use crate::vm::wasm::memory::WasmMemory; use crate::vm::{host_env, WasmCacheAccess}; diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 75fbfb4add..dbfdb4c961 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -11,8 +11,9 @@ use wasmer::BaseTunables; use super::memory::{Limit, WasmMemory}; use super::TxCache; use crate::ledger::gas::{BlockGasMeter, VpGasMeter}; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::proto::Tx; use crate::types::address::Address; use crate::types::internal::HostEnvResult; From a9c41e5dbc2465f434613d4ffb7711c7535f2263 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 30 Sep 2022 16:37:19 +0100 Subject: [PATCH 0762/1995] Update to chrono 0.4.22 and turn off default features This is to avoid pulling in wasm-bindgen --- Cargo.lock | 38 ++++++++++++--- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- wasm/tx_template/Cargo.lock | 62 +++++++++++++++--------- wasm/vp_template/Cargo.lock | 62 +++++++++++++++--------- wasm/wasm_source/Cargo.lock | 34 +++---------- wasm_for_tests/wasm_source/Cargo.lock | 70 +++++++++++++++------------ 7 files changed, 158 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4448bd3ea..982ca7f95c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -1120,14 +1129,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits 0.2.15", "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -2114,9 +2125,9 @@ dependencies = [ [[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", @@ -2866,6 +2877,19 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi 0.3.9", +] + [[package]] name = "ibc" version = "0.14.0" @@ -3285,9 +3309,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 08df6d1949..b938707ea7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -73,7 +73,7 @@ ark-serialize = "0.3" 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" +chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4ae17e8fff..07a6df65ff 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,7 +14,7 @@ wasm-runtime = ["namada/wasm-runtime"] [dependencies] namada = {path = "../shared", features = ["testing", "ibc-mocks"]} namada_vm_env = {path = "../vm_env"} -chrono = "0.4.19" +chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} concat-idents = "1.1.2" prost = "0.9.0" serde_json = {version = "1.0.65"} diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 6415ecf977..995a8c3685 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.55" @@ -374,14 +383,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", "num-integer", "num-traits", - "time 0.1.44", "winapi", ] @@ -406,6 +414,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -1040,6 +1054,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.14.0" @@ -1063,7 +1090,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time", "tracing", ] @@ -1221,9 +1248,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libloading" @@ -2511,7 +2538,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time", "zeroize", ] @@ -2538,7 +2565,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time", ] [[package]] @@ -2555,7 +2582,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time", ] [[package]] @@ -2576,7 +2603,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time", "url", "uuid", "walkdir", @@ -2594,7 +2621,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time", ] [[package]] @@ -2637,17 +2664,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "time" version = "0.3.7" diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3f0160f2e8..7732bae554 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.55" @@ -374,14 +383,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", "num-integer", "num-traits", - "time 0.1.44", "winapi", ] @@ -406,6 +414,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -1040,6 +1054,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.14.0" @@ -1063,7 +1090,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time", "tracing", ] @@ -1221,9 +1248,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libloading" @@ -2511,7 +2538,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time", "zeroize", ] @@ -2538,7 +2565,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time", ] [[package]] @@ -2555,7 +2582,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time", ] [[package]] @@ -2576,7 +2603,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time", "url", "uuid", "walkdir", @@ -2594,7 +2621,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time", ] [[package]] @@ -2637,17 +2664,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "time" version = "0.3.7" diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index a156f2291b..da25147eda 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -388,11 +388,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", - "js-sys", "num-integer", "num-traits", - "time 0.1.44", - "wasm-bindgen", "winapi", ] @@ -983,7 +980,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1102,7 +1099,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.14", + "time", "tracing", ] @@ -2562,7 +2559,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.14", + "time", "zeroize", ] @@ -2589,7 +2586,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.14", + "time", ] [[package]] @@ -2606,7 +2603,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.14", + "time", ] [[package]] @@ -2627,7 +2624,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.14", + "time", "url", "uuid", "walkdir", @@ -2645,7 +2642,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.14", + "time", ] [[package]] @@ -2688,17 +2685,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.14" @@ -2915,12 +2901,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -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" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 7521fbccca..4556ab8587 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.56" @@ -374,14 +383,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", "num-integer", "num-traits", - "time 0.1.44", "winapi", ] @@ -406,6 +414,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.2" @@ -968,7 +982,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1050,6 +1064,19 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.14.0" @@ -1073,7 +1100,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", + "time", "tracing", ] @@ -1231,9 +1258,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libloading" @@ -2542,7 +2569,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time", "zeroize", ] @@ -2569,7 +2596,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time", ] [[package]] @@ -2586,7 +2613,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time", ] [[package]] @@ -2607,7 +2634,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time", "url", "uuid", "walkdir", @@ -2625,7 +2652,7 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.9", + "time", ] [[package]] @@ -2668,17 +2695,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.9" @@ -2897,12 +2913,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -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" From ea7761ff966d8d637f3ccd9c4966df6a13b459c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Oct 2022 08:30:40 +0000 Subject: [PATCH 0763/1995] [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 79da26f124..49455e617b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.c5f0ae1fa204d07620e2102632b23306fbeb44ee327f672a370d322f93bd1b9a.wasm", - "tx_ibc.wasm": "tx_ibc.401ab4f167b431e254a32cc09f72379224237e2ae90dffece49e4da97efbfd4c.wasm", - "tx_init_account.wasm": "tx_init_account.9ac1106e47bcacd26647bc939c231d16a8f804f0a4165034923abe5d483673d5.wasm", - "tx_init_nft.wasm": "tx_init_nft.b27740719a3f8bf0de74b6158c659377cf8ecdb6c98c18bfe8dac8343043d815.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3d929b91b9860b69f80c812ed88e04cdf201e87016189065d39645babc2e0934.wasm", - "tx_init_validator.wasm": "tx_init_validator.2d90ebc82e8907a883e0c3ac932a54085823702628efe0822c000965b59b55cb.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.8e085a930024ddd772e087f87a2f7d1d307e134e1181f4f7cf396d77999d8f5c.wasm", - "tx_transfer.wasm": "tx_transfer.454172df7da0d31a6231a031279d54cff6735538c116435ff18bab0fd8a9d8e2.wasm", - "tx_unbond.wasm": "tx_unbond.36ffd754fb6790a8b6f4f2666eb8fdd3ba23a0b6b88b31142d3e294281d70ef2.wasm", - "tx_update_vp.wasm": "tx_update_vp.f5a0f3b46f1c28ed4e26eb8b9c31ab5aa62458bc16490fd37a960757e3a2928c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9bb61c5f4610405a573e233a276df4a8c740ce8689f50c58fc94d460421480ef.wasm", - "tx_withdraw.wasm": "tx_withdraw.4741bb87e8d88ff04589d7e84c7e316d98c35cc5f3759d61e1c31df1b539977f.wasm", - "vp_nft.wasm": "vp_nft.98f7a5915aa9790346827479377aea0cbe2db87ceef925b21db847a2f14e8b73.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.006a91027a36ae62a84a3279fc8f4a2138238c104a1c6b3ccb68e5b8b461ea32.wasm", - "vp_token.wasm": "vp_token.7e48d02781760550f5e8102916acf08a81990dc48cca10acf81bce3269bbc74d.wasm", - "vp_user.wasm": "vp_user.6e896c3d01f13e6a1d20c39b03289ff045835f2ba3f8c06974bcd4b8ebc051d6.wasm" + "tx_from_intent.wasm": "tx_from_intent.aacf3881273c1d322d9f67a2fee9cf9b5cab3661969b2adc388727a9ad7564f5.wasm", + "tx_ibc.wasm": "tx_ibc.66695a390c04405759d1128612556a0d8ce85ff3c6adf50546fcd991eaee6f41.wasm", + "tx_init_account.wasm": "tx_init_account.afc109717426c966943e0bf8f8d5f064908552335c109a4b0d81a72e7faa5113.wasm", + "tx_init_nft.wasm": "tx_init_nft.5f828560e5dd7be8a9bc25e5cfe5d258ea444ae3d097954398892562432ee0d6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2ce224b475520e48e19ba1dc70f0e01b8219c927fc9b60b7ab231fa833d09ab2.wasm", + "tx_init_validator.wasm": "tx_init_validator.4e84084907a3029d79833560b42c3ab69cf38020dc82c0a2f1ba323cbf372c6f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.788f29b1fb84835e2a905630dee2618be297bd53c68fcacf1c12fff81399dbc9.wasm", + "tx_transfer.wasm": "tx_transfer.7c84e04d3d636306bf09490744cdf4229ecc4954b10cffcff7d4beddd5f27c64.wasm", + "tx_unbond.wasm": "tx_unbond.6393cd64250c22ed82f32cfe9bde743d7ac57a9937e5ce0fc0ec8902301361f3.wasm", + "tx_update_vp.wasm": "tx_update_vp.e03daa135e498a83aabbd17109867ef138daa5e73a7d64904cb71da01c55ee06.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1cea6c13a9b78547363463e2c72c88b2cd7052e1d0da7c7bc67d6dffdbeafea2.wasm", + "tx_withdraw.wasm": "tx_withdraw.03da515f86aa2caeef33b81988f64675aef46257242cc5f42d73b986d04b94cf.wasm", + "vp_nft.wasm": "vp_nft.84395d98e82714782af42e7246bb000666ce29aa30e1952a97a87a3244fd7114.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ca4a51feebdb0313a8a87d6defd38679870e2a1be5d629979db9b11a0c60397.wasm", + "vp_token.wasm": "vp_token.227ccbdbd7401613bcd31e123e2021c12c4ed0f7ee746ba2f0296b9e18436eb0.wasm", + "vp_user.wasm": "vp_user.a174ba668bb9d868509632bed424aef09ddd016faf072f1cf5c2fcef1a468bd0.wasm" } \ No newline at end of file From 7bfbedaf8504f8ca5d1e65a70a069a96b123eb92 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 3 Oct 2022 09:36:35 +0100 Subject: [PATCH 0764/1995] Fix cargo doc --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index afbb707847..06ff3ff000 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -267,7 +267,7 @@ pub fn deserialize_vote_extensions( }) } -/// Given a `Vec` of [`ExtendedVoteInfo`], return an iterator over the +/// Given a slice of [`TxBytes`], return an iterator over the /// ones we could deserialize to [`VoteExtension`] /// instances. #[cfg(not(feature = "abcipp"))] From fcbe462ce79caf74d904a59ac27f17f0eef36c1c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 3 Oct 2022 09:47:46 +0100 Subject: [PATCH 0765/1995] Remove unnecessary move --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 11172d5865..99de036046 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -167,7 +167,7 @@ where VoteExtensionError, >, > + 'iter { - vote_extensions.into_iter().map(move |vote_extension| { + vote_extensions.into_iter().map(|vote_extension| { self.validate_eth_events_vext_and_get_it_back( vote_extension, self.storage.last_height, From 3543b63bbb0d099bf0d5a37f6715853ef0960ec5 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 11:51:39 +0200 Subject: [PATCH 0766/1995] [feat]: Added proptests to the bridge tree proofs and corrected the bugs it found --- .../ledger/eth_bridge/storage/bridge_pool.rs | 140 +++++++++++++++++- 1 file changed, 135 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 8bf13ab7aa..c09bdcb4e8 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -215,7 +215,7 @@ impl BridgePoolTree { hashes = next_hashes; } // add the root to the proof - if proof_hashes.is_empty() { + if flags.is_empty() && proof_hashes.is_empty() && leaves.is_empty() { proof_hashes.push(self.root.clone()); } @@ -297,9 +297,13 @@ impl BridgePoolProof { return false; } if self.flags.is_empty() { - return match self.proof.last() { - Some(proof_root) => &root == proof_root, - None => false, + return if let Some(leaf) = self.leaves.last() { + root == leaf.keccak256() + } else { + match self.proof.last() { + Some(proof_root) => &root == proof_root, + None => false, + } }; } let total_hashes = self.flags.len(); @@ -349,6 +353,10 @@ impl BridgePoolProof { #[cfg(test)] mod test_bridge_pool_tree { use std::array; + + use itertools::Itertools; + use proptest::prelude::*; + use super::*; use crate::types::ethereum_events::EthAddress; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; @@ -574,7 +582,7 @@ mod test_bridge_pool_tree { assert!(!tree.contains_key(&Key::from(&transfer)).expect("Test failed")); } - /// Test that the empty proof works + /// Test that the empty proof works. #[test] fn test_empty_proof() { let tree = BridgePoolTree::default(); @@ -584,6 +592,30 @@ mod test_bridge_pool_tree { assert!(proof.verify(Default::default())); } + /// Test that the proof works for proving the only leaf in the tree + #[test] + fn test_single_leaf() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into() + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address() + } + }; + let mut tree = BridgePoolTree::default(); + let key = Key::from(&transfer); + let _ = tree.update_key(&key).expect("Test failed"); + let proof = tree.get_membership_proof(array::from_ref(&key), vec![transfer]).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + + /// Check proofs for membership of single transfer + /// in a tree with two leaves. #[test] fn test_one_leaf_of_two_proof() { let mut tree = BridgePoolTree::default(); @@ -645,6 +677,7 @@ mod test_bridge_pool_tree { assert!(proof.verify(tree.root().into())); } + /// Test that proving an empty subset of leaves always works #[test] fn test_proof_no_leaves() { let mut tree = BridgePoolTree::default(); @@ -675,6 +708,34 @@ mod test_bridge_pool_tree { /// Test a proof for all the leaves #[test] fn test_proof_all_leaves() { + let mut tree = BridgePoolTree::default(); + let mut transfers = vec![]; + for i in 0..2 { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([i;20]), + recipient: EthAddress([i+1; 20]), + amount: (i as u64).into(), + nonce: 42u64.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + } + }; + let key = Key::from(&transfer); + transfers.push(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + transfers.sort_by_key(|t| t.keccak256()); + let keys: Vec<_> = transfers.iter().map(Key::from).collect(); + let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + + /// Test a proof for all the leaves when the number of leaves is odd + #[test] + fn test_proof_all_leaves_odd() { let mut tree = BridgePoolTree::default(); let mut transfers = vec![]; for i in 0..3 { @@ -736,4 +797,73 @@ mod test_bridge_pool_tree { let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); assert!(proof.verify(tree.root().into())); } + + /// Create a random set of transfers. + fn random_transfers(number: usize) -> impl Strategy> { + prop::collection::vec( + ( + prop::array::uniform20(0u8..), + prop::num::u64::ANY, + ), + 0..=number, + ) + .prop_flat_map( | addrs | + Just( + addrs.into_iter().map(| (addr, nonce)| + PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress(addr.clone()), + recipient: EthAddress(addr), + amount: Default::default(), + nonce: nonce.into() + }, + gas_fee: GasFee { + amount: Default::default(), + payer: bertha_address() + } + }, + ) + .dedup() + .collect::>() + ) + ) + } + + prop_compose! { + /// Creates a random set of transfers and + /// then returns them along with a chosen subset. + fn arb_transfers_and_subset() + (transfers in random_transfers(50)) + ( + transfers in Just(transfers.clone()), + to_prove in proptest::sample::subsequence(transfers.clone(), 0..=transfers.len()), + ) + -> (Vec, Vec) { + (transfers, to_prove) + } + } + + proptest!{ + /// Given a random tree and a subset of leaves, + /// verify that the constructed multi-proof correctly + /// verifies. + #[test] + fn test_verify_proof((transfers, mut to_prove) in arb_transfers_and_subset()) { + let mut tree = BridgePoolTree::default(); + for transfer in &transfers { + let key = Key::from(transfer); + let _ = tree.update_key(&key).expect("Test failed"); + } + + to_prove.sort_by_key(|t| t.keccak256()); + let mut keys = vec![]; + let mut values = vec![]; + for transfer in to_prove.into_iter() { + keys.push(Key::from(&transfer)); + values.push(transfer); + } + let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + assert!(proof.verify(tree.root().into())); + } + } } \ No newline at end of file From 1116073dc12df19af46715ff72f5fdf30f29e782 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 30 Sep 2022 13:25:14 +0100 Subject: [PATCH 0767/1995] Patch tendermint-related crates at the workspace level --- Cargo.lock | 131 ++++++++------- Cargo.toml | 15 ++ apps/Cargo.toml | 10 +- shared/Cargo.toml | 8 +- wasm/tx_template/Cargo.lock | 219 ++------------------------ wasm/tx_template/Cargo.toml | 4 + wasm/vp_template/Cargo.lock | 215 ++----------------------- wasm/vp_template/Cargo.toml | 10 ++ wasm/wasm_source/Cargo.lock | 206 ++---------------------- wasm/wasm_source/Cargo.toml | 10 ++ wasm_for_tests/wasm_source/Cargo.lock | 215 ++----------------------- wasm_for_tests/wasm_source/Cargo.toml | 10 ++ 12 files changed, 167 insertions(+), 886 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 982ca7f95c..f848f69185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2893,12 +2893,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2909,10 +2909,10 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time 0.3.9", "tracing 0.1.35", ] @@ -2936,10 +2936,10 @@ 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 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5", + "tendermint-testgen 0.23.5", "time 0.3.9", "tracing 0.1.35", ] @@ -2947,14 +2947,14 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "base64 0.13.0", "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=bat/abciplus)", + "tendermint-proto 0.23.6", ] [[package]] @@ -2967,7 +2967,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 0.23.5", ] [[package]] @@ -4311,9 +4311,9 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "itertools 0.10.3", @@ -4335,10 +4335,10 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=bat/abciplus)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint 0.23.5", + "tendermint 0.23.6", + "tendermint-proto 0.23.5", + "tendermint-proto 0.23.6", "test-log", "thiserror", "tiny-keccak", @@ -4425,14 +4425,14 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "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=bat/abciplus)", - "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=bat/abciplus)", - "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=bat/abciplus)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint 0.23.5", + "tendermint 0.23.6", + "tendermint-config 0.23.5", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.5", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.5", + "tendermint-rpc 0.23.6", "test-log", "thiserror", "tokio", @@ -4441,8 +4441,8 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", "tracing 0.1.35", "tracing-log", "tracing-subscriber 0.3.11", @@ -6911,7 +6911,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6931,15 +6931,15 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5", "time 0.3.9", "zeroize", ] [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6959,7 +6959,7 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto 0.23.6", "time 0.3.9", "zeroize", ] @@ -6967,25 +6967,25 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde 1.0.137", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5", "toml", "url 2.2.2", ] [[package]] name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "flex-error", "serde 1.0.137", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint 0.23.6", "toml", "url 2.2.2", ] @@ -6993,33 +6993,32 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", "serde 1.0.137", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5", + "tendermint-rpc 0.23.5", "time 0.3.9", ] [[package]] name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" 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)", + "tendermint 0.23.6", "time 0.3.9", ] [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes 1.1.0", "flex-error", @@ -7035,8 +7034,8 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "bytes 1.1.0", "flex-error", @@ -7053,7 +7052,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "async-tungstenite", @@ -7071,9 +7070,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5", + "tendermint-config 0.23.5", + "tendermint-proto 0.23.5", "thiserror", "time 0.3.9", "tokio", @@ -7085,8 +7084,8 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "async-tungstenite", @@ -7104,9 +7103,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 0.23.6", + "tendermint-config 0.23.6", + "tendermint-proto 0.23.6", "thiserror", "time 0.3.9", "tokio", @@ -7119,7 +7118,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7127,14 +7126,14 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint 0.23.5", "time 0.3.9", ] [[package]] name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "ed25519-dalek", "gumdrop", @@ -7142,7 +7141,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint 0.23.6", "time 0.3.9", ] @@ -7619,13 +7618,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=bat/abciplus#21623a99bdca5b006d53752a1967849bef3b89ea" +source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ "bytes 1.1.0", "futures 0.3.21", "pin-project 1.0.10", "prost 0.9.0", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus)", + "tendermint-proto 0.23.5", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7637,13 +7636,13 @@ dependencies = [ [[package]] name = "tower-abci" version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" +source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" 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?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto 0.23.6", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..ccbb2c35de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,21 @@ borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git # borsh-derive-internal = {path = "../borsh-rs/borsh-derive-internal"} # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} + +# patched to a commit on the `eth-bridge-integration` branch of our fork +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} + +# patched to a commit on the `eth-bridge-integration` branch of our fork +tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "fcc0014d0bda707109901abfa1b2f782d242f082"} + [profile.release] lto = true opt-level = 3 diff --git a/apps/Cargo.toml b/apps/Cargo.toml index c68e53f828..cd7a0f638f 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -129,10 +129,10 @@ tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev tendermint-config-abcipp = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"], optional = true} -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", features = ["http-client", "websocket-client"], optional = true} +tendermint = {version = "0.23.6", optional = true} +tendermint-config = {version = "0.23.6", optional = true} +tendermint-proto = {version = "0.23.6", optional = true} +tendermint-rpc = {version = "0.23.6", features = ["http-client", "websocket-client"], optional = true} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -141,7 +141,7 @@ 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-abcipp = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200", optional = true} -tower-abci = {git = "https://github.com/heliaxdev/tower-abci", branch = "bat/abciplus", optional = true} +tower-abci = {version = "0.1.0", optional = true} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b938707ea7..54c6e158ca 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -88,8 +88,8 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} -ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "6255c4e01db11781e5a8cdb89737f55a8ad81d63", default-features = false, optional = true} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "6255c4e01db11781e5a8cdb89737f55a8ad81d63", default-features = false, optional = true} +ibc = {version = "0.14.0", default-features = false, optional = true} +ibc-proto = {version = "0.17.1", default-features = false, optional = true} ics23 = "0.7.0" itertools = "0.10.3" loupe = {version = "0.1.3", optional = true} @@ -112,8 +112,8 @@ tempfile = {version = "3.2.0", optional = true} # temporarily using fork work-around for https://github.com/informalsystems/tendermint-rs/issues/971 tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "bat/abciplus", optional = true} +tendermint = {version = "0.23.6", optional = true} +tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" tiny-keccak = "2.0.2" tracing = "0.1.30" diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 995a8c3685..a6b77ec9c6 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -886,16 +886,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - [[package]] name = "funty" version = "2.0.0" @@ -1070,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "base64", "bytes", @@ -1129,17 +1119,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1352,12 +1331,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.4.1" @@ -1659,39 +1632,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - [[package]] name = "pest" version = "2.1.3" @@ -1711,26 +1651,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.8" @@ -2246,15 +2166,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -2516,8 +2427,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a3d617db287955b07e4bf1523b831bf5055e7d3ceab8504174181df40cb143" dependencies = [ "async-trait", "bytes", @@ -2542,36 +2454,24 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml", - "url", -] - [[package]] name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26787f3d9a6dc1c5e1e5661d7d70bb53613852b01545730df082128fd98e6529" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", - "tendermint-rpc", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a65da26cc1f24cd53f40d1267372c22d6ce727d9fd40c6c61b879b26154994" dependencies = [ "bytes", "flex-error", @@ -2585,34 +2485,11 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "getrandom", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "url", - "uuid", - "walkdir", -] - [[package]] name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1514e269ebd2dd1f99e931ac99d96eb9b521a0820d9f03e9b56ac724ca456f19" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2690,21 +2567,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "toml" version = "0.5.8" @@ -2810,21 +2672,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2843,24 +2690,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "valuable" version = "0.1.0" @@ -2882,17 +2711,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -3248,15 +3066,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 25f95edea7..53e83553cc 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -25,6 +25,10 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} + [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) lto = true diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 7732bae554..b9dcdc3d5d 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -886,16 +886,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - [[package]] name = "funty" version = "2.0.0" @@ -1070,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "base64", "bytes", @@ -1129,17 +1119,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1352,12 +1331,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.4.1" @@ -1659,39 +1632,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - [[package]] name = "pest" version = "2.1.3" @@ -1711,26 +1651,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.8" @@ -2246,15 +2166,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -2516,8 +2427,8 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "bytes", @@ -2542,36 +2453,22 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml", - "url", -] - [[package]] name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", - "tendermint-rpc", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "bytes", "flex-error", @@ -2585,34 +2482,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "getrandom", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "url", - "uuid", - "walkdir", -] - [[package]] name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2690,21 +2563,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "toml" version = "0.5.8" @@ -2799,21 +2657,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2832,24 +2675,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "valuable" version = "0.1.0" @@ -2882,17 +2707,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -3248,15 +3062,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 6819729320..e2eadbd977 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -25,6 +25,16 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} + +# patched to a commit on the `eth-bridge-integration` branch of our fork +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} + [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) lto = true diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index da25147eda..3f0a675b4d 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -886,15 +886,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" -dependencies = [ - "percent-encoding", -] - [[package]] name = "funty" version = "2.0.0" @@ -1079,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "bytes", "derive_more", @@ -1106,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "base64", "bytes", @@ -1138,16 +1129,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1686,39 +1667,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - [[package]] name = "pest" version = "2.3.0" @@ -1739,26 +1687,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2273,15 +2201,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -2537,8 +2456,8 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "bytes", @@ -2563,36 +2482,22 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml", - "url", -] - [[package]] name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", - "tendermint-rpc", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "bytes", "flex-error", @@ -2606,34 +2511,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "getrandom", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "url", - "uuid", - "walkdir", -] - [[package]] name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2711,21 +2592,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "toml" version = "0.5.9" @@ -2819,27 +2685,12 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -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.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2858,23 +2709,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" -[[package]] -name = "url" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "version_check" version = "0.9.4" @@ -2890,17 +2724,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3266,15 +3089,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 12fe3c72be..ffd9730962 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -57,6 +57,16 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} + +# patched to a commit on the `eth-bridge-integration` branch of our fork +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} + [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) lto = true diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4556ab8587..e31959dbef 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -887,16 +887,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - [[package]] name = "funty" version = "2.0.0" @@ -1080,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "bytes", "derive_more", @@ -1107,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" dependencies = [ "base64", "bytes", @@ -1139,17 +1129,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -1362,12 +1341,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - [[package]] name = "memchr" version = "2.4.1" @@ -1690,39 +1663,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" -[[package]] -name = "peg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2", - "quote", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - [[package]] name = "pest" version = "2.1.3" @@ -1742,26 +1682,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.8" @@ -2277,15 +2197,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -2547,8 +2458,8 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "bytes", @@ -2573,36 +2484,22 @@ dependencies = [ "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "flex-error", - "serde", - "serde_json", - "tendermint", - "toml", - "url", -] - [[package]] name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "derive_more", "flex-error", "serde", "tendermint", - "tendermint-rpc", "time", ] [[package]] name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "bytes", "flex-error", @@ -2616,34 +2513,10 @@ dependencies = [ "time", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" -dependencies = [ - "bytes", - "flex-error", - "getrandom", - "peg", - "pin-project", - "serde", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint", - "tendermint-config", - "tendermint-proto", - "thiserror", - "time", - "url", - "uuid", - "walkdir", -] - [[package]] name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" +version = "0.23.6" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "ed25519-dalek", "gumdrop", @@ -2721,21 +2594,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "toml" version = "0.5.8" @@ -2830,21 +2688,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2863,24 +2706,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - [[package]] name = "valuable" version = "0.1.0" @@ -2902,17 +2727,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3268,15 +3082,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index d4df7a2e5c..1033271a6d 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -39,6 +39,16 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} + +# patched to a commit on the `eth-bridge-integration` branch of our fork +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} + [dev-dependencies] namada_tests = {path = "../../tests"} From 1a62c47896d1c3daba4147c30c810167a10d9d2b Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 13:00:27 +0200 Subject: [PATCH 0768/1995] [fix]: fixed the confirmations calculation for ethereum events --- apps/src/lib/node/ledger/ethereum_node/events.rs | 2 +- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 0d176b875f..4071d6a0f0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -202,7 +202,7 @@ pub mod eth_events { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations >= height.clone() - self.block_height.clone() + self.confirmations <= height.clone() - self.block_height.clone() } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 28fd09ccb5..d73537157c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -244,7 +244,7 @@ mod test_oracle { abort_recv: Receiver<()>, } - /// Set up an oracle with a mock web3 client that we can contr + /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); @@ -474,7 +474,7 @@ mod test_oracle { // increase block height so first event is confirmed but second is // not. admin_channel - .send(TestCmd::NewHeight(Uint256::from(102u32))) + .send(TestCmd::NewHeight(Uint256::from(105u32))) .expect("Test failed"); // check the correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); From a7e638abb8055e303ff47895f0ed512c4890591e Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 13:06:06 +0200 Subject: [PATCH 0769/1995] Testing From b22c8fb44e59d80df99e021057dd977e0d23b9c4 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 13:10:09 +0200 Subject: [PATCH 0770/1995] Testing From f76e941ad93fef5bb008d8a08d9079baa3d5146e Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 13:16:22 +0200 Subject: [PATCH 0771/1995] testing From aec09f41a53283347e1a2cf6ebaef29bc4bb53d0 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 13:18:56 +0200 Subject: [PATCH 0772/1995] testing From 74a8115f8cc950402b0410233a958b4a2893e51f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 12:56:11 +0100 Subject: [PATCH 0773/1995] With tiago/ethbridge --- Cargo.lock | 22 +++++++++++----------- Cargo.toml | 16 ++++++++-------- wasm/tx_template/Cargo.lock | 4 ++-- wasm/tx_template/Cargo.toml | 4 ++-- wasm/vp_template/Cargo.lock | 12 ++++++------ wasm/vp_template/Cargo.toml | 12 ++++++------ wasm/wasm_source/Cargo.lock | 12 ++++++------ wasm/wasm_source/Cargo.toml | 12 ++++++------ wasm_for_tests/wasm_source/Cargo.lock | 12 ++++++------ wasm_for_tests/wasm_source/Cargo.toml | 12 ++++++------ 10 files changed, 59 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f848f69185..dc54aa2b3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2893,12 +2893,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "base64 0.13.0", "bytes 1.1.0", @@ -4311,9 +4311,9 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "itertools 0.10.3", @@ -6939,7 +6939,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6980,7 +6980,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "flex-error", "serde 1.0.137", @@ -7006,7 +7006,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "derive_more", "flex-error", @@ -7035,7 +7035,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "bytes 1.1.0", "flex-error", @@ -7085,7 +7085,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "async-tungstenite", @@ -7133,7 +7133,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/Cargo.toml b/Cargo.toml index ccbb2c35de..d31a8f234b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,16 +33,16 @@ borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} # patched to a commit on the `eth-bridge-integration` branch of our fork tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "fcc0014d0bda707109901abfa1b2f782d242f082"} diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index a6b77ec9c6..f4f5fcdadb 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1060,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "bytes", "derive_more", @@ -1087,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "base64", "bytes", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 53e83553cc..28f0038689 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -26,8 +26,8 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index b9dcdc3d5d..a002518543 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1060,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "bytes", "derive_more", @@ -1087,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "base64", "bytes", @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "bytes", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "derive_more", "flex-error", @@ -2468,7 +2468,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "bytes", "flex-error", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index e2eadbd977..1c3241a8ca 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -26,14 +26,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 3f0a675b4d..3797901e2d 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "base64", "bytes", @@ -2457,7 +2457,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "bytes", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "derive_more", "flex-error", @@ -2497,7 +2497,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "bytes", "flex-error", @@ -2514,7 +2514,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index ffd9730962..6766179255 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -58,14 +58,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index e31959dbef..b46b78e65f 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=5b4dce005958364baa21872d02b5e8feed1a3cf5#5b4dce005958364baa21872d02b5e8feed1a3cf5" dependencies = [ "base64", "bytes", @@ -2459,7 +2459,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "bytes", @@ -2487,7 +2487,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "derive_more", "flex-error", @@ -2499,7 +2499,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "bytes", "flex-error", @@ -2516,7 +2516,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 1033271a6d..edb278de97 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -40,14 +40,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} [dev-dependencies] namada_tests = {path = "../../tests"} From f38516cd7302ba44b12b7c938e72df023390d633 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 13:57:50 +0100 Subject: [PATCH 0774/1995] Fix patching for wasm/tx_template --- wasm/tx_template/Cargo.lock | 12 ++++-------- wasm/tx_template/Cargo.toml | 6 ++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index a6b77ec9c6..78c2272f93 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -2428,8 +2428,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a3d617db287955b07e4bf1523b831bf5055e7d3ceab8504174181df40cb143" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "async-trait", "bytes", @@ -2457,8 +2456,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26787f3d9a6dc1c5e1e5661d7d70bb53613852b01545730df082128fd98e6529" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "derive_more", "flex-error", @@ -2470,8 +2468,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a65da26cc1f24cd53f40d1267372c22d6ce727d9fd40c6c61b879b26154994" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "bytes", "flex-error", @@ -2488,8 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1514e269ebd2dd1f99e931ac99d96eb9b521a0820d9f03e9b56ac724ca456f19" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 53e83553cc..49828ed0bd 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -25,6 +25,12 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} + # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} From e4fbf64ffb2d7a2de6f3cf796b30bf78c7b99f8c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 13:59:44 +0100 Subject: [PATCH 0775/1995] Fix patching for wasm/tx_template --- wasm/tx_template/Cargo.lock | 12 ++++-------- wasm/tx_template/Cargo.toml | 6 ++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index f4f5fcdadb..cac5de9042 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -2428,8 +2428,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a3d617db287955b07e4bf1523b831bf5055e7d3ceab8504174181df40cb143" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "async-trait", "bytes", @@ -2457,8 +2456,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26787f3d9a6dc1c5e1e5661d7d70bb53613852b01545730df082128fd98e6529" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "derive_more", "flex-error", @@ -2470,8 +2468,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a65da26cc1f24cd53f40d1267372c22d6ce727d9fd40c6c61b879b26154994" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "bytes", "flex-error", @@ -2488,8 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1514e269ebd2dd1f99e931ac99d96eb9b521a0820d9f03e9b56ac724ca456f19" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=8c8935e9b3d901b5f9229e34e5b7f90248397b5d#8c8935e9b3d901b5f9229e34e5b7f90248397b5d" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 28f0038689..251990e682 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -25,6 +25,12 @@ borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223 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"} +# patched to a commit on the `eth-bridge-integration` branch of our fork +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "8c8935e9b3d901b5f9229e34e5b7f90248397b5d"} + # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "5b4dce005958364baa21872d02b5e8feed1a3cf5"} From 013fe5740bc16bc75102e208913a58434603718e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Oct 2022 13:34:27 +0000 Subject: [PATCH 0776/1995] [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 49455e617b..de0f089b1f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.aacf3881273c1d322d9f67a2fee9cf9b5cab3661969b2adc388727a9ad7564f5.wasm", - "tx_ibc.wasm": "tx_ibc.66695a390c04405759d1128612556a0d8ce85ff3c6adf50546fcd991eaee6f41.wasm", - "tx_init_account.wasm": "tx_init_account.afc109717426c966943e0bf8f8d5f064908552335c109a4b0d81a72e7faa5113.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f828560e5dd7be8a9bc25e5cfe5d258ea444ae3d097954398892562432ee0d6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2ce224b475520e48e19ba1dc70f0e01b8219c927fc9b60b7ab231fa833d09ab2.wasm", - "tx_init_validator.wasm": "tx_init_validator.4e84084907a3029d79833560b42c3ab69cf38020dc82c0a2f1ba323cbf372c6f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.788f29b1fb84835e2a905630dee2618be297bd53c68fcacf1c12fff81399dbc9.wasm", - "tx_transfer.wasm": "tx_transfer.7c84e04d3d636306bf09490744cdf4229ecc4954b10cffcff7d4beddd5f27c64.wasm", - "tx_unbond.wasm": "tx_unbond.6393cd64250c22ed82f32cfe9bde743d7ac57a9937e5ce0fc0ec8902301361f3.wasm", - "tx_update_vp.wasm": "tx_update_vp.e03daa135e498a83aabbd17109867ef138daa5e73a7d64904cb71da01c55ee06.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1cea6c13a9b78547363463e2c72c88b2cd7052e1d0da7c7bc67d6dffdbeafea2.wasm", - "tx_withdraw.wasm": "tx_withdraw.03da515f86aa2caeef33b81988f64675aef46257242cc5f42d73b986d04b94cf.wasm", - "vp_nft.wasm": "vp_nft.84395d98e82714782af42e7246bb000666ce29aa30e1952a97a87a3244fd7114.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ca4a51feebdb0313a8a87d6defd38679870e2a1be5d629979db9b11a0c60397.wasm", - "vp_token.wasm": "vp_token.227ccbdbd7401613bcd31e123e2021c12c4ed0f7ee746ba2f0296b9e18436eb0.wasm", - "vp_user.wasm": "vp_user.a174ba668bb9d868509632bed424aef09ddd016faf072f1cf5c2fcef1a468bd0.wasm" + "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", + "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", + "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", + "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", + "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", + "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", + "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", + "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", + "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", + "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", + "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", + "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" } \ No newline at end of file From f208f2be83d87cd256107a01b4d4885393c4a856 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 5 Sep 2022 17:00:12 +0100 Subject: [PATCH 0777/1995] Update storage and mint new wrapped ERC20s --- apps/src/lib/node/ledger/protocol/mod.rs | 27 +- .../transactions/ethereum_events/eth_msgs.rs | 92 +++++ .../transactions/ethereum_events/events.rs | 182 ++++++++++ .../transactions/ethereum_events/mod.rs | 329 ++++++++++++++++++ .../transactions/ethereum_events/read.rs | 93 +++++ .../transactions/ethereum_events/update.rs | 65 ++++ .../transactions/ethereum_events/utils.rs | 197 +++++++++++ .../node/ledger/protocol/transactions/mod.rs | 8 + .../ledger/protocol/transactions/store.rs | 145 ++++++++ apps/src/lib/node/ledger/shell/mod.rs | 2 +- .../lib/node/ledger/shell/vote_extensions.rs | 8 + shared/src/types/ethereum_events.rs | 57 ++- shared/src/types/voting_power.rs | 14 + 13 files changed, 1201 insertions(+), 18 deletions(-) create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/mod.rs create mode 100644 apps/src/lib/node/ledger/protocol/transactions/store.rs diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 9b1b13c859..9c313a5ca3 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -1,4 +1,6 @@ //! The ledger's protocol +mod transactions; + use std::collections::BTreeSet; use std::panic; @@ -24,6 +26,7 @@ use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; +use self::transactions::store::{LedgerStore, StoreExt}; use crate::node::ledger::shell::Shell; #[derive(Error, Debug)] @@ -34,6 +37,8 @@ pub enum Error { TxDecodingError(proto::Error), #[error("Transaction runner error: {0}")] TxRunnerError(vm::wasm::run::Error), + #[error(transparent)] + ProtocolTxError(#[from] eyre::Error), #[error("Txs must either be encrypted or a decryption of an encrypted tx")] TxTypeError, #[error("Gas error: {0}")] @@ -131,7 +136,8 @@ where }, ), TxType::Protocol(ProtocolTx { tx, .. }) => { - apply_protocol_tx(tx, storage) + let mut store: LedgerStore = storage.into(); + apply_protocol_tx(tx, &mut store) } _ => { // other transaction types we treat as a noop @@ -202,26 +208,17 @@ where /// is updated natively rather than via the wasm environment, so gas does not /// need to be metered and validity predicates are bypassed. A [`TxResult`] /// containing changed keys and the like should be returned in the normal way. -pub(crate) fn apply_protocol_tx<'a, D, H>( +pub(crate) fn apply_protocol_tx( tx: ProtocolTxType, - // TODO: eventually this `storage` parameter could be tightened further to - // an impl trait of only the subset of [`Storage`] functionality that we - // need - _storage: &'a mut Storage, -) -> Result -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ + store: &mut impl StoreExt, +) -> Result { match tx { ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { events, .. }) => { - if !events.is_empty() { - tracing::debug!(n = events.len(), "Ethereum events received"); - } - Ok(TxResult::default()) + self::transactions::ethereum_events::apply_derived_tx(store, events) + .map_err(Error::ProtocolTxError) } ProtocolTxType::ValidatorSetUpdate(_) => Ok(TxResult::default()), _ => { diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs new file mode 100644 index 0000000000..fb73dd1f3d --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -0,0 +1,92 @@ +use std::collections::BTreeSet; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada::types::address::Address; +use namada::types::ethereum_events::EthereumEvent; +use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada::types::voting_power::FractionalVotingPower; + +/// Represents an Ethereum event being seen by some validators +#[derive( + Debug, + Clone, + Ord, + PartialOrd, + PartialEq, + Eq, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct EthMsgUpdate { + /// The event being seen + pub body: EthereumEvent, + /// Addresses of the validators who have just seen this event + /// we use [`BTreeSet`] even though ordering is not important here, so that + /// we can derive [`Hash`] for [`EthMsgUpdate`]. This also conveniently + /// orders addresses in the order in which they should be stored in + /// blockchain storage. + pub seen_by: BTreeSet
, +} + +impl From for EthMsgUpdate { + fn from( + MultiSignedEthEvent { event, signers }: MultiSignedEthEvent, + ) -> Self { + Self { + body: event, + seen_by: signers.into_iter().map(|(address, _)| address).collect(), + } + } +} + +/// Represents an event stored under `eth_msgs` +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthMsg { + /// The event being stored + pub body: EthereumEvent, + /// The total voting power that's voted for this event across all epochs + pub voting_power: FractionalVotingPower, + /// The addresses of validators that voted for this event, in sorted order. + pub seen_by: Vec
, + /// Whether this event has been acted on or not + pub seen: bool, +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeSet, HashSet}; + + use namada::types::address; + use namada::types::ethereum_events::testing::{ + arbitrary_nonce, arbitrary_single_transfer, + }; + use namada::types::storage::BlockHeight; + + use super::*; + + #[test] + /// Tests [`From`] for [`EthMsgUpdate`] + fn test_from_multi_signed_eth_event_for_eth_msg_update() { + let sole_validator = address::testing::established_address_1(); + let receiver = address::testing::established_address_2(); + let event = arbitrary_single_transfer(arbitrary_nonce(), receiver); + let with_signers = MultiSignedEthEvent { + event: event.clone(), + signers: HashSet::from_iter(vec![( + sole_validator.clone(), + BlockHeight(100), + )]), + }; + let expected = EthMsgUpdate { + body: event, + seen_by: BTreeSet::from_iter(vec![sole_validator]), + }; + + let update: EthMsgUpdate = with_signers.into(); + + assert_eq!(update, expected); + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs new file mode 100644 index 0000000000..97b5e17497 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -0,0 +1,182 @@ +//! Logic for acting on events + +use std::collections::BTreeSet; + +use eyre::Result; +use namada::ledger::eth_bridge::storage::wrapped_erc20s; +use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; +use namada::types::storage::Key; + +use super::update; +use crate::node::ledger::protocol::transactions::store::Store; + +/// Updates storage based on the given confirmed `event`. For example, for a +/// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding +/// transferred assets to the appropriate receiver addresses. +pub(super) fn act_on( + store: &mut impl Store, + event: &EthereumEvent, +) -> Result> { + match &event { + EthereumEvent::TransfersToNamada { transfers, .. } => { + act_on_transfers_to_namada(store, transfers) + } + _ => { + tracing::debug!("No actions taken for event"); + Ok(BTreeSet::default()) + } + } +} + +fn act_on_transfers_to_namada( + store: &mut impl Store, + transfers: &[TransferToNamada], +) -> Result> { + let mut changed_keys = BTreeSet::default(); + for TransferToNamada { + amount, + asset, + receiver, + } in transfers + { + let keys: wrapped_erc20s::Keys = asset.into(); + let balance_key = keys.balance(receiver); + update::amount(store, &balance_key, |balance| { + tracing::debug!( + %balance_key, + ?balance, + "Existing value found", + ); + balance.receive(amount); + tracing::debug!( + %balance_key, + ?balance, + "New value calculated", + ); + })?; + _ = changed_keys.insert(balance_key); + + let supply_key = keys.supply(); + update::amount(store, &supply_key, |supply| { + tracing::debug!( + %supply_key, + ?supply, + "Existing value found", + ); + supply.receive(amount); + tracing::debug!( + %supply_key, + ?supply, + "New value calculated", + ); + })?; + _ = changed_keys.insert(supply_key); + } + Ok(changed_keys) +} + +#[cfg(test)] +mod tests { + use borsh::BorshSerialize; + use namada::types::address; + use namada::types::ethereum_events::testing::{ + arbitrary_eth_address, arbitrary_keccak_hash, arbitrary_nonce, + DAI_ERC20_ETH_ADDRESS, + }; + use namada::types::token::Amount; + + use super::*; + use crate::node::ledger::protocol::transactions::store::testing::FakeStore; + + #[test] + /// Test that we do not make any changes to storage when acting on most + /// events + fn test_act_on_does_nothing_for_other_events() { + let mut store = FakeStore::default(); + let events = vec![ + EthereumEvent::NewContract { + name: "bridge".to_string(), + address: arbitrary_eth_address(), + }, + EthereumEvent::TransfersToEthereum { + nonce: arbitrary_nonce(), + transfers: vec![], + }, + EthereumEvent::UpdateBridgeWhitelist { + nonce: arbitrary_nonce(), + whitelist: vec![], + }, + EthereumEvent::UpgradedContract { + name: "bridge".to_string(), + address: arbitrary_eth_address(), + }, + EthereumEvent::ValidatorSetUpdate { + nonce: arbitrary_nonce(), + bridge_validator_hash: arbitrary_keccak_hash(), + governance_validator_hash: arbitrary_keccak_hash(), + }, + ]; + + for event in events.iter() { + act_on(&mut store, event).unwrap(); + + assert!( + store.values.is_empty(), + "storage changed unexpectedly while acting on event: {:#?}", + event + ); + } + } + + #[test] + /// Test that storage is indeed changed when we act on a non-empty + /// TransfersToNamada batch + fn test_act_on_changes_storage_for_transfers_to_namada() { + let mut store = FakeStore::default(); + let amount = Amount::from(100); + let receiver = address::testing::established_address_1(); + let transfers = vec![TransferToNamada { + amount, + asset: DAI_ERC20_ETH_ADDRESS, + receiver, + }]; + let event = EthereumEvent::TransfersToNamada { + nonce: arbitrary_nonce(), + transfers, + }; + + act_on(&mut store, &event).unwrap(); + + assert!(!store.values.is_empty()); + } + + #[test] + /// Test acting on a single transfer and minting the first ever wDAI + fn test_act_on_transfers_to_namada_mints_wdai() { + let mut store = FakeStore::default(); + + let amount = Amount::from(100); + let receiver = address::testing::established_address_1(); + let transfers = vec![TransferToNamada { + amount, + asset: DAI_ERC20_ETH_ADDRESS, + receiver: receiver.clone(), + }]; + + act_on_transfers_to_namada(&mut store, &transfers).unwrap(); + + let wdai: wrapped_erc20s::Keys = (&DAI_ERC20_ETH_ADDRESS).into(); + let receiver_balance_key = wdai.balance(&receiver); + let wdai_supply_key = wdai.supply(); + + assert_eq!(store.values.len(), 2); + assert_eq!( + store.values.get(&receiver_balance_key).unwrap(), + &amount.try_to_vec().unwrap() + ); + assert_eq!( + store.values.get(&wdai_supply_key).unwrap(), + &amount.try_to_vec().unwrap() + ); + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs new file mode 100644 index 0000000000..8c412fa5e0 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -0,0 +1,329 @@ +//! Code for handling [`ProtocolTxType::EthereumEvents`] transactions. +mod eth_msgs; +mod events; +mod read; +mod update; +mod utils; + +use std::collections::{BTreeSet, HashMap, HashSet}; + +use borsh::BorshSerialize; +use eth_msgs::{EthMsg, EthMsgUpdate}; +use eyre::{eyre, Result}; +use namada::ledger::eth_bridge::storage::eth_msgs::Keys; +use namada::types::address::Address; +use namada::types::ethereum_events::EthereumEvent; +use namada::types::storage; +use namada::types::transaction::TxResult; +use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada::types::voting_power::FractionalVotingPower; + +use super::store::{Store, StoreExt}; + +/// The keys changed while applying a protocol transaction +type ChangedKeys = BTreeSet; + +/// Applies derived state changes to storage, based on Ethereum `events` which +/// were newly seen by some active validator(s) in the last epoch. For `events` +/// which have been seen by enough voting power, extra state changes may take +/// place, such as minting of wrapped ERC20s. +/// +/// This function is deterministic based on some existing blockchain state and +/// the passed `events`. +pub(crate) fn apply_derived_tx( + store: &mut impl StoreExt, + events: Vec, +) -> Result { + if events.is_empty() { + return Ok(TxResult::default()); + } + tracing::info!( + ethereum_events = events.len(), + "Applying state updates derived from Ethereum events found in \ + protocol transaction" + ); + + let (updates, voting_powers) = get_update_data(store, events)?; + + let changed_keys = apply_updates(store, updates, voting_powers)?; + + Ok(TxResult { + changed_keys, + ..Default::default() + }) +} + +/// Constructs all needed data that may be needed for updating #EthBridge +/// internal account storage based on `events`. +fn get_update_data( + store: &impl StoreExt, + events: Vec, +) -> Result<( + HashSet, + HashMap, +)> { + let last_epoch = store.get_last_epoch(); + tracing::debug!(?last_epoch, "Got epoch of last block"); + + let active_validators = store.get_active_validators(Some(last_epoch)); + tracing::debug!( + n = active_validators.len(), + "got active validators - {:#?}", + active_validators, + ); + + let voters = utils::get_voters_for_events(events.iter()); + tracing::debug!(?voters, "Got validators who voted on at least one event"); + + let voting_powers = + utils::get_voting_powers_for_selected(&active_validators, voters)?; + tracing::debug!( + ?voting_powers, + "got voting powers for relevant validators" + ); + + let updates = events.into_iter().map(Into::::into).collect(); + + Ok((updates, voting_powers)) +} + +/// Apply an Ethereum state update + act on any events which are confirmed +pub(super) fn apply_updates( + store: &mut impl Store, + updates: HashSet, + voting_powers: HashMap, +) -> Result { + tracing::debug!( + updates.len = updates.len(), + ?voting_powers, + "Applying Ethereum state update transaction" + ); + + let mut changed_keys = BTreeSet::default(); + let mut confirmed = vec![]; + for update in updates { + // The order in which updates are applied to storage does not matter. + // The final storage state will be the same regardless. + let (mut changed, newly_confirmed) = + apply_update(store, update.clone(), &voting_powers)?; + changed_keys.append(&mut changed); + if newly_confirmed { + confirmed.push(update.body); + } + } + if confirmed.is_empty() { + tracing::debug!("No events were newly confirmed"); + return Ok(changed_keys); + } + tracing::debug!(n = confirmed.len(), "Events were newly confirmed",); + + // Right now, the order in which events are acted on does not matter. + // For `TransfersToNamada` events, they can happen in any order. + for event in &confirmed { + let mut changed = events::act_on(store, event)?; + changed_keys.append(&mut changed); + } + Ok(changed_keys) +} + +/// Apply an [`EthMsgUpdate`] to storage. Returns any keys changed and whether +/// the event was newly seen. +fn apply_update( + store: &mut impl Store, + update: EthMsgUpdate, + voting_powers: &HashMap, +) -> Result<(ChangedKeys, bool)> { + let eth_msg_keys = Keys::from(&update.body); + + // we arbitrarily look at whether the seen key is present to + // determine if the /eth_msg already exists in storage, but maybe there + // is a less arbitrary way to do this + let exists_in_storage = store.has_key(ð_msg_keys.seen())?; + + let (eth_msg_post, changed) = if !exists_in_storage { + calculate_new_eth_msg(update, voting_powers)? + } else { + calculate_updated_eth_msg(store, update, voting_powers)? + }; + write_eth_msg(store, ð_msg_keys, ð_msg_post)?; + Ok((changed, !exists_in_storage)) +} + +fn calculate_new_eth_msg( + update: EthMsgUpdate, + voting_powers: &HashMap, +) -> Result<(EthMsg, ChangedKeys)> { + let eth_msg_keys = Keys::from(&update.body); + tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); + + let mut seen_by_voting_power = FractionalVotingPower::default(); + for validator in &update.seen_by { + match voting_powers.get(validator) { + Some(voting_power) => seen_by_voting_power += voting_power, + None => { + return Err(eyre!( + "voting power was not provided for validator {}", + validator + )); + } + }; + } + + let newly_confirmed = + seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; + Ok(( + EthMsg { + body: update.body, + voting_power: seen_by_voting_power, + // the below `.collect()` is deterministic and will result in a + // sorted vector as `update.seen_by` is a [`BTreeSet`] + seen_by: update.seen_by.into_iter().collect(), + seen: newly_confirmed, + }, + eth_msg_keys.into_iter().collect(), + )) +} + +fn calculate_updated_eth_msg( + store: &mut impl Store, + update: EthMsgUpdate, + voting_powers: &HashMap, +) -> Result<(EthMsg, ChangedKeys)> { + let eth_msg_keys = Keys::from(&update.body); + tracing::debug!( + %eth_msg_keys.prefix, + "Ethereum event already exists in storage", + ); + let body: EthereumEvent = read::value(store, ð_msg_keys.body())?; + let seen: bool = read::value(store, ð_msg_keys.seen())?; + let seen_by: Vec
= read::value(store, ð_msg_keys.seen_by())?; + let voting_power: FractionalVotingPower = + read::value(store, ð_msg_keys.voting_power())?; + + let eth_msg_pre = EthMsg { + body, + voting_power, + seen_by, + seen, + }; + tracing::debug!("Read EthMsg - {:#?}", ð_msg_pre); + Ok(calculate_diff(eth_msg_pre, update, voting_powers)) +} + +fn calculate_diff( + eth_msg: EthMsg, + _update: EthMsgUpdate, + _voting_powers: &HashMap, +) -> (EthMsg, ChangedKeys) { + tracing::warn!( + "Updating Ethereum events is not yet implemented, so this Ethereum \ + event won't change" + ); + (eth_msg, BTreeSet::default()) +} + +fn write_eth_msg( + store: &mut impl Store, + eth_msg_keys: &Keys, + eth_msg: &EthMsg, +) -> Result<()> { + tracing::debug!("writing EthMsg - {:#?}", eth_msg); + store.write(ð_msg_keys.body(), ð_msg.body.try_to_vec()?)?; + store.write(ð_msg_keys.seen(), ð_msg.seen.try_to_vec()?)?; + store.write(ð_msg_keys.seen_by(), ð_msg.seen_by.try_to_vec()?)?; + store.write( + ð_msg_keys.voting_power(), + ð_msg.voting_power.try_to_vec()?, + )?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeSet, HashMap, HashSet}; + + use borsh::BorshDeserialize; + use namada::ledger::eth_bridge::storage::wrapped_erc20s; + use namada::types::address; + use namada::types::ethereum_events::testing::{ + arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, + }; + use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; + use namada::types::token::Amount; + + use super::*; + use crate::node::ledger::protocol::transactions::store::testing::FakeStore; + + #[test] + /// Test applying a `TransfersToNamada` batch containing a single transfer + fn test_apply_single_transfer() -> Result<()> { + let sole_validator = address::testing::gen_established_address(); + let receiver = address::testing::established_address_2(); + + let amount = arbitrary_amount(); + let asset = arbitrary_eth_address(); + let body = EthereumEvent::TransfersToNamada { + nonce: arbitrary_nonce(), + transfers: vec![TransferToNamada { + amount, + asset: asset.clone(), + receiver: receiver.clone(), + }], + }; + let update = EthMsgUpdate { + body: body.clone(), + seen_by: BTreeSet::from_iter(vec![sole_validator.clone()]), + }; + let updates = HashSet::from_iter(vec![update]); + let voting_powers = HashMap::from_iter(vec![( + sole_validator.clone(), + FractionalVotingPower::new(1, 1).unwrap(), + )]); + let mut storage = FakeStore::default(); + + let changed_keys = apply_updates(&mut storage, updates, voting_powers)?; + + let eth_msg_keys: Keys = (&body).into(); + let wrapped_erc20_keys: wrapped_erc20s::Keys = (&asset).into(); + assert_eq!( + BTreeSet::from_iter(vec![ + eth_msg_keys.body(), + eth_msg_keys.seen(), + eth_msg_keys.seen_by(), + eth_msg_keys.voting_power(), + wrapped_erc20_keys.balance(&receiver), + wrapped_erc20_keys.supply(), + ]), + changed_keys + ); + + let body_bytes = storage.read(ð_msg_keys.body())?.unwrap(); + assert_eq!(EthereumEvent::try_from_slice(&body_bytes)?, body); + let seen_bytes = storage.read(ð_msg_keys.seen())?.unwrap(); + assert!(bool::try_from_slice(&seen_bytes)?); + let seen_by_bytes = storage.read(ð_msg_keys.seen_by())?.unwrap(); + assert_eq!( + Vec::
::try_from_slice(&seen_by_bytes)?, + vec![sole_validator] + ); + let voting_power_bytes = + storage.read(ð_msg_keys.voting_power())?.unwrap(); + assert_eq!(<(u64, u64)>::try_from_slice(&voting_power_bytes)?, (1, 1)); + + let wrapped_erc20_balance_bytes = storage + .read(&wrapped_erc20_keys.balance(&receiver))? + .unwrap(); + assert_eq!( + Amount::try_from_slice(&wrapped_erc20_balance_bytes)?, + amount + ); + let wrapped_erc20_supply_bytes = + storage.read(&wrapped_erc20_keys.supply())?.unwrap(); + assert_eq!( + Amount::try_from_slice(&wrapped_erc20_supply_bytes)?, + amount + ); + + Ok(()) + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs new file mode 100644 index 0000000000..6061b017ab --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -0,0 +1,93 @@ +//! Helpers for reading from storage +use borsh::BorshDeserialize; +use eyre::{eyre, Result}; +use namada::types::storage; +use namada::types::token::Amount; + +use super::Store; + +/// Returns the stored Amount, or 0 if not stored +pub(super) fn amount_or_default( + store: &impl Store, + key: &storage::Key, +) -> Result { + let amount = match maybe_value(store, key)? { + Some(amount) => amount, + None => Amount::from(0), + }; + Ok(amount) +} + +/// Read some arbitrary value from storage, erroring if it's not found +pub(super) fn value( + store: &impl Store, + key: &storage::Key, +) -> Result { + maybe_value(store, key)?.ok_or_else(|| eyre!("no value found at {}", key)) +} + +/// Try to read some arbitrary value from storage, returning `None` if nothing +/// is read. This will still error if there is data stored at `key` but it is +/// not deserializable to `T`. +pub(super) fn maybe_value( + storage: &impl Store, + key: &storage::Key, +) -> Result> { + let maybe_val = storage.read(key)?; + let bytes = match maybe_val { + Some(bytes) => bytes, + None => return Ok(None), + }; + let deserialized = T::try_from_slice(&bytes[..])?; + Ok(Some(deserialized)) +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use borsh::BorshSerialize; + use namada::types::storage; + use namada::types::token::Amount; + + use crate::node::ledger::protocol::transactions::ethereum_events::read; + use crate::node::ledger::protocol::transactions::store::testing::FakeStore; + use crate::node::ledger::protocol::transactions::store::Store; + + #[test] + fn test_amount_returns_zero_for_uninitialized_storage() { + let fake_storage = FakeStore::default(); + let a = read::amount_or_default( + &fake_storage, + &storage::Key::parse( + "some arbitrary key with no stored + value", + ) + .unwrap(), + ) + .unwrap(); + assert_eq!(a, Amount::from(0)); + } + + #[test] + fn test_amount_returns_stored_amount() { + let key = storage::Key::parse("some arbitrary key").unwrap(); + let amount = Amount::from(1_000_000); + let mut fake_storage = FakeStore::default(); + fake_storage + .write(&key, amount.try_to_vec().unwrap()) + .unwrap(); + + let a = read::amount_or_default(&fake_storage, &key).unwrap(); + assert_eq!(a, amount); + } + + #[test] + fn test_amount_errors_if_not_amount() { + let key = storage::Key::parse("some arbitrary key").unwrap(); + let amount = "not an Amount type"; + let mut fake_storage = FakeStore::default(); + fake_storage.write(&key, amount.as_bytes()).unwrap(); + + assert_matches!(read::amount_or_default(&fake_storage, &key), Err(_)); + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs new file mode 100644 index 0000000000..235557d64d --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -0,0 +1,65 @@ +//! Helpers for writing to storage +use borsh::{BorshDeserialize, BorshSerialize}; +use eyre::Result; +use namada::types::storage; +use namada::types::token::Amount; + +use super::Store; + +/// Reads the `Amount` from key, applies update then writes it back +pub(super) fn amount( + store: &mut impl Store, + key: &storage::Key, + update: impl Fn(&mut Amount), +) -> Result { + let mut amount = super::read::amount_or_default(store, key)?; + update(&mut amount); + store.write(key, amount.try_to_vec()?)?; + Ok(amount) +} + +#[allow(dead_code)] +/// Reads an arbitrary value, applies update then writes it back +pub(super) fn value( + store: &mut impl Store, + key: &storage::Key, + update: impl Fn(&mut T), +) -> Result { + let mut value = super::read::value(store, key)?; + update(&mut value); + store.write(key, value.try_to_vec()?)?; + Ok(value) +} + +#[cfg(test)] +mod tests { + use borsh::{BorshDeserialize, BorshSerialize}; + use eyre::{eyre, Result}; + use namada::types::storage; + + use crate::node::ledger::protocol::transactions::store::testing::FakeStore; + use crate::node::ledger::protocol::transactions::store::Store; + + #[test] + /// Test updating a value + fn test_value() -> Result<()> { + let key = storage::Key::parse("some arbitrary key") + .expect("could not set up test"); + let value = 21; + let mut fake_storage = FakeStore::default(); + let serialized = value.try_to_vec().expect("could not set up test"); + fake_storage + .write(&key, serialized) + .expect("could not set up test"); + + super::value(&mut fake_storage, &key, |v: &mut i32| *v *= 2)?; + + let new_val = fake_storage.read(&key)?; + let new_val = match new_val { + Some(new_val) => ::try_from_slice(&new_val)?, + None => return Err(eyre!("no value found")), + }; + assert_eq!(new_val, 42); + Ok(()) + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs new file mode 100644 index 0000000000..6623df2602 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -0,0 +1,197 @@ +use std::collections::{BTreeSet, HashMap, HashSet}; + +use eyre::eyre; +use namada::ledger::pos::types::{VotingPower, WeightedValidator}; +use namada::types::address::Address; +use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada::types::voting_power::FractionalVotingPower; + +/// Gets all the voters from the given events. +pub(super) fn get_voters_for_events<'a>( + events: impl Iterator, +) -> HashSet
{ + events.fold(HashSet::new(), |mut validators, event| { + validators + .extend(event.signers.iter().map(|(addr, _)| addr.to_owned())); + validators + }) +} + +/// Gets the voting power of `selected` from `all_active`. Errors if a +/// `selected` validator is not found in `all_active`. +pub(super) fn get_voting_powers_for_selected( + all_active: &BTreeSet>, + selected: HashSet
, +) -> eyre::Result> { + let total_voting_power = sum_voting_powers(all_active); + let voting_powers: HashMap = all_active + .iter() + .filter(|validator| selected.contains(&validator.address)) + .map(|validator| { + // TODO: get rid of .unwrap() call in here + ( + validator.address.to_owned(), + FractionalVotingPower::new( + validator.voting_power.into(), + total_voting_power.into(), + ) + .unwrap(), + ) + }) + .collect(); + for validator in &selected { + if voting_powers.get(validator).is_none() { + return Err(eyre!( + "couldn't get voting power for validator {}", + validator, + )); + } + } + Ok(voting_powers) +} + +pub(super) fn sum_voting_powers( + validators: &BTreeSet>, +) -> VotingPower { + validators + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use assert_matches::assert_matches; + use namada::types::address; + use namada::types::ethereum_events::testing::arbitrary_voting_power; + + use super::*; + + #[test] + /// Test getting the voting power for the sole active validator from the set + /// of active validators + fn test_get_voting_powers_for_selected_sole_validator() { + let sole_validator = address::testing::established_address_1(); + let voting_power = arbitrary_voting_power(); + let weighted_sole_validator = WeightedValidator { + voting_power, + address: sole_validator.clone(), + }; + let validators = HashSet::from_iter(vec![sole_validator.clone()]); + let active_validators = + BTreeSet::from_iter(vec![weighted_sole_validator]); + + let result = + get_voting_powers_for_selected(&active_validators, validators); + + let voting_powers = match result { + Ok(voting_powers) => voting_powers, + Err(error) => panic!("error: {:?}", error), + }; + assert_eq!(voting_powers.len(), 1); + assert_matches!(voting_powers.get(&sole_validator), Some(v) if *v == FractionalVotingPower::new(1, 1).unwrap()); + } + + #[test] + /// Test that an error is returned if a validator is not found in the set of + /// active validators + fn test_get_voting_powers_for_selected_missing_validator() { + let present_validator = address::testing::established_address_1(); + let missing_validator = address::testing::established_address_2(); + let voting_power = arbitrary_voting_power(); + let weighted_present_validator = WeightedValidator { + voting_power, + address: present_validator.clone(), + }; + let validators = + HashSet::from_iter(vec![present_validator, missing_validator]); + let active_validators = + BTreeSet::from_iter(vec![weighted_present_validator]); + + let result = + get_voting_powers_for_selected(&active_validators, validators); + + assert!(result.is_err()); + } + + #[test] + /// Test getting the voting powers for two active validators from the set of + /// active validators + fn test_get_voting_powers_for_selected_two_validators() { + let validator_1 = address::testing::established_address_1(); + let validator_2 = address::testing::established_address_2(); + let voting_power_1 = VotingPower::from(100); + let voting_power_2 = VotingPower::from(200); + let weighted_validator_1 = WeightedValidator { + voting_power: voting_power_1, + address: validator_1.clone(), + }; + let weighted_validator_2 = WeightedValidator { + voting_power: voting_power_2, + address: validator_2.clone(), + }; + let validators = + HashSet::from_iter(vec![validator_1.clone(), validator_2.clone()]); + let active_validators = BTreeSet::from_iter(vec![ + weighted_validator_1, + weighted_validator_2, + ]); + + let result = + get_voting_powers_for_selected(&active_validators, validators); + + let voting_powers = match result { + Ok(voting_powers) => voting_powers, + Err(error) => panic!("error: {:?}", error), + }; + assert_eq!(voting_powers.len(), 2); + assert_matches!(voting_powers.get(&validator_1), Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap()); + assert_matches!(voting_powers.get(&validator_2), Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap()); + } + + #[test] + /// Test summing the voting powers for a set of validators containing only + /// one validator + fn test_sum_voting_powers_sole_validator() { + let sole_validator = address::testing::established_address_1(); + let voting_power = arbitrary_voting_power(); + let weighted_sole_validator = WeightedValidator { + voting_power, + address: sole_validator, + }; + let validators = BTreeSet::from_iter(vec![weighted_sole_validator]); + + let total = sum_voting_powers(&validators); + + assert_eq!(total, voting_power); + } + + #[test] + /// Test summing the voting powers for a set of validators containing two + /// validators + fn test_sum_voting_powers_two_validators() { + let validator_1 = address::testing::established_address_1(); + let validator_2 = address::testing::established_address_2(); + let voting_power_1 = VotingPower::from(100); + let voting_power_2 = VotingPower::from(200); + let weighted_validator_1 = WeightedValidator { + voting_power: voting_power_1, + address: validator_1, + }; + let weighted_validator_2 = WeightedValidator { + voting_power: voting_power_2, + address: validator_2, + }; + let validators = BTreeSet::from_iter(vec![ + weighted_validator_1, + weighted_validator_2, + ]); + + let total = sum_voting_powers(&validators); + + assert_eq!(total, VotingPower::from(300)); + } +} diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs new file mode 100644 index 0000000000..5d0f477e29 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -0,0 +1,8 @@ +//! This module contains functionality for handling protocol transactions. +//! +//! When a protocol transaction is included in a block, we may expect all nodes +//! to update their blockchain state in a deterministic way. This can be done +//! natively rather than via the wasm environment as happens with regular +//! transactions. +pub(super) mod ethereum_events; +pub(super) mod store; diff --git a/apps/src/lib/node/ledger/protocol/transactions/store.rs b/apps/src/lib/node/ledger/protocol/transactions/store.rs new file mode 100644 index 0000000000..8d633becf8 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/store.rs @@ -0,0 +1,145 @@ +//! Interfaces for interacting with blockchain state as part of applying a +//! protocol transaction. + +use std::collections::BTreeSet; + +use eyre::Result; +use namada::ledger; +use namada::ledger::pos::types::WeightedValidator; +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::types::address::Address; +use namada::types::storage::{self, Epoch}; + +use crate::node::ledger::shell::queries::QueriesExt; + +/// Storage functionality needed for applying state changes required by a +/// protocol transaction. We don't need to know about the gas cost of changes or +/// the like as gas is not charged for protocol transactions. +pub(crate) trait Store { + /// Returns some value stored at `key`, or `None` if no value is stored + /// there. + fn read(&self, key: &storage::Key) -> Result>>; + /// Check if the given key is present in storage. + fn has_key(&self, key: &storage::Key) -> Result; + /// Write a value to `key` + fn write( + &mut self, + key: &storage::Key, + value: impl AsRef<[u8]> + Clone, + ) -> Result<()>; +} + +/// Higher level API for the storage that might be used when applying protocol +/// transactions +pub(crate) trait StoreExt: Store { + fn get_last_epoch(&self) -> Epoch; + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet>; +} + +/// Our handle on blockchain state via a [`ledger::storage::Storage`] +pub(crate) struct LedgerStore<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + native: &'a mut ledger::storage::Storage, +} + +impl<'a, D, H> From<&'a mut ledger::storage::Storage> + for LedgerStore<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn from(native: &'a mut ledger::storage::Storage) -> Self { + Self { native } + } +} + +impl<'a, D, H> Store for LedgerStore<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn read(&self, key: &storage::Key) -> Result>> { + let (maybe_val, _) = self.native.read(key)?; + Ok(maybe_val) + } + + fn has_key(&self, key: &storage::Key) -> Result { + let (has_key, _) = self.native.has_key(key)?; + Ok(has_key) + } + + fn write( + &mut self, + key: &storage::Key, + value: impl AsRef<[u8]> + Clone, + ) -> Result<()> { + _ = self.native.write(key, value)?; + Ok(()) + } +} + +impl<'a, D, H> StoreExt for LedgerStore<'a, D, H> +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn get_last_epoch(&self) -> Epoch { + let (last_epoch, _) = self.native.get_last_epoch(); + last_epoch + } + + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet> { + self.native.get_active_validators(epoch) + } +} + +#[allow(missing_docs)] +/// Test helpers +#[cfg(any(test, feature = "testing"))] +pub mod testing { + use std::collections::HashMap; + + use eyre::Result; + use namada::types::storage; + + use super::*; + + /// Very simple fake storage for use in tests. In-memory map of + /// [`storage::Key`]s to raw bytes. + #[derive(Default)] + pub struct FakeStore { + pub values: HashMap>, + } + + impl Store for FakeStore { + fn read(&self, key: &storage::Key) -> Result>> { + let val = self.values.get(key); + match val { + Some(val) => Ok(Some(val.to_owned())), + None => Ok(None), + } + } + + fn has_key(&self, key: &storage::Key) -> Result { + Ok(self.values.contains_key(key)) + } + + fn write( + &mut self, + key: &storage::Key, + value: impl AsRef<[u8]> + Clone, + ) -> Result<()> { + _ = self.values.insert(key.clone(), value.as_ref().to_vec()); + Ok(()) + } + } +} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2be5913d96..eeead299f8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -9,7 +9,7 @@ mod finalize_block; mod init_chain; mod prepare_proposal; mod process_proposal; -mod queries; +pub(super) mod queries; mod vote_extensions; use std::collections::{BTreeSet, HashSet}; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 06ff3ff000..1e11e8248c 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -88,6 +88,14 @@ where ethereum_events: self.new_ethereum_events(), validator_addr, }; + if !ext.ethereum_events.is_empty() { + tracing::info!( + new_ethereum_events.len = ext.ethereum_events.len(), + ?ext.block_height, + "Voting for new Ethereum events" + ); + tracing::debug!("New Ethereum events - {:#?}", ext.ethereum_events); + } let protocol_key = match &self.mode { ShellMode::Validator { data, .. } => &data.keys.protocol_keypair, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 493f92cd7b..139eb47913 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -86,6 +86,7 @@ impl FromStr for EthAddress { PartialEq, Eq, PartialOrd, + Hash, Ord, Clone, Debug, @@ -160,8 +161,7 @@ pub enum EthereumEvent { impl EthereumEvent { /// SHA256 of the Borsh serialization of the [`EthereumEvent`]. - #[allow(dead_code)] - pub(crate) fn hash(&self) -> Result { + pub fn hash(&self) -> Result { let bytes = self.try_to_vec()?; Ok(Hash::sha256(&bytes)) } @@ -174,6 +174,7 @@ impl EthereumEvent { PartialEq, Eq, PartialOrd, + Hash, Ord, BorshSerialize, BorshDeserialize, @@ -194,6 +195,7 @@ pub struct TransferToNamada { Debug, PartialEq, Eq, + Hash, PartialOrd, Ord, BorshSerialize, @@ -218,6 +220,7 @@ pub struct TransferToEthereum { Debug, PartialEq, Eq, + Hash, PartialOrd, Ord, BorshSerialize, @@ -268,9 +271,15 @@ pub mod tests { } #[allow(missing_docs)] +/// Test helpers #[cfg(any(test, feature = "testing"))] pub mod testing { + use namada_proof_of_stake::types::VotingPower; + use super::*; + use crate::types::storage::Epoch; + use crate::types::token::Amount; + use crate::types::voting_power::FractionalVotingPower; pub const DAI_ERC20_ETH_ADDRESS_CHECKSUMMED: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; @@ -284,4 +293,48 @@ pub mod testing { 160, 184, 105, 145, 198, 33, 139, 54, 193, 209, 157, 74, 46, 158, 176, 206, 54, 6, 235, 72, ]); + + pub fn arbitrary_eth_address() -> EthAddress { + DAI_ERC20_ETH_ADDRESS + } + + pub fn arbitrary_fractional_voting_power() -> FractionalVotingPower { + FractionalVotingPower::new(1, 3).unwrap() + } + + pub fn arbitrary_nonce() -> Uint { + 123.into() + } + + pub fn arbitrary_keccak_hash() -> KeccakHash { + KeccakHash([0; 32]) + } + + pub fn arbitrary_amount() -> Amount { + Amount::from(1_000) + } + + pub fn arbitrary_voting_power() -> VotingPower { + VotingPower::from(1_000) + } + + pub fn arbitrary_epoch() -> Epoch { + Epoch(100) + } + + /// A [`EthereumEvent::TransfersToNamada`] containing a single transfer of + /// some arbitrary ERC20 + pub fn arbitrary_single_transfer( + nonce: Uint, + receiver: Address, + ) -> EthereumEvent { + EthereumEvent::TransfersToNamada { + nonce, + transfers: vec![TransferToNamada { + amount: arbitrary_amount(), + asset: arbitrary_eth_address(), + receiver, + }], + } + } } diff --git a/shared/src/types/voting_power.rs b/shared/src/types/voting_power.rs index e5c35c20bb..6cbc3895e2 100644 --- a/shared/src/types/voting_power.rs +++ b/shared/src/types/voting_power.rs @@ -52,12 +52,26 @@ impl Add for FractionalVotingPower { } } +impl Add<&FractionalVotingPower> for FractionalVotingPower { + type Output = Self; + + fn add(self, rhs: &FractionalVotingPower) -> Self::Output { + Self(self.0 + (*rhs).0) + } +} + impl AddAssign for FractionalVotingPower { fn add_assign(&mut self, rhs: FractionalVotingPower) { *self = Self(self.0 + rhs.0) } } +impl AddAssign<&FractionalVotingPower> for FractionalVotingPower { + fn add_assign(&mut self, rhs: &FractionalVotingPower) { + *self = Self(self.0 + (*rhs).0) + } +} + impl BorshSerialize for FractionalVotingPower { fn serialize( &self, From 13f86da5e62e175449043d106e3871fa9b1c9d40 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 22 Sep 2022 10:36:12 +0100 Subject: [PATCH 0778/1995] Convert to using Storage/TestStorage --- apps/src/lib/node/ledger/protocol/mod.rs | 22 +-- .../transactions/ethereum_events/events.rs | 72 +++++---- .../transactions/ethereum_events/mod.rs | 122 +++++++++------ .../transactions/ethereum_events/read.rs | 48 +++--- .../transactions/ethereum_events/update.rs | 35 +++-- .../node/ledger/protocol/transactions/mod.rs | 1 - .../ledger/protocol/transactions/store.rs | 145 ------------------ 7 files changed, 181 insertions(+), 264 deletions(-) delete mode 100644 apps/src/lib/node/ledger/protocol/transactions/store.rs diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 9c313a5ca3..8e46225a13 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -26,7 +26,6 @@ use namada::vm::{self, wasm, WasmCacheAccess}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; -use self::transactions::store::{LedgerStore, StoreExt}; use crate::node::ledger::shell::Shell; #[derive(Error, Debug)] @@ -136,8 +135,7 @@ where }, ), TxType::Protocol(ProtocolTx { tx, .. }) => { - let mut store: LedgerStore = storage.into(); - apply_protocol_tx(tx, &mut store) + apply_protocol_tx(tx, storage) } _ => { // other transaction types we treat as a noop @@ -208,18 +206,22 @@ where /// is updated natively rather than via the wasm environment, so gas does not /// need to be metered and validity predicates are bypassed. A [`TxResult`] /// containing changed keys and the like should be returned in the normal way. -pub(crate) fn apply_protocol_tx( +pub(crate) fn apply_protocol_tx( tx: ProtocolTxType, - store: &mut impl StoreExt, -) -> Result { + storage: &mut Storage, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ match tx { ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { events, .. - }) => { - self::transactions::ethereum_events::apply_derived_tx(store, events) - .map_err(Error::ProtocolTxError) - } + }) => self::transactions::ethereum_events::apply_derived_tx( + storage, events, + ) + .map_err(Error::ProtocolTxError), ProtocolTxType::ValidatorSetUpdate(_) => Ok(TxResult::default()), _ => { tracing::error!( diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index 97b5e17497..0a23efaee8 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -4,22 +4,26 @@ use std::collections::BTreeSet; use eyre::Result; use namada::ledger::eth_bridge::storage::wrapped_erc20s; +use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::storage::Key; use super::update; -use crate::node::ledger::protocol::transactions::store::Store; /// Updates storage based on the given confirmed `event`. For example, for a /// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding /// transferred assets to the appropriate receiver addresses. -pub(super) fn act_on( - store: &mut impl Store, +pub(super) fn act_on( + storage: &mut Storage, event: &EthereumEvent, -) -> Result> { +) -> Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ match &event { EthereumEvent::TransfersToNamada { transfers, .. } => { - act_on_transfers_to_namada(store, transfers) + act_on_transfers_to_namada(storage, transfers) } _ => { tracing::debug!("No actions taken for event"); @@ -28,10 +32,14 @@ pub(super) fn act_on( } } -fn act_on_transfers_to_namada( - store: &mut impl Store, +fn act_on_transfers_to_namada( + storage: &mut Storage, transfers: &[TransferToNamada], -) -> Result> { +) -> Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ let mut changed_keys = BTreeSet::default(); for TransferToNamada { amount, @@ -41,7 +49,7 @@ fn act_on_transfers_to_namada( { let keys: wrapped_erc20s::Keys = asset.into(); let balance_key = keys.balance(receiver); - update::amount(store, &balance_key, |balance| { + update::amount(storage, &balance_key, |balance| { tracing::debug!( %balance_key, ?balance, @@ -57,7 +65,7 @@ fn act_on_transfers_to_namada( _ = changed_keys.insert(balance_key); let supply_key = keys.supply(); - update::amount(store, &supply_key, |supply| { + update::amount(storage, &supply_key, |supply| { tracing::debug!( %supply_key, ?supply, @@ -77,7 +85,11 @@ fn act_on_transfers_to_namada( #[cfg(test)] mod tests { + use std::str::FromStr; + + use assert_matches::assert_matches; use borsh::BorshSerialize; + use namada::ledger::storage::testing::TestStorage; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_eth_address, arbitrary_keccak_hash, arbitrary_nonce, @@ -86,13 +98,12 @@ mod tests { use namada::types::token::Amount; use super::*; - use crate::node::ledger::protocol::transactions::store::testing::FakeStore; #[test] /// Test that we do not make any changes to storage when acting on most /// events fn test_act_on_does_nothing_for_other_events() { - let mut store = FakeStore::default(); + let mut storage = TestStorage::default(); let events = vec![ EthereumEvent::NewContract { name: "bridge".to_string(), @@ -118,10 +129,11 @@ mod tests { ]; for event in events.iter() { - act_on(&mut store, event).unwrap(); - - assert!( - store.values.is_empty(), + act_on(&mut storage, event).unwrap(); + let root = Key::from_str("").unwrap(); + assert_eq!( + storage.iter_prefix(&root).0.count(), + 0, "storage changed unexpectedly while acting on event: {:#?}", event ); @@ -132,7 +144,7 @@ mod tests { /// Test that storage is indeed changed when we act on a non-empty /// TransfersToNamada batch fn test_act_on_changes_storage_for_transfers_to_namada() { - let mut store = FakeStore::default(); + let mut storage = TestStorage::default(); let amount = Amount::from(100); let receiver = address::testing::established_address_1(); let transfers = vec![TransferToNamada { @@ -145,15 +157,16 @@ mod tests { transfers, }; - act_on(&mut store, &event).unwrap(); + act_on(&mut storage, &event).unwrap(); - assert!(!store.values.is_empty()); + let root = Key::from_str("").unwrap(); + assert_eq!(storage.iter_prefix(&root).0.count(), 2); } #[test] /// Test acting on a single transfer and minting the first ever wDAI fn test_act_on_transfers_to_namada_mints_wdai() { - let mut store = FakeStore::default(); + let mut storage = TestStorage::default(); let amount = Amount::from(100); let receiver = address::testing::established_address_1(); @@ -163,20 +176,19 @@ mod tests { receiver: receiver.clone(), }]; - act_on_transfers_to_namada(&mut store, &transfers).unwrap(); + act_on_transfers_to_namada(&mut storage, &transfers).unwrap(); let wdai: wrapped_erc20s::Keys = (&DAI_ERC20_ETH_ADDRESS).into(); let receiver_balance_key = wdai.balance(&receiver); let wdai_supply_key = wdai.supply(); - assert_eq!(store.values.len(), 2); - assert_eq!( - store.values.get(&receiver_balance_key).unwrap(), - &amount.try_to_vec().unwrap() - ); - assert_eq!( - store.values.get(&wdai_supply_key).unwrap(), - &amount.try_to_vec().unwrap() - ); + let root = Key::from_str("").unwrap(); + assert_eq!(storage.iter_prefix(&root).0.count(), 2); + + let expected_amount = amount.try_to_vec().unwrap(); + for key in vec![receiver_balance_key, wdai_supply_key] { + let (value, _) = storage.read(&key).unwrap(); + assert_matches!(value, Some(bytes) if bytes == expected_amount); + } } } diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 8c412fa5e0..ad11edf496 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -11,6 +11,7 @@ use borsh::BorshSerialize; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::eth_msgs::Keys; +use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage; @@ -18,7 +19,7 @@ use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; -use super::store::{Store, StoreExt}; +use crate::node::ledger::shell::queries::QueriesExt; /// The keys changed while applying a protocol transaction type ChangedKeys = BTreeSet; @@ -30,10 +31,14 @@ type ChangedKeys = BTreeSet; /// /// This function is deterministic based on some existing blockchain state and /// the passed `events`. -pub(crate) fn apply_derived_tx( - store: &mut impl StoreExt, +pub(crate) fn apply_derived_tx( + storage: &mut Storage, events: Vec, -) -> Result { +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ if events.is_empty() { return Ok(TxResult::default()); } @@ -43,9 +48,9 @@ pub(crate) fn apply_derived_tx( protocol transaction" ); - let (updates, voting_powers) = get_update_data(store, events)?; + let (updates, voting_powers) = get_update_data(storage, events)?; - let changed_keys = apply_updates(store, updates, voting_powers)?; + let changed_keys = apply_updates(storage, updates, voting_powers)?; Ok(TxResult { changed_keys, @@ -55,17 +60,21 @@ pub(crate) fn apply_derived_tx( /// Constructs all needed data that may be needed for updating #EthBridge /// internal account storage based on `events`. -fn get_update_data( - store: &impl StoreExt, +fn get_update_data( + storage: &Storage, events: Vec, ) -> Result<( HashSet, HashMap, -)> { - let last_epoch = store.get_last_epoch(); +)> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let (last_epoch, _) = storage.get_last_epoch(); tracing::debug!(?last_epoch, "Got epoch of last block"); - let active_validators = store.get_active_validators(Some(last_epoch)); + let active_validators = storage.get_active_validators(Some(last_epoch)); tracing::debug!( n = active_validators.len(), "got active validators - {:#?}", @@ -88,11 +97,15 @@ fn get_update_data( } /// Apply an Ethereum state update + act on any events which are confirmed -pub(super) fn apply_updates( - store: &mut impl Store, +pub(super) fn apply_updates( + storage: &mut Storage, updates: HashSet, voting_powers: HashMap, -) -> Result { +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ tracing::debug!( updates.len = updates.len(), ?voting_powers, @@ -105,7 +118,7 @@ pub(super) fn apply_updates( // The order in which updates are applied to storage does not matter. // The final storage state will be the same regardless. let (mut changed, newly_confirmed) = - apply_update(store, update.clone(), &voting_powers)?; + apply_update(storage, update.clone(), &voting_powers)?; changed_keys.append(&mut changed); if newly_confirmed { confirmed.push(update.body); @@ -120,7 +133,7 @@ pub(super) fn apply_updates( // Right now, the order in which events are acted on does not matter. // For `TransfersToNamada` events, they can happen in any order. for event in &confirmed { - let mut changed = events::act_on(store, event)?; + let mut changed = events::act_on(storage, event)?; changed_keys.append(&mut changed); } Ok(changed_keys) @@ -128,24 +141,28 @@ pub(super) fn apply_updates( /// Apply an [`EthMsgUpdate`] to storage. Returns any keys changed and whether /// the event was newly seen. -fn apply_update( - store: &mut impl Store, +fn apply_update( + storage: &mut Storage, update: EthMsgUpdate, voting_powers: &HashMap, -) -> Result<(ChangedKeys, bool)> { +) -> Result<(ChangedKeys, bool)> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ let eth_msg_keys = Keys::from(&update.body); // we arbitrarily look at whether the seen key is present to // determine if the /eth_msg already exists in storage, but maybe there // is a less arbitrary way to do this - let exists_in_storage = store.has_key(ð_msg_keys.seen())?; + let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; let (eth_msg_post, changed) = if !exists_in_storage { calculate_new_eth_msg(update, voting_powers)? } else { - calculate_updated_eth_msg(store, update, voting_powers)? + calculate_updated_eth_msg(storage, update, voting_powers)? }; - write_eth_msg(store, ð_msg_keys, ð_msg_post)?; + write_eth_msg(storage, ð_msg_keys, ð_msg_post)?; Ok((changed, !exists_in_storage)) } @@ -184,11 +201,15 @@ fn calculate_new_eth_msg( )) } -fn calculate_updated_eth_msg( - store: &mut impl Store, +fn calculate_updated_eth_msg( + store: &mut Storage, update: EthMsgUpdate, voting_powers: &HashMap, -) -> Result<(EthMsg, ChangedKeys)> { +) -> Result<(EthMsg, ChangedKeys)> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ let eth_msg_keys = Keys::from(&update.body); tracing::debug!( %eth_msg_keys.prefix, @@ -222,16 +243,20 @@ fn calculate_diff( (eth_msg, BTreeSet::default()) } -fn write_eth_msg( - store: &mut impl Store, +fn write_eth_msg( + storage: &mut Storage, eth_msg_keys: &Keys, eth_msg: &EthMsg, -) -> Result<()> { +) -> Result<()> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ tracing::debug!("writing EthMsg - {:#?}", eth_msg); - store.write(ð_msg_keys.body(), ð_msg.body.try_to_vec()?)?; - store.write(ð_msg_keys.seen(), ð_msg.seen.try_to_vec()?)?; - store.write(ð_msg_keys.seen_by(), ð_msg.seen_by.try_to_vec()?)?; - store.write( + storage.write(ð_msg_keys.body(), ð_msg.body.try_to_vec()?)?; + storage.write(ð_msg_keys.seen(), ð_msg.seen.try_to_vec()?)?; + storage.write(ð_msg_keys.seen_by(), ð_msg.seen_by.try_to_vec()?)?; + storage.write( ð_msg_keys.voting_power(), ð_msg.voting_power.try_to_vec()?, )?; @@ -244,6 +269,7 @@ mod tests { use borsh::BorshDeserialize; use namada::ledger::eth_bridge::storage::wrapped_erc20s; + use namada::ledger::storage::testing::TestStorage; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, @@ -252,7 +278,6 @@ mod tests { use namada::types::token::Amount; use super::*; - use crate::node::ledger::protocol::transactions::store::testing::FakeStore; #[test] /// Test applying a `TransfersToNamada` batch containing a single transfer @@ -279,7 +304,7 @@ mod tests { sole_validator.clone(), FractionalVotingPower::new(1, 1).unwrap(), )]); - let mut storage = FakeStore::default(); + let mut storage = TestStorage::default(); let changed_keys = apply_updates(&mut storage, updates, voting_powers)?; @@ -297,28 +322,37 @@ mod tests { changed_keys ); - let body_bytes = storage.read(ð_msg_keys.body())?.unwrap(); + let (body_bytes, _) = storage.read(ð_msg_keys.body())?; + let body_bytes = body_bytes.unwrap(); assert_eq!(EthereumEvent::try_from_slice(&body_bytes)?, body); - let seen_bytes = storage.read(ð_msg_keys.seen())?.unwrap(); + + let (seen_bytes, _) = storage.read(ð_msg_keys.seen())?; + let seen_bytes = seen_bytes.unwrap(); assert!(bool::try_from_slice(&seen_bytes)?); - let seen_by_bytes = storage.read(ð_msg_keys.seen_by())?.unwrap(); + + let (seen_by_bytes, _) = storage.read(ð_msg_keys.seen_by())?; + let seen_by_bytes = seen_by_bytes.unwrap(); assert_eq!( Vec::
::try_from_slice(&seen_by_bytes)?, vec![sole_validator] ); - let voting_power_bytes = - storage.read(ð_msg_keys.voting_power())?.unwrap(); + + let (voting_power_bytes, _) = + storage.read(ð_msg_keys.voting_power())?; + let voting_power_bytes = voting_power_bytes.unwrap(); assert_eq!(<(u64, u64)>::try_from_slice(&voting_power_bytes)?, (1, 1)); - let wrapped_erc20_balance_bytes = storage - .read(&wrapped_erc20_keys.balance(&receiver))? - .unwrap(); + let (wrapped_erc20_balance_bytes, _) = + storage.read(&wrapped_erc20_keys.balance(&receiver))?; + let wrapped_erc20_balance_bytes = wrapped_erc20_balance_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_balance_bytes)?, amount ); - let wrapped_erc20_supply_bytes = - storage.read(&wrapped_erc20_keys.supply())?.unwrap(); + + let (wrapped_erc20_supply_bytes, _) = + storage.read(&wrapped_erc20_keys.supply())?; + let wrapped_erc20_supply_bytes = wrapped_erc20_supply_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_supply_bytes)?, amount diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs index 6061b017ab..d60fc1f15b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -1,17 +1,20 @@ //! Helpers for reading from storage use borsh::BorshDeserialize; use eyre::{eyre, Result}; +use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::storage; use namada::types::token::Amount; -use super::Store; - /// Returns the stored Amount, or 0 if not stored -pub(super) fn amount_or_default( - store: &impl Store, +pub(super) fn amount_or_default( + storage: &Storage, key: &storage::Key, -) -> Result { - let amount = match maybe_value(store, key)? { +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let amount = match maybe_value(storage, key)? { Some(amount) => amount, None => Amount::from(0), }; @@ -19,21 +22,29 @@ pub(super) fn amount_or_default( } /// Read some arbitrary value from storage, erroring if it's not found -pub(super) fn value( - store: &impl Store, +pub(super) fn value( + storage: &Storage, key: &storage::Key, -) -> Result { - maybe_value(store, key)?.ok_or_else(|| eyre!("no value found at {}", key)) +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + maybe_value(storage, key)?.ok_or_else(|| eyre!("no value found at {}", key)) } /// Try to read some arbitrary value from storage, returning `None` if nothing /// is read. This will still error if there is data stored at `key` but it is /// not deserializable to `T`. -pub(super) fn maybe_value( - storage: &impl Store, +pub(super) fn maybe_value( + storage: &Storage, key: &storage::Key, -) -> Result> { - let maybe_val = storage.read(key)?; +) -> Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let (maybe_val, _) = storage.read(key)?; let bytes = match maybe_val { Some(bytes) => bytes, None => return Ok(None), @@ -46,16 +57,15 @@ pub(super) fn maybe_value( mod tests { use assert_matches::assert_matches; use borsh::BorshSerialize; + use namada::ledger::storage::testing::TestStorage; use namada::types::storage; use namada::types::token::Amount; use crate::node::ledger::protocol::transactions::ethereum_events::read; - use crate::node::ledger::protocol::transactions::store::testing::FakeStore; - use crate::node::ledger::protocol::transactions::store::Store; #[test] fn test_amount_returns_zero_for_uninitialized_storage() { - let fake_storage = FakeStore::default(); + let fake_storage = TestStorage::default(); let a = read::amount_or_default( &fake_storage, &storage::Key::parse( @@ -72,7 +82,7 @@ mod tests { fn test_amount_returns_stored_amount() { let key = storage::Key::parse("some arbitrary key").unwrap(); let amount = Amount::from(1_000_000); - let mut fake_storage = FakeStore::default(); + let mut fake_storage = TestStorage::default(); fake_storage .write(&key, amount.try_to_vec().unwrap()) .unwrap(); @@ -85,7 +95,7 @@ mod tests { fn test_amount_errors_if_not_amount() { let key = storage::Key::parse("some arbitrary key").unwrap(); let amount = "not an Amount type"; - let mut fake_storage = FakeStore::default(); + let mut fake_storage = TestStorage::default(); fake_storage.write(&key, amount.as_bytes()).unwrap(); assert_matches!(read::amount_or_default(&fake_storage, &key), Err(_)); diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index 235557d64d..8dfb2fe948 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -1,17 +1,20 @@ //! Helpers for writing to storage use borsh::{BorshDeserialize, BorshSerialize}; use eyre::Result; +use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::storage; use namada::types::token::Amount; -use super::Store; - /// Reads the `Amount` from key, applies update then writes it back -pub(super) fn amount( - store: &mut impl Store, +pub(super) fn amount( + store: &mut Storage, key: &storage::Key, update: impl Fn(&mut Amount), -) -> Result { +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ let mut amount = super::read::amount_or_default(store, key)?; update(&mut amount); store.write(key, amount.try_to_vec()?)?; @@ -20,11 +23,15 @@ pub(super) fn amount( #[allow(dead_code)] /// Reads an arbitrary value, applies update then writes it back -pub(super) fn value( - store: &mut impl Store, +pub(super) fn value( + store: &mut Storage, key: &storage::Key, update: impl Fn(&mut T), -) -> Result { +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ let mut value = super::read::value(store, key)?; update(&mut value); store.write(key, value.try_to_vec()?)?; @@ -35,26 +42,24 @@ pub(super) fn value( mod tests { use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{eyre, Result}; + use namada::ledger::storage::testing::TestStorage; use namada::types::storage; - use crate::node::ledger::protocol::transactions::store::testing::FakeStore; - use crate::node::ledger::protocol::transactions::store::Store; - #[test] /// Test updating a value fn test_value() -> Result<()> { let key = storage::Key::parse("some arbitrary key") .expect("could not set up test"); let value = 21; - let mut fake_storage = FakeStore::default(); + let mut storage = TestStorage::default(); let serialized = value.try_to_vec().expect("could not set up test"); - fake_storage + storage .write(&key, serialized) .expect("could not set up test"); - super::value(&mut fake_storage, &key, |v: &mut i32| *v *= 2)?; + super::value(&mut storage, &key, |v: &mut i32| *v *= 2)?; - let new_val = fake_storage.read(&key)?; + let (new_val, _) = storage.read(&key)?; let new_val = match new_val { Some(new_val) => ::try_from_slice(&new_val)?, None => return Err(eyre!("no value found")), diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 5d0f477e29..381dda4fee 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -5,4 +5,3 @@ //! natively rather than via the wasm environment as happens with regular //! transactions. pub(super) mod ethereum_events; -pub(super) mod store; diff --git a/apps/src/lib/node/ledger/protocol/transactions/store.rs b/apps/src/lib/node/ledger/protocol/transactions/store.rs deleted file mode 100644 index 8d633becf8..0000000000 --- a/apps/src/lib/node/ledger/protocol/transactions/store.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Interfaces for interacting with blockchain state as part of applying a -//! protocol transaction. - -use std::collections::BTreeSet; - -use eyre::Result; -use namada::ledger; -use namada::ledger::pos::types::WeightedValidator; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; -use namada::types::address::Address; -use namada::types::storage::{self, Epoch}; - -use crate::node::ledger::shell::queries::QueriesExt; - -/// Storage functionality needed for applying state changes required by a -/// protocol transaction. We don't need to know about the gas cost of changes or -/// the like as gas is not charged for protocol transactions. -pub(crate) trait Store { - /// Returns some value stored at `key`, or `None` if no value is stored - /// there. - fn read(&self, key: &storage::Key) -> Result>>; - /// Check if the given key is present in storage. - fn has_key(&self, key: &storage::Key) -> Result; - /// Write a value to `key` - fn write( - &mut self, - key: &storage::Key, - value: impl AsRef<[u8]> + Clone, - ) -> Result<()>; -} - -/// Higher level API for the storage that might be used when applying protocol -/// transactions -pub(crate) trait StoreExt: Store { - fn get_last_epoch(&self) -> Epoch; - fn get_active_validators( - &self, - epoch: Option, - ) -> BTreeSet>; -} - -/// Our handle on blockchain state via a [`ledger::storage::Storage`] -pub(crate) struct LedgerStore<'a, D, H> -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - native: &'a mut ledger::storage::Storage, -} - -impl<'a, D, H> From<&'a mut ledger::storage::Storage> - for LedgerStore<'a, D, H> -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - fn from(native: &'a mut ledger::storage::Storage) -> Self { - Self { native } - } -} - -impl<'a, D, H> Store for LedgerStore<'a, D, H> -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - fn read(&self, key: &storage::Key) -> Result>> { - let (maybe_val, _) = self.native.read(key)?; - Ok(maybe_val) - } - - fn has_key(&self, key: &storage::Key) -> Result { - let (has_key, _) = self.native.has_key(key)?; - Ok(has_key) - } - - fn write( - &mut self, - key: &storage::Key, - value: impl AsRef<[u8]> + Clone, - ) -> Result<()> { - _ = self.native.write(key, value)?; - Ok(()) - } -} - -impl<'a, D, H> StoreExt for LedgerStore<'a, D, H> -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - fn get_last_epoch(&self) -> Epoch { - let (last_epoch, _) = self.native.get_last_epoch(); - last_epoch - } - - fn get_active_validators( - &self, - epoch: Option, - ) -> BTreeSet> { - self.native.get_active_validators(epoch) - } -} - -#[allow(missing_docs)] -/// Test helpers -#[cfg(any(test, feature = "testing"))] -pub mod testing { - use std::collections::HashMap; - - use eyre::Result; - use namada::types::storage; - - use super::*; - - /// Very simple fake storage for use in tests. In-memory map of - /// [`storage::Key`]s to raw bytes. - #[derive(Default)] - pub struct FakeStore { - pub values: HashMap>, - } - - impl Store for FakeStore { - fn read(&self, key: &storage::Key) -> Result>> { - let val = self.values.get(key); - match val { - Some(val) => Ok(Some(val.to_owned())), - None => Ok(None), - } - } - - fn has_key(&self, key: &storage::Key) -> Result { - Ok(self.values.contains_key(key)) - } - - fn write( - &mut self, - key: &storage::Key, - value: impl AsRef<[u8]> + Clone, - ) -> Result<()> { - _ = self.values.insert(key.clone(), value.as_ref().to_vec()); - Ok(()) - } - } -} From bf23637ba65843198a51c76de31a586d400a83a5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 22 Sep 2022 11:29:45 +0100 Subject: [PATCH 0779/1995] Fix rustdocs --- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index ad11edf496..3317eb3a8c 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -1,4 +1,6 @@ -//! Code for handling [`ProtocolTxType::EthereumEvents`] transactions. +//! Code for handling +//! [`namada::types::transaction::protocol::ProtocolTxType::EthereumEvents`] +//! transactions. mod eth_msgs; mod events; mod read; From 3a16774ea554aed7f0fe1d7324c154244f553f72 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 22 Sep 2022 16:26:03 +0100 Subject: [PATCH 0780/1995] Changing EthMsgUpdate::seen_by to (Address, BlockHeight) --- .../transactions/ethereum_events/eth_msgs.rs | 12 +++++++++--- .../protocol/transactions/ethereum_events/mod.rs | 14 +++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index fb73dd1f3d..71c01f1c29 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; +use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; @@ -26,7 +27,9 @@ pub struct EthMsgUpdate { /// we can derive [`Hash`] for [`EthMsgUpdate`]. This also conveniently /// orders addresses in the order in which they should be stored in /// blockchain storage. - pub seen_by: BTreeSet
, + // NOTE(feature = "abcipp"): This can just become BTreeSet
because + // BlockHeight will always be the previous block + pub seen_by: BTreeSet<(Address, BlockHeight)>, } impl From for EthMsgUpdate { @@ -35,7 +38,7 @@ impl From for EthMsgUpdate { ) -> Self { Self { body: event, - seen_by: signers.into_iter().map(|(address, _)| address).collect(), + seen_by: signers.into_iter().collect(), } } } @@ -82,7 +85,10 @@ mod tests { }; let expected = EthMsgUpdate { body: event, - seen_by: BTreeSet::from_iter(vec![sole_validator]), + seen_by: BTreeSet::from_iter(vec![( + sole_validator, + BlockHeight(100), + )]), }; let update: EthMsgUpdate = with_signers.into(); diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 3317eb3a8c..87374499d2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -176,7 +176,7 @@ fn calculate_new_eth_msg( tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); let mut seen_by_voting_power = FractionalVotingPower::default(); - for validator in &update.seen_by { + for (validator, _) in &update.seen_by { match voting_powers.get(validator) { Some(voting_power) => seen_by_voting_power += voting_power, None => { @@ -196,7 +196,11 @@ fn calculate_new_eth_msg( voting_power: seen_by_voting_power, // the below `.collect()` is deterministic and will result in a // sorted vector as `update.seen_by` is a [`BTreeSet`] - seen_by: update.seen_by.into_iter().collect(), + seen_by: update + .seen_by + .into_iter() + .map(|(validator, _)| validator) + .collect(), seen: newly_confirmed, }, eth_msg_keys.into_iter().collect(), @@ -278,6 +282,7 @@ mod tests { }; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::token::Amount; + use storage::BlockHeight; use super::*; @@ -299,7 +304,10 @@ mod tests { }; let update = EthMsgUpdate { body: body.clone(), - seen_by: BTreeSet::from_iter(vec![sole_validator.clone()]), + seen_by: BTreeSet::from_iter(vec![( + sole_validator.clone(), + BlockHeight(100), + )]), }; let updates = HashSet::from_iter(vec![update]); let voting_powers = HashMap::from_iter(vec![( From b900f38a0cda7dc3a4c4113372d6ce5eb72ce6ab Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 11:16:13 +0100 Subject: [PATCH 0781/1995] Make get_update_data use BlockHeight --- .../transactions/ethereum_events/mod.rs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 87374499d2..9f20a2a006 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -16,7 +16,7 @@ use namada::ledger::eth_bridge::storage::eth_msgs::Keys; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; -use namada::types::storage; +use namada::types::storage::{self, BlockHeight}; use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; @@ -52,7 +52,14 @@ where let (updates, voting_powers) = get_update_data(storage, events)?; - let changed_keys = apply_updates(storage, updates, voting_powers)?; + let changed_keys = apply_updates( + storage, + updates, + voting_powers + .into_iter() + .map(|((addr, _), voting_power)| (addr, voting_power)) + .collect(), + )?; Ok(TxResult { changed_keys, @@ -67,16 +74,26 @@ fn get_update_data( events: Vec, ) -> Result<( HashSet, - HashMap, + HashMap<(Address, BlockHeight), FractionalVotingPower>, )> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let (last_epoch, _) = storage.get_last_epoch(); - tracing::debug!(?last_epoch, "Got epoch of last block"); + // TODO: this assumes all events are from the last block height, and ignores + // the block height that is actually in them + let last_block_height = storage.last_height; + let last_block_epoch = storage + .get_epoch(last_block_height) + .expect("The epoch of the last block height should always be known"); + tracing::debug!( + ?last_block_height, + ?last_block_epoch, + "Got epoch of last block" + ); - let active_validators = storage.get_active_validators(Some(last_epoch)); + let active_validators = + storage.get_active_validators(Some(last_block_epoch)); tracing::debug!( n = active_validators.len(), "got active validators - {:#?}", @@ -95,6 +112,13 @@ where let updates = events.into_iter().map(Into::::into).collect(); + // TODO: temporarily using last block height always + let voting_powers = voting_powers + .into_iter() + .map(|(validator, voting_power)| { + ((validator, last_block_height), voting_power) + }) + .collect(); Ok((updates, voting_powers)) } From 1d87c44b7aa37f9dbd0329af2dc104b231ccf52b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 11:39:44 +0100 Subject: [PATCH 0782/1995] Make apply_update use BlockHeight --- .../transactions/ethereum_events/mod.rs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 9f20a2a006..129efb5d6f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -52,14 +52,7 @@ where let (updates, voting_powers) = get_update_data(storage, events)?; - let changed_keys = apply_updates( - storage, - updates, - voting_powers - .into_iter() - .map(|((addr, _), voting_power)| (addr, voting_power)) - .collect(), - )?; + let changed_keys = apply_updates(storage, updates, voting_powers)?; Ok(TxResult { changed_keys, @@ -126,7 +119,7 @@ where pub(super) fn apply_updates( storage: &mut Storage, updates: HashSet, - voting_powers: HashMap, + voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -170,7 +163,7 @@ where fn apply_update( storage: &mut Storage, update: EthMsgUpdate, - voting_powers: &HashMap, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result<(ChangedKeys, bool)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -194,14 +187,16 @@ where fn calculate_new_eth_msg( update: EthMsgUpdate, - voting_powers: &HashMap, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result<(EthMsg, ChangedKeys)> { let eth_msg_keys = Keys::from(&update.body); tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); let mut seen_by_voting_power = FractionalVotingPower::default(); - for (validator, _) in &update.seen_by { - match voting_powers.get(validator) { + for (validator, block_height) in &update.seen_by { + match voting_powers + .get(&(validator.to_owned(), block_height.to_owned())) + { Some(voting_power) => seen_by_voting_power += voting_power, None => { return Err(eyre!( @@ -234,7 +229,7 @@ fn calculate_new_eth_msg( fn calculate_updated_eth_msg( store: &mut Storage, update: EthMsgUpdate, - voting_powers: &HashMap, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result<(EthMsg, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -264,7 +259,7 @@ where fn calculate_diff( eth_msg: EthMsg, _update: EthMsgUpdate, - _voting_powers: &HashMap, + _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> (EthMsg, ChangedKeys) { tracing::warn!( "Updating Ethereum events is not yet implemented, so this Ethereum \ @@ -335,7 +330,7 @@ mod tests { }; let updates = HashSet::from_iter(vec![update]); let voting_powers = HashMap::from_iter(vec![( - sole_validator.clone(), + (sole_validator.clone(), BlockHeight(100)), FractionalVotingPower::new(1, 1).unwrap(), )]); let mut storage = TestStorage::default(); From dc544396c324b83de22dfca5c99ee89d14277f6a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 11:44:01 +0100 Subject: [PATCH 0783/1995] Make get_voters_for_events use BlockHeight --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 9 ++++++--- .../protocol/transactions/ethereum_events/utils.rs | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 129efb5d6f..18695e2028 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -74,7 +74,8 @@ where H: 'static + StorageHasher + Sync, { // TODO: this assumes all events are from the last block height, and ignores - // the block height that is actually in them + // the block height that is actually in them, for the purpose of fetching + // active validators let last_block_height = storage.last_height; let last_block_epoch = storage .get_epoch(last_block_height) @@ -96,8 +97,10 @@ where let voters = utils::get_voters_for_events(events.iter()); tracing::debug!(?voters, "Got validators who voted on at least one event"); - let voting_powers = - utils::get_voting_powers_for_selected(&active_validators, voters)?; + let voting_powers = utils::get_voting_powers_for_selected( + &active_validators, + voters.into_iter().map(|(addr, _)| addr).collect(), + )?; tracing::debug!( ?voting_powers, "got voting powers for relevant validators" diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 6623df2602..4d705e3ceb 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -3,16 +3,16 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use eyre::eyre; use namada::ledger::pos::types::{VotingPower, WeightedValidator}; use namada::types::address::Address; +use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; /// Gets all the voters from the given events. pub(super) fn get_voters_for_events<'a>( events: impl Iterator, -) -> HashSet
{ +) -> HashSet<(Address, BlockHeight)> { events.fold(HashSet::new(), |mut validators, event| { - validators - .extend(event.signers.iter().map(|(addr, _)| addr.to_owned())); + validators.extend(event.signers.iter().cloned()); validators }) } From dc90fc8272c75030acade1fe7d7015aa28d0bb87 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 12:46:24 +0100 Subject: [PATCH 0784/1995] Use BlockHeight everywhere --- .../transactions/ethereum_events/mod.rs | 58 ++++---- .../transactions/ethereum_events/utils.rs | 134 ++++++++++++------ 2 files changed, 119 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 18695e2028..7f70e88496 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -7,12 +7,13 @@ mod read; mod update; mod utils; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::BorshSerialize; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::eth_msgs::Keys; +use namada::ledger::pos::types::WeightedValidator; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; @@ -60,6 +61,25 @@ where }) } +fn get_active_validators( + storage: &Storage, + block_heights: HashSet, +) -> BTreeMap>> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let mut active_validators = BTreeMap::default(); + for height in block_heights.into_iter() { + let epoch = storage.get_epoch(height).expect( + "The epoch of the last block height should always be known", + ); + _ = active_validators + .insert(height, storage.get_active_validators(Some(epoch))); + } + active_validators +} + /// Constructs all needed data that may be needed for updating #EthBridge /// internal account storage based on `events`. fn get_update_data( @@ -73,34 +93,21 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - // TODO: this assumes all events are from the last block height, and ignores - // the block height that is actually in them, for the purpose of fetching - // active validators - let last_block_height = storage.last_height; - let last_block_epoch = storage - .get_epoch(last_block_height) - .expect("The epoch of the last block height should always be known"); - tracing::debug!( - ?last_block_height, - ?last_block_epoch, - "Got epoch of last block" - ); + let voters = utils::get_votes_for_events(events.iter()); + tracing::debug!(?voters, "Got validators who voted on at least one event"); - let active_validators = - storage.get_active_validators(Some(last_block_epoch)); + let active_validators = get_active_validators( + storage, + voters.iter().map(|(_, h)| h.to_owned()).collect(), + ); tracing::debug!( n = active_validators.len(), "got active validators - {:#?}", active_validators, ); - let voters = utils::get_voters_for_events(events.iter()); - tracing::debug!(?voters, "Got validators who voted on at least one event"); - - let voting_powers = utils::get_voting_powers_for_selected( - &active_validators, - voters.into_iter().map(|(addr, _)| addr).collect(), - )?; + let voting_powers = + utils::get_voting_powers_for_selected(&active_validators, voters)?; tracing::debug!( ?voting_powers, "got voting powers for relevant validators" @@ -108,13 +115,6 @@ where let updates = events.into_iter().map(Into::::into).collect(); - // TODO: temporarily using last block height always - let voting_powers = voting_powers - .into_iter() - .map(|(validator, voting_power)| { - ((validator, last_block_height), voting_power) - }) - .collect(); Ok((updates, voting_powers)) } diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 4d705e3ceb..0b3caae68c 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -1,6 +1,7 @@ -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; +use itertools::Itertools; use namada::ledger::pos::types::{VotingPower, WeightedValidator}; use namada::types::address::Address; use namada::types::storage::BlockHeight; @@ -8,7 +9,7 @@ use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; /// Gets all the voters from the given events. -pub(super) fn get_voters_for_events<'a>( +pub(super) fn get_votes_for_events<'a>( events: impl Iterator, ) -> HashSet<(Address, BlockHeight)> { events.fold(HashSet::new(), |mut validators, event| { @@ -20,36 +21,67 @@ pub(super) fn get_voters_for_events<'a>( /// Gets the voting power of `selected` from `all_active`. Errors if a /// `selected` validator is not found in `all_active`. pub(super) fn get_voting_powers_for_selected( - all_active: &BTreeSet>, - selected: HashSet
, -) -> eyre::Result> { - let total_voting_power = sum_voting_powers(all_active); - let voting_powers: HashMap = all_active - .iter() - .filter(|validator| selected.contains(&validator.address)) - .map(|validator| { - // TODO: get rid of .unwrap() call in here - ( - validator.address.to_owned(), - FractionalVotingPower::new( - validator.voting_power.into(), - total_voting_power.into(), - ) - .unwrap(), - ) - }) - .collect(); - for validator in &selected { - if voting_powers.get(validator).is_none() { - return Err(eyre!( - "couldn't get voting power for validator {}", - validator, - )); - } - } + all_active: &BTreeMap>>, + selected: HashSet<(Address, BlockHeight)>, +) -> eyre::Result> { + let total_voting_powers = sum_voting_powers_for_block_heights(all_active); + let voting_powers = selected + .into_iter() + .map( + |(addr, height)| -> eyre::Result<( + (Address, BlockHeight), + FractionalVotingPower, + )> { + let active_validators = + all_active.get(&height).ok_or_else(|| { + eyre!( + "No active validators found for height {}", + height + ) + })?; + let individual_voting_power = active_validators + .iter() + .find(|&v| v.address == addr) + .ok_or_else(|| { + eyre!( + "No active validator found with address {} for \ + height {}", + addr, + height + ) + })? + .voting_power; + let total_voting_power = total_voting_powers + .get(&height) + .ok_or_else(|| { + eyre!( + "No total voting power provided for height {}", + height + ) + })? + .to_owned(); + Ok(( + (addr, height), + FractionalVotingPower::new( + individual_voting_power.into(), + total_voting_power.into(), + )?, + )) + }, + ) + .try_collect()?; Ok(voting_powers) } +pub(super) fn sum_voting_powers_for_block_heights( + validators: &BTreeMap>>, +) -> BTreeMap { + validators + .iter() + .map(|(h, vs)| (h.to_owned(), sum_voting_powers(vs))) + .collect() +} + pub(super) fn sum_voting_powers( validators: &BTreeSet>, ) -> VotingPower { @@ -80,9 +112,14 @@ mod tests { voting_power, address: sole_validator.clone(), }; - let validators = HashSet::from_iter(vec![sole_validator.clone()]); - let active_validators = - BTreeSet::from_iter(vec![weighted_sole_validator]); + let validators = HashSet::from_iter(vec![( + sole_validator.clone(), + BlockHeight(100), + )]); + let active_validators = BTreeMap::from_iter(vec![( + BlockHeight(100), + BTreeSet::from_iter(vec![weighted_sole_validator]), + )]); let result = get_voting_powers_for_selected(&active_validators, validators); @@ -92,7 +129,7 @@ mod tests { Err(error) => panic!("error: {:?}", error), }; assert_eq!(voting_powers.len(), 1); - assert_matches!(voting_powers.get(&sole_validator), Some(v) if *v == FractionalVotingPower::new(1, 1).unwrap()); + assert_matches!(voting_powers.get(&(sole_validator, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(1, 1).unwrap()); } #[test] @@ -106,10 +143,14 @@ mod tests { voting_power, address: present_validator.clone(), }; - let validators = - HashSet::from_iter(vec![present_validator, missing_validator]); - let active_validators = - BTreeSet::from_iter(vec![weighted_present_validator]); + let validators = HashSet::from_iter(vec![ + (present_validator.clone(), BlockHeight(100)), + (missing_validator.clone(), BlockHeight(100)), + ]); + let active_validators = BTreeMap::from_iter(vec![( + BlockHeight(100), + BTreeSet::from_iter(vec![weighted_present_validator]), + )]); let result = get_voting_powers_for_selected(&active_validators, validators); @@ -133,12 +174,17 @@ mod tests { voting_power: voting_power_2, address: validator_2.clone(), }; - let validators = - HashSet::from_iter(vec![validator_1.clone(), validator_2.clone()]); - let active_validators = BTreeSet::from_iter(vec![ - weighted_validator_1, - weighted_validator_2, + let validators = HashSet::from_iter(vec![ + (validator_1.clone(), BlockHeight(100)), + (validator_2.clone(), BlockHeight(100)), ]); + let active_validators = BTreeMap::from_iter(vec![( + BlockHeight(100), + BTreeSet::from_iter(vec![ + weighted_validator_1, + weighted_validator_2, + ]), + )]); let result = get_voting_powers_for_selected(&active_validators, validators); @@ -148,8 +194,8 @@ mod tests { Err(error) => panic!("error: {:?}", error), }; assert_eq!(voting_powers.len(), 2); - assert_matches!(voting_powers.get(&validator_1), Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap()); - assert_matches!(voting_powers.get(&validator_2), Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap()); + assert_matches!(voting_powers.get(&(validator_1, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap()); + assert_matches!(voting_powers.get(&(validator_2, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap()); } #[test] From 9457ddf24b34cd0c34f9575820f6e508df78d90f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 15:34:06 +0100 Subject: [PATCH 0785/1995] Remove redundant clones --- .../ledger/protocol/transactions/ethereum_events/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 0b3caae68c..832f98d709 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -144,8 +144,8 @@ mod tests { address: present_validator.clone(), }; let validators = HashSet::from_iter(vec![ - (present_validator.clone(), BlockHeight(100)), - (missing_validator.clone(), BlockHeight(100)), + (present_validator, BlockHeight(100)), + (missing_validator, BlockHeight(100)), ]); let active_validators = BTreeMap::from_iter(vec![( BlockHeight(100), From 018f52ebfbd70299d33d5bfd301e1739258cb2d5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 23 Sep 2022 15:38:56 +0100 Subject: [PATCH 0786/1995] Split up get_update_data to fix clippy warning --- .../transactions/ethereum_events/mod.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 7f70e88496..5115a4c5e0 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -51,7 +51,9 @@ where protocol transaction" ); - let (updates, voting_powers) = get_update_data(storage, events)?; + let voting_powers = get_voting_powers(storage, &events)?; + + let updates = events.into_iter().map(Into::::into).collect(); let changed_keys = apply_updates(storage, updates, voting_powers)?; @@ -80,15 +82,12 @@ where active_validators } -/// Constructs all needed data that may be needed for updating #EthBridge -/// internal account storage based on `events`. -fn get_update_data( +/// Constructs a map of all validators who voted for an event to their +/// fractional voting power for block heights at which they voted for an event +fn get_voting_powers( storage: &Storage, - events: Vec, -) -> Result<( - HashSet, - HashMap<(Address, BlockHeight), FractionalVotingPower>, -)> + events: &[MultiSignedEthEvent], +) -> Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -113,9 +112,7 @@ where "got voting powers for relevant validators" ); - let updates = events.into_iter().map(Into::::into).collect(); - - Ok((updates, voting_powers)) + Ok(voting_powers) } /// Apply an Ethereum state update + act on any events which are confirmed From c7507006d216dda96909eb438f0dcef6377d4b6e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 09:28:44 +0100 Subject: [PATCH 0787/1995] Remove unused testing functions --- shared/src/types/ethereum_events.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 139eb47913..f27758f3b4 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -277,9 +277,7 @@ pub mod testing { use namada_proof_of_stake::types::VotingPower; use super::*; - use crate::types::storage::Epoch; use crate::types::token::Amount; - use crate::types::voting_power::FractionalVotingPower; pub const DAI_ERC20_ETH_ADDRESS_CHECKSUMMED: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; @@ -298,10 +296,6 @@ pub mod testing { DAI_ERC20_ETH_ADDRESS } - pub fn arbitrary_fractional_voting_power() -> FractionalVotingPower { - FractionalVotingPower::new(1, 3).unwrap() - } - pub fn arbitrary_nonce() -> Uint { 123.into() } @@ -318,10 +312,6 @@ pub mod testing { VotingPower::from(1_000) } - pub fn arbitrary_epoch() -> Epoch { - Epoch(100) - } - /// A [`EthereumEvent::TransfersToNamada`] containing a single transfer of /// some arbitrary ERC20 pub fn arbitrary_single_transfer( From 6d02b35183ac1e10bafdfb4a5586b41ffff1f6bf Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 10:38:08 +0100 Subject: [PATCH 0788/1995] Add tests for get_votes_for_events --- .../transactions/ethereum_events/utils.rs | 65 ++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 832f98d709..aa2bb0b0ad 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -8,7 +8,8 @@ use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; -/// Gets all the voters from the given events. +/// Extract all the voters and the block heights at which they voted from the +/// given events. pub(super) fn get_votes_for_events<'a>( events: impl Iterator, ) -> HashSet<(Address, BlockHeight)> { @@ -98,7 +99,9 @@ mod tests { use assert_matches::assert_matches; use namada::types::address; - use namada::types::ethereum_events::testing::arbitrary_voting_power; + use namada::types::ethereum_events::testing::{ + arbitrary_single_transfer, arbitrary_voting_power, + }; use super::*; @@ -240,4 +243,62 @@ mod tests { assert_eq!(total, VotingPower::from(300)); } + + #[test] + /// Assert we don't return anything if we try to get the votes for an empty + /// vec of events + pub fn test_get_votes_for_events_empty() { + let events = vec![]; + let votes = get_votes_for_events(events.iter()); + assert!(votes.is_empty()); + } + + #[test] + /// Test that we correctly get the votes from a vec of events + pub fn test_get_votes_for_events() { + let events = vec![ + MultiSignedEthEvent { + event: arbitrary_single_transfer( + 1.into(), + address::testing::established_address_1(), + ), + signers: HashSet::from_iter(vec![ + ( + address::testing::established_address_1(), + BlockHeight(100), + ), + ( + address::testing::established_address_2(), + BlockHeight(102), + ), + ]), + }, + MultiSignedEthEvent { + event: arbitrary_single_transfer( + 2.into(), + address::testing::established_address_2(), + ), + signers: HashSet::from_iter(vec![ + ( + address::testing::established_address_1(), + BlockHeight(101), + ), + ( + address::testing::established_address_3(), + BlockHeight(100), + ), + ]), + }, + ]; + let votes = get_votes_for_events(events.iter()); + assert_eq!( + votes, + HashSet::from_iter(vec![ + (address::testing::established_address_1(), BlockHeight(100)), + (address::testing::established_address_1(), BlockHeight(101)), + (address::testing::established_address_2(), BlockHeight(102)), + (address::testing::established_address_3(), BlockHeight(100)) + ]) + ) + } } From 2e0aff405e732ad401de54e781fc3509d68db748 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 10:47:39 +0100 Subject: [PATCH 0789/1995] Add another test for get_voting_powers_for_selected --- .../transactions/ethereum_events/utils.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index aa2bb0b0ad..bd5dbf8d0f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -161,6 +161,21 @@ mod tests { assert!(result.is_err()); } + #[test] + /// Assert we error if we are passed an `(Address, BlockHeight)` but are not + /// given a corrseponding set of validators for the block height + fn test_get_voting_powers_for_selected_no_active_validators_for_height() { + let all_active = BTreeMap::default(); + let selected = HashSet::from_iter(vec![( + address::testing::established_address_1(), + BlockHeight(100), + )]); + + let result = get_voting_powers_for_selected(&all_active, selected); + + assert!(result.is_err()); + } + #[test] /// Test getting the voting powers for two active validators from the set of /// active validators From f784b9fea24182da4e403fe40f26373fca816bc7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 11:39:28 +0100 Subject: [PATCH 0790/1995] Add a test for apply_derived_tx --- .../transactions/ethereum_events/mod.rs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 5115a4c5e0..cdddf18ede 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -294,10 +294,14 @@ mod tests { use borsh::BorshDeserialize; use namada::ledger::eth_bridge::storage::wrapped_erc20s; + use namada::ledger::pos::namada_proof_of_stake::epoched::Epoched; + use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::ledger::pos::types::ValidatorSet; use namada::ledger::storage::testing::TestStorage; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, + DAI_ERC20_ETH_ADDRESS, }; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::token::Amount; @@ -389,4 +393,69 @@ mod tests { Ok(()) } + + #[test] + /// Test applying a single transfer via `apply_derived_tx` + fn test_apply_derived_tx() { + let mut storage = TestStorage::default(); + let sole_validator = address::testing::established_address_2(); + let receiver = address::testing::established_address_1(); + let validator_set = ValidatorSet { + active: BTreeSet::from_iter(vec![WeightedValidator { + voting_power: 100.into(), + address: sole_validator.to_owned(), + }]), + inactive: BTreeSet::default(), + }; + let validator_sets = Epoched::init_at_genesis(validator_set, 1); + storage.write_validator_set(&validator_sets); + + let event = EthereumEvent::TransfersToNamada { + nonce: 1.into(), + transfers: vec![TransferToNamada { + amount: Amount::from(100), + asset: DAI_ERC20_ETH_ADDRESS, + receiver: receiver.clone(), + }], + }; + + let result = apply_derived_tx( + &mut storage, + vec![MultiSignedEthEvent { + event: event.clone(), + signers: HashSet::from_iter(vec![( + sole_validator, + BlockHeight(100), + )]), + }], + ); + + let tx_result = match result { + Ok(tx_result) => tx_result, + Err(err) => panic!("unexpected error: {:#?}", err), + }; + + assert_eq!( + tx_result.gas_used, 0, + "No gas should be used for a derived transaction" + ); + let eth_msg_keys = Keys::from(&event); + let dai_keys = wrapped_erc20s::Keys::from(&DAI_ERC20_ETH_ADDRESS); + assert_eq!( + tx_result.changed_keys, + BTreeSet::from_iter(vec![ + eth_msg_keys.body(), + eth_msg_keys.seen(), + eth_msg_keys.seen_by(), + eth_msg_keys.voting_power(), + dai_keys.balance(&receiver), + dai_keys.supply(), + ]) + ); + assert!(tx_result.vps_result.accepted_vps.is_empty()); + assert!(tx_result.vps_result.rejected_vps.is_empty()); + assert!(tx_result.vps_result.errors.is_empty()); + assert!(tx_result.initialized_accounts.is_empty()); + assert!(tx_result.ibc_event.is_none()); + } } From 197c5839eacfa0aa02740a70099e7dc681f81bc3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 11:57:28 +0100 Subject: [PATCH 0791/1995] Factor out set_up_test_storage function --- .../transactions/ethereum_events/mod.rs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index cdddf18ede..60724d4284 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -297,7 +297,9 @@ mod tests { use namada::ledger::pos::namada_proof_of_stake::epoched::Epoched; use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::types::ValidatorSet; + use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; + use namada::ledger::storage::Sha256Hasher; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, @@ -394,21 +396,29 @@ mod tests { Ok(()) } - #[test] - /// Test applying a single transfer via `apply_derived_tx` - fn test_apply_derived_tx() { + /// Set up a `TestStorage` initialized at genesis with a single validator + fn set_up_test_storage( + sole_validator: Address, + ) -> Storage { let mut storage = TestStorage::default(); - let sole_validator = address::testing::established_address_2(); - let receiver = address::testing::established_address_1(); let validator_set = ValidatorSet { active: BTreeSet::from_iter(vec![WeightedValidator { voting_power: 100.into(), - address: sole_validator.to_owned(), + address: sole_validator, }]), inactive: BTreeSet::default(), }; let validator_sets = Epoched::init_at_genesis(validator_set, 1); storage.write_validator_set(&validator_sets); + storage + } + + #[test] + /// Test applying a single transfer via `apply_derived_tx` + fn test_apply_derived_tx() { + let sole_validator = address::testing::established_address_2(); + let mut storage = set_up_test_storage(sole_validator.clone()); + let receiver = address::testing::established_address_1(); let event = EthereumEvent::TransfersToNamada { nonce: 1.into(), From 769d109ab76359a6e6a13ed36f072231a9f85bb5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 12:09:08 +0100 Subject: [PATCH 0792/1995] Let set_up_test_storage accept multiple validators --- .../transactions/ethereum_events/mod.rs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 60724d4284..341e30ef99 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -396,16 +396,20 @@ mod tests { Ok(()) } - /// Set up a `TestStorage` initialized at genesis with a single validator + /// Set up a `TestStorage` initialized at genesis with validators of equal + /// power fn set_up_test_storage( - sole_validator: Address, + active_validators: HashSet
, ) -> Storage { let mut storage = TestStorage::default(); let validator_set = ValidatorSet { - active: BTreeSet::from_iter(vec![WeightedValidator { - voting_power: 100.into(), - address: sole_validator, - }]), + active: active_validators + .into_iter() + .map(|address| WeightedValidator { + voting_power: 100.into(), + address, + }) + .collect(), inactive: BTreeSet::default(), }; let validator_sets = Epoched::init_at_genesis(validator_set, 1); @@ -417,7 +421,9 @@ mod tests { /// Test applying a single transfer via `apply_derived_tx` fn test_apply_derived_tx() { let sole_validator = address::testing::established_address_2(); - let mut storage = set_up_test_storage(sole_validator.clone()); + let mut storage = set_up_test_storage(HashSet::from_iter(vec![ + sole_validator.clone(), + ])); let receiver = address::testing::established_address_1(); let event = EthereumEvent::TransfersToNamada { From e725f77e28bbe25b9587d20821e62e84009566e4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 12:18:46 +0100 Subject: [PATCH 0793/1995] Add test for when an event is seen but cannot be minted straightaway --- .../transactions/ethereum_events/mod.rs | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 341e30ef99..2a5fdb46ec 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -418,8 +418,10 @@ mod tests { } #[test] - /// Test applying a single transfer via `apply_derived_tx` - fn test_apply_derived_tx() { + /// Test applying a single transfer via `apply_derived_tx`, where an event + /// has enough voting power behind it for it to be applied at the same time + /// that it is recorded in storage + fn test_apply_derived_tx_record_event_and_mint() { let sole_validator = address::testing::established_address_2(); let mut storage = set_up_test_storage(HashSet::from_iter(vec![ sole_validator.clone(), @@ -474,4 +476,55 @@ mod tests { assert!(tx_result.initialized_accounts.is_empty()); assert!(tx_result.ibc_event.is_none()); } + + /// Test calling apply_derived_tx for an event that isn't backed by enough + /// voting power to be acted on immediately + #[test] + fn test_apply_derived_tx_record_event_dont_mint() { + let validator_a = address::testing::established_address_2(); + let validator_b = address::testing::established_address_3(); + let mut storage = set_up_test_storage(HashSet::from_iter(vec![ + validator_a.clone(), + validator_b.clone(), + ])); + let receiver = address::testing::established_address_1(); + + let original_event = EthereumEvent::TransfersToNamada { + nonce: 1.into(), + transfers: vec![TransferToNamada { + amount: Amount::from(100), + asset: DAI_ERC20_ETH_ADDRESS, + receiver: receiver.clone(), + }], + }; + + let result = apply_derived_tx( + &mut storage, + vec![MultiSignedEthEvent { + event: original_event.clone(), + signers: HashSet::from_iter(vec![( + validator_a, + BlockHeight(100), + )]), + }], + ); + let tx_result = match result { + Ok(tx_result) => tx_result, + Err(err) => panic!("unexpected error: {:#?}", err), + }; + + let eth_msg_keys = Keys::from(&original_event); + assert_eq!( + tx_result.changed_keys, + BTreeSet::from_iter(vec![ + eth_msg_keys.body(), + eth_msg_keys.seen(), + eth_msg_keys.seen_by(), + eth_msg_keys.voting_power(), + ]), + "The Ethereum event should have been recorded, but no minting \ + should have happened yet as it has only been seen by 1/2 the \ + voting power so far" + ); + } } From 941b3f7bc28f3a76be86451c03d1c71f2608d4c6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 12:23:15 +0100 Subject: [PATCH 0794/1995] fix: don't act on new eth msgs unless seen = true --- .../transactions/ethereum_events/mod.rs | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 2a5fdb46ec..02dc52e8f6 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -176,13 +176,20 @@ where // is a less arbitrary way to do this let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; - let (eth_msg_post, changed) = if !exists_in_storage { - calculate_new_eth_msg(update, voting_powers)? + let (eth_msg_post, changed, confirmed) = if !exists_in_storage { + let (eth_msg_post, changed) = + calculate_new_eth_msg(update, voting_powers)?; + let confirmed = eth_msg_post.seen; + (eth_msg_post, changed, confirmed) } else { - calculate_updated_eth_msg(storage, update, voting_powers)? + let (eth_msg_post, changed) = + calculate_updated_eth_msg(storage, update, voting_powers)?; + let confirmed = + eth_msg_post.seen && changed.contains(ð_msg_keys.seen()); + (eth_msg_post, changed, confirmed) }; write_eth_msg(storage, ð_msg_keys, ð_msg_post)?; - Ok((changed, !exists_in_storage)) + Ok((changed, confirmed)) } fn calculate_new_eth_msg( @@ -527,4 +534,55 @@ mod tests { voting power so far" ); } + + /// Test calling apply_derived_tx for an event that isn't backed by enough + /// voting power to be acted on immediately + #[test] + fn test_apply_derived_tx_record_event_dont_mint() { + let validator_a = address::testing::established_address_2(); + let validator_b = address::testing::established_address_3(); + let mut storage = set_up_test_storage(HashSet::from_iter(vec![ + validator_a.clone(), + validator_b.clone(), + ])); + let receiver = address::testing::established_address_1(); + + let original_event = EthereumEvent::TransfersToNamada { + nonce: 1.into(), + transfers: vec![TransferToNamada { + amount: Amount::from(100), + asset: DAI_ERC20_ETH_ADDRESS, + receiver: receiver.clone(), + }], + }; + + let result = apply_derived_tx( + &mut storage, + vec![MultiSignedEthEvent { + event: original_event.clone(), + signers: HashSet::from_iter(vec![( + validator_a, + BlockHeight(100), + )]), + }], + ); + let tx_result = match result { + Ok(tx_result) => tx_result, + Err(err) => panic!("unexpected error: {:#?}", err), + }; + + let eth_msg_keys = Keys::from(&original_event); + assert_eq!( + tx_result.changed_keys, + BTreeSet::from_iter(vec![ + eth_msg_keys.body(), + eth_msg_keys.seen(), + eth_msg_keys.seen_by(), + eth_msg_keys.voting_power(), + ]), + "The Ethereum event should have been recorded, but no minting \ + should have happened yet as it has only been seen by 1/2 the \ + voting power so far" + ); + } } From 33157035f977903e3ad168d39f44a971100576d8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 12:40:21 +0100 Subject: [PATCH 0795/1995] Rename tests --- .../transactions/ethereum_events/mod.rs | 61 ++----------------- 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 02dc52e8f6..69375054ff 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -428,7 +428,7 @@ mod tests { /// Test applying a single transfer via `apply_derived_tx`, where an event /// has enough voting power behind it for it to be applied at the same time /// that it is recorded in storage - fn test_apply_derived_tx_record_event_and_mint() { + fn test_apply_derived_tx_new_event_mint_immediately() { let sole_validator = address::testing::established_address_2(); let mut storage = set_up_test_storage(HashSet::from_iter(vec![ sole_validator.clone(), @@ -487,7 +487,7 @@ mod tests { /// Test calling apply_derived_tx for an event that isn't backed by enough /// voting power to be acted on immediately #[test] - fn test_apply_derived_tx_record_event_dont_mint() { + fn test_apply_derived_tx_new_event_dont_mint() { let validator_a = address::testing::established_address_2(); let validator_b = address::testing::established_address_3(); let mut storage = set_up_test_storage(HashSet::from_iter(vec![ @@ -496,58 +496,7 @@ mod tests { ])); let receiver = address::testing::established_address_1(); - let original_event = EthereumEvent::TransfersToNamada { - nonce: 1.into(), - transfers: vec![TransferToNamada { - amount: Amount::from(100), - asset: DAI_ERC20_ETH_ADDRESS, - receiver: receiver.clone(), - }], - }; - - let result = apply_derived_tx( - &mut storage, - vec![MultiSignedEthEvent { - event: original_event.clone(), - signers: HashSet::from_iter(vec![( - validator_a, - BlockHeight(100), - )]), - }], - ); - let tx_result = match result { - Ok(tx_result) => tx_result, - Err(err) => panic!("unexpected error: {:#?}", err), - }; - - let eth_msg_keys = Keys::from(&original_event); - assert_eq!( - tx_result.changed_keys, - BTreeSet::from_iter(vec![ - eth_msg_keys.body(), - eth_msg_keys.seen(), - eth_msg_keys.seen_by(), - eth_msg_keys.voting_power(), - ]), - "The Ethereum event should have been recorded, but no minting \ - should have happened yet as it has only been seen by 1/2 the \ - voting power so far" - ); - } - - /// Test calling apply_derived_tx for an event that isn't backed by enough - /// voting power to be acted on immediately - #[test] - fn test_apply_derived_tx_record_event_dont_mint() { - let validator_a = address::testing::established_address_2(); - let validator_b = address::testing::established_address_3(); - let mut storage = set_up_test_storage(HashSet::from_iter(vec![ - validator_a.clone(), - validator_b.clone(), - ])); - let receiver = address::testing::established_address_1(); - - let original_event = EthereumEvent::TransfersToNamada { + let event = EthereumEvent::TransfersToNamada { nonce: 1.into(), transfers: vec![TransferToNamada { amount: Amount::from(100), @@ -559,7 +508,7 @@ mod tests { let result = apply_derived_tx( &mut storage, vec![MultiSignedEthEvent { - event: original_event.clone(), + event: event.clone(), signers: HashSet::from_iter(vec![( validator_a, BlockHeight(100), @@ -571,7 +520,7 @@ mod tests { Err(err) => panic!("unexpected error: {:#?}", err), }; - let eth_msg_keys = Keys::from(&original_event); + let eth_msg_keys = Keys::from(&event); assert_eq!( tx_result.changed_keys, BTreeSet::from_iter(vec![ From 26f2091a4aa9b1f99547ceaa3116160fdb6ff0ae Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 26 Sep 2022 12:46:35 +0100 Subject: [PATCH 0796/1995] Remove unnecessary clones --- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 69375054ff..a9b24deb39 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -492,7 +492,7 @@ mod tests { let validator_b = address::testing::established_address_3(); let mut storage = set_up_test_storage(HashSet::from_iter(vec![ validator_a.clone(), - validator_b.clone(), + validator_b, ])); let receiver = address::testing::established_address_1(); @@ -501,7 +501,7 @@ mod tests { transfers: vec![TransferToNamada { amount: Amount::from(100), asset: DAI_ERC20_ETH_ADDRESS, - receiver: receiver.clone(), + receiver, }], }; From 31267ca717a214611361ecaf42bd681b838964b1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 11:03:05 +0100 Subject: [PATCH 0797/1995] Add `Hash` to KeccakHash --- shared/src/types/keccak.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 8d43ecf9c6..722024344a 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -28,6 +28,7 @@ pub enum TryFromError { Debug, PartialEq, Eq, + Hash, PartialOrd, Ord, BorshSerialize, From 3f708b7f7620a12dd0b35776657a7ef568997fac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 27 Sep 2022 10:54:59 +0000 Subject: [PATCH 0798/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 49455e617b..08abe4c67f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.aacf3881273c1d322d9f67a2fee9cf9b5cab3661969b2adc388727a9ad7564f5.wasm", - "tx_ibc.wasm": "tx_ibc.66695a390c04405759d1128612556a0d8ce85ff3c6adf50546fcd991eaee6f41.wasm", - "tx_init_account.wasm": "tx_init_account.afc109717426c966943e0bf8f8d5f064908552335c109a4b0d81a72e7faa5113.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f828560e5dd7be8a9bc25e5cfe5d258ea444ae3d097954398892562432ee0d6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2ce224b475520e48e19ba1dc70f0e01b8219c927fc9b60b7ab231fa833d09ab2.wasm", - "tx_init_validator.wasm": "tx_init_validator.4e84084907a3029d79833560b42c3ab69cf38020dc82c0a2f1ba323cbf372c6f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.788f29b1fb84835e2a905630dee2618be297bd53c68fcacf1c12fff81399dbc9.wasm", - "tx_transfer.wasm": "tx_transfer.7c84e04d3d636306bf09490744cdf4229ecc4954b10cffcff7d4beddd5f27c64.wasm", - "tx_unbond.wasm": "tx_unbond.6393cd64250c22ed82f32cfe9bde743d7ac57a9937e5ce0fc0ec8902301361f3.wasm", - "tx_update_vp.wasm": "tx_update_vp.e03daa135e498a83aabbd17109867ef138daa5e73a7d64904cb71da01c55ee06.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1cea6c13a9b78547363463e2c72c88b2cd7052e1d0da7c7bc67d6dffdbeafea2.wasm", - "tx_withdraw.wasm": "tx_withdraw.03da515f86aa2caeef33b81988f64675aef46257242cc5f42d73b986d04b94cf.wasm", - "vp_nft.wasm": "vp_nft.84395d98e82714782af42e7246bb000666ce29aa30e1952a97a87a3244fd7114.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ca4a51feebdb0313a8a87d6defd38679870e2a1be5d629979db9b11a0c60397.wasm", - "vp_token.wasm": "vp_token.227ccbdbd7401613bcd31e123e2021c12c4ed0f7ee746ba2f0296b9e18436eb0.wasm", - "vp_user.wasm": "vp_user.a174ba668bb9d868509632bed424aef09ddd016faf072f1cf5c2fcef1a468bd0.wasm" + "tx_bridge_pool.wasm": "tx_bridge_pool.dee69ea94dca815418a893081463df0f98046ff8df3b18273e12093f29c845ab.wasm", + "tx_from_intent.wasm": "tx_from_intent.794cad2b4a3865dd4ab018331016b118af47ff22162abbaad75c59084766c268.wasm", + "tx_ibc.wasm": "tx_ibc.e48421975a3f732bb1ff120c40b3a71e87122543f436d6a59639c907a4a84d57.wasm", + "tx_init_account.wasm": "tx_init_account.55bc0adac8d731dc7ea4925fc0dd3b086c8d12bd0c2bbd8f4c9193f736dbf0e3.wasm", + "tx_init_nft.wasm": "tx_init_nft.6c5c808c8b033258a6cc68cef74104074ae9ba07ddfab157924fcade45350ee7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6d457961abc7aa59ac49dd2309b038649ee63df35b4b8e98e23a675357e81853.wasm", + "tx_init_validator.wasm": "tx_init_validator.9f3e98ae9ff9302d737db6ee197fc90477b6f2edd4ff89f71d04ba1a5518f9b7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.5c395cbaedf3b7055df1f341d6bc36f70f0375b62a3957462f6399b0ebcf1bb6.wasm", + "tx_transfer.wasm": "tx_transfer.982d522e4463dcb6c763ebfc6d00f31e1afb87af4430566f5604a279bdd158ad.wasm", + "tx_unbond.wasm": "tx_unbond.f016a1a4868e57cfe7d33997e0816870da025ad7c0fb6b71ddf3931dd599765e.wasm", + "tx_update_vp.wasm": "tx_update_vp.63ff8d0142f9f521a58236514d39032fa581002751d74750cdac502796a7667a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.2d27405550e8926d76561153274419e3e12de0f6800cf0df5a5a1143f66b026f.wasm", + "tx_withdraw.wasm": "tx_withdraw.a200c6f0c5c7c9138690f32e858a0055115d06115531bfb956c09eceb1424436.wasm", + "vp_nft.wasm": "vp_nft.0dfc70e398dff7e16ef4ee9d8e7cfb3b1fccb55ff6f69b6318c6e9ea50403008.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.42c11427a99ed106169aa597c563b398a19a0f62381801beaebe46515731ba2b.wasm", + "vp_token.wasm": "vp_token.c85f437e5737f36cdb0c0158db178d1f0be476b3cf720d839251431dcf2337df.wasm", + "vp_user.wasm": "vp_user.2f3d129875637b3fa1de237917741f69c5eb42b817d9ed03a6a0b3345e2462ec.wasm" } \ No newline at end of file From abf0789609f9ff3cc22cd98abb8f455e080f5036 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 27 Sep 2022 16:50:11 +0100 Subject: [PATCH 0799/1995] Update apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs Co-authored-by: Tiago Carvalho --- .../node/ledger/protocol/transactions/ethereum_events/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index 0a23efaee8..2564830a2c 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -26,7 +26,7 @@ where act_on_transfers_to_namada(storage, transfers) } _ => { - tracing::debug!("No actions taken for event"); + tracing::debug!(?event, "No actions taken for Ethereum event"); Ok(BTreeSet::default()) } } From 30e91bf73e39a342338ef8c90caf35919c27cfa6 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 27 Sep 2022 16:52:15 +0100 Subject: [PATCH 0800/1995] Update apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs Co-authored-by: Tiago Carvalho --- .../node/ledger/protocol/transactions/ethereum_events/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index 8dfb2fe948..f93b92cf1f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -9,7 +9,7 @@ use namada::types::token::Amount; pub(super) fn amount( store: &mut Storage, key: &storage::Key, - update: impl Fn(&mut Amount), + update: impl FnOnce(&mut Amount), ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, From e2f5b9862cb90887b0a05eb3061e89b7dcb9ffad Mon Sep 17 00:00:00 2001 From: James Date: Tue, 27 Sep 2022 16:52:25 +0100 Subject: [PATCH 0801/1995] Update apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs Co-authored-by: Tiago Carvalho --- .../node/ledger/protocol/transactions/ethereum_events/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index f93b92cf1f..d6aebac3b6 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -26,7 +26,7 @@ where pub(super) fn value( store: &mut Storage, key: &storage::Key, - update: impl Fn(&mut T), + update: impl FnOnce(&mut T), ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, From 6f739227e3e205b53afe3a880f9398e053ff7db7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 17:01:44 +0100 Subject: [PATCH 0802/1995] Use named arguments in eyre! strings --- .../transactions/ethereum_events/utils.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index bd5dbf8d0f..dce8a5c08f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -35,20 +35,15 @@ pub(super) fn get_voting_powers_for_selected( )> { let active_validators = all_active.get(&height).ok_or_else(|| { - eyre!( - "No active validators found for height {}", - height - ) + eyre!("No active validators found for height {height}") })?; let individual_voting_power = active_validators .iter() .find(|&v| v.address == addr) .ok_or_else(|| { eyre!( - "No active validator found with address {} for \ - height {}", - addr, - height + "No active validator found with address {addr} \ + for height {height}" ) })? .voting_power; @@ -56,8 +51,8 @@ pub(super) fn get_voting_powers_for_selected( .get(&height) .ok_or_else(|| { eyre!( - "No total voting power provided for height {}", - height + "No total voting power provided for height \ + {height}" ) })? .to_owned(); From 4e70c38f89e3d725ecb73579498083dbf3be1648 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 15:22:38 +0100 Subject: [PATCH 0803/1995] Shorten amount_or_default code --- .../ledger/protocol/transactions/ethereum_events/read.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs index d60fc1f15b..5f377f8133 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -14,11 +14,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let amount = match maybe_value(storage, key)? { - Some(amount) => amount, - None => Amount::from(0), - }; - Ok(amount) + Ok(maybe_value(storage, key)?.unwrap_or_default()) } /// Read some arbitrary value from storage, erroring if it's not found From 8a014a67e27327f17a5db86632b696f0fba8b922 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Oct 2022 15:24:23 +0100 Subject: [PATCH 0804/1995] Update apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs Co-authored-by: Jacob Turner --- .../ledger/protocol/transactions/ethereum_events/utils.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index dce8a5c08f..b7d563d9dd 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -127,7 +127,10 @@ mod tests { Err(error) => panic!("error: {:?}", error), }; assert_eq!(voting_powers.len(), 1); - assert_matches!(voting_powers.get(&(sole_validator, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(1, 1).unwrap()); + assert_matches!( + voting_powers.get(&(sole_validator, BlockHeight(100))), + Some(v) if *v == FractionalVotingPower::new(1, 1).unwrap() + ); } #[test] From 105b9a0e68ecf865c9805ab43b24351e2f92b55e Mon Sep 17 00:00:00 2001 From: James Date: Mon, 3 Oct 2022 15:24:45 +0100 Subject: [PATCH 0805/1995] Update apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs Co-authored-by: Jacob Turner --- .../protocol/transactions/ethereum_events/utils.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index b7d563d9dd..2e2441a733 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -210,8 +210,14 @@ mod tests { Err(error) => panic!("error: {:?}", error), }; assert_eq!(voting_powers.len(), 2); - assert_matches!(voting_powers.get(&(validator_1, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap()); - assert_matches!(voting_powers.get(&(validator_2, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap()); + assert_matches!( + voting_powers.get(&(validator_1, BlockHeight(100))), + Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap() + ); + assert_matches!( + voting_powers.get(&(validator_2, BlockHeight(100))), + Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap() + ); } #[test] From 1f5650a34670036dc38859b12e4d87b0e67192fe Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 15:41:51 +0100 Subject: [PATCH 0806/1995] Run make fmt --- .../node/ledger/protocol/transactions/ethereum_events/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 2e2441a733..990e3b5fde 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -215,7 +215,7 @@ mod tests { Some(v) if *v == FractionalVotingPower::new(100, 300).unwrap() ); assert_matches!( - voting_powers.get(&(validator_2, BlockHeight(100))), + voting_powers.get(&(validator_2, BlockHeight(100))), Some(v) if *v == FractionalVotingPower::new(200, 300).unwrap() ); } From 64f73c7186d78d73f80f51a5851ff24468d9ad9b Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 16:42:43 +0200 Subject: [PATCH 0807/1995] [fix]: Switched the ethereum oracle to use a bounded channel --- .../lib/node/ledger/ethereum_node/events.rs | 2 +- .../lib/node/ledger/ethereum_node/oracle.rs | 34 +++++++++++-------- .../node/ledger/ethereum_node/test_tools.rs | 10 +++--- apps/src/lib/node/ledger/mod.rs | 10 ++++-- .../lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 25 +++++++------- .../shell/vote_extensions/eth_events.rs | 8 ++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 4 +-- 8 files changed, 52 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 0d176b875f..4071d6a0f0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -202,7 +202,7 @@ pub mod eth_events { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations >= height.clone() - self.block_height.clone() + self.confirmations <= height.clone() - self.block_height.clone() } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 28fd09ccb5..d29c3db068 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use clarity::Address; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::{Sender as BoundedSender}; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -14,7 +14,7 @@ use super::events::{signatures, PendingEvent}; use super::test_tools::mock_web3_client::Web3; /// Minimum number of confirmations needed to trust an Ethereum branch -pub(crate) const MIN_CONFIRMATIONS: u64 = 50; +pub(crate) const MIN_CONFIRMATIONS: u64 = 100; /// Dummy addresses for smart contracts const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); @@ -28,7 +28,7 @@ pub struct Oracle { client: Web3, /// A channel for sending processed and confirmed /// events to the ledger process - sender: UnboundedSender, + sender: BoundedSender, /// A channel to signal that the ledger should shut down /// because the Oracle has stopped abort: Option>, @@ -55,7 +55,7 @@ impl Oracle { /// Initialize a new [`Oracle`] pub fn new( url: &str, - sender: UnboundedSender, + sender: BoundedSender, abort: Sender<()>, ) -> Self { Self { @@ -69,12 +69,16 @@ impl Oracle { /// ledger. Returns a boolean indicating that all sent /// successfully. If false is returned, the receiver /// has hung up. - fn send(&self, events: Vec) -> bool { - events - .into_iter() - .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) - && !self.sender.is_closed() + /// + /// N.B. this will block if the internal channel buffer + /// is full. + async fn send(&self, events: Vec) -> bool { + for event in events.into_iter() { + if self.sender.send(event).await.is_err() { + return false; + } + } + !self.sender.is_closed() } /// Check if the receiver in the ledger has hung up. @@ -88,7 +92,7 @@ impl Oracle { /// processes and forwards Ethereum events to the ledger pub fn run_oracle( url: impl AsRef, - sender: UnboundedSender, + sender: BoundedSender, abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); @@ -192,7 +196,7 @@ async fn run_oracle_aux(oracle: Oracle) { } }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)) { + if !oracle.send(process_queue(&latest_block, &mut pending)).await { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" @@ -240,14 +244,14 @@ mod test_oracle { struct TestPackage { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, - eth_recv: tokio::sync::mpsc::UnboundedReceiver, + eth_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } /// Set up an oracle with a mock web3 client that we can contr fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); - let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -474,7 +478,7 @@ mod test_oracle { // increase block height so first event is confirmed but second is // not. admin_channel - .send(TestCmd::NewHeight(Uint256::from(102u32))) + .send(TestCmd::NewHeight(Uint256::from(105u32))) .expect("Test failed"); // check the correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index c6184bda99..507f9c5a3d 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -32,12 +32,12 @@ pub mod mock_oracle { use namada::types::ethereum_events::EthereumEvent; use tokio::macros::support::poll_fn; - use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; pub fn run_oracle( _: impl AsRef, - _: UnboundedSender, + _: BoundedSender, mut abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { @@ -53,7 +53,7 @@ pub mod mock_oracle { pub mod event_endpoint { use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; - use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::mpsc::Sender as BoundedSender; const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); @@ -61,7 +61,7 @@ pub mod event_endpoint { const PATH: &str = "eth_events"; pub fn start_oracle( - sender: UnboundedSender, + sender: BoundedSender, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { use warp::Filter; @@ -87,7 +87,7 @@ pub mod event_endpoint { } }; tracing::debug!("Serialized event - {:#?}", event); - match sender.send(event) { + match sender.try_send(event) { Ok(()) => warp::reply::with_status( "OK", warp::http::StatusCode::OK, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 369c938c1d..657f73e0b4 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -44,6 +44,10 @@ const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; /// Env. var to set a number of Rayon global worker threads const ENV_VAR_RAYON_THREADS: &str = "ANOMA_RAYON_THREADS"; +/// The maximum number of Ethereum events the channel between +/// the oracle and the shell can hold. +const ORACLE_CHANNEL_BUFFER_SIZE: usize = 1000; + // Until ABCI++ is ready, the shim provides the service implementation. // We will add this part back in once the shim is no longer needed. //``` @@ -384,7 +388,7 @@ async fn run_aux_setup( /// a new OS thread, to drive the ABCI server. fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, - eth_receiver: Option>, + eth_receiver: Option>, wasm_dir: PathBuf, setup_data: RunAuxSetup, config: config::Ledger, @@ -610,7 +614,7 @@ async fn start_ethereum_node( config: &config::Ledger, ) -> ( task::JoinHandle>, - Option>, + Option>, task::JoinHandle<()>, ) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { @@ -660,7 +664,7 @@ async fn start_ethereum_node( }); // Start the oracle for listening to Ethereum events - let (eth_sender, eth_receiver) = mpsc::unbounded_channel(); + let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let oracle = match config.ethereum.mode { ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( ethereum_url, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 642f2590f3..6ff72a1ec0 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -888,7 +888,7 @@ mod test_finalize_block { name: "Test".to_string(), address: EthAddress([0; 20]), }; - oracle.send(event.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event.clone())).expect("Test failed"); let [queued_event]: [EthereumEvent; 1] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(queued_event, event); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2be5913d96..039b2ca7c9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -47,7 +47,7 @@ use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver, UnboundedSender}; use super::protocol::ShellParams; use super::rpc; @@ -183,14 +183,14 @@ pub(super) enum ShellMode { /// and queueing them up for inclusion in vote extensions #[derive(Debug)] pub(super) struct EthereumReceiver { - channel: UnboundedReceiver, + channel: Receiver, queue: BTreeSet, } impl EthereumReceiver { /// Create a new [`EthereumReceiver`] from a channel connected /// to an Ethereum oracle - pub fn new(channel: UnboundedReceiver) -> Self { + pub fn new(channel: Receiver) -> Self { Self { channel, queue: BTreeSet::new(), @@ -333,7 +333,7 @@ where config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -768,13 +768,14 @@ mod test_utils { use namada::types::storage::{BlockHash, Epoch, Header}; use namada::types::transaction::Fee; use tempfile::tempdir; - use tokio::sync::mpsc::UnboundedReceiver; + use tokio::sync::mpsc::{Sender, UnboundedReceiver}; use super::*; use crate::facade::tendermint_proto::abci::{ RequestInitChain, RequestProcessProposal, }; use crate::facade::tendermint_proto::google::protobuf::Timestamp; + use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; @@ -863,11 +864,11 @@ mod test_utils { ) -> ( Self, UnboundedReceiver>, - UnboundedSender, + Sender, ) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (eth_sender, eth_receiver) = - tokio::sync::mpsc::unbounded_channel(); + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB @@ -894,7 +895,7 @@ mod test_utils { pub fn new() -> ( Self, UnboundedReceiver>, - UnboundedSender, + Sender, ) { Self::new_at_height(BlockHeight(1)) } @@ -967,7 +968,7 @@ mod test_utils { ) -> ( TestShell, UnboundedReceiver>, - UnboundedSender, + Sender, ) { let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); @@ -987,7 +988,7 @@ mod test_utils { pub(super) fn setup() -> ( TestShell, UnboundedReceiver>, - UnboundedSender, + Sender, ) { setup_at_height(BlockHeight(0)) } @@ -1016,7 +1017,7 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); // we have to use RocksDB for this test let (sender, _) = tokio::sync::mpsc::unbounded_channel(); - let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); + let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let mut shell = Shell::::new( @@ -1076,7 +1077,7 @@ mod test_utils { // Drop the shell std::mem::drop(shell); - let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); + let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); // Reboot the shell and check that the queue was restored from DB let shell = Shell::::new( config::Ledger::new( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index fe1bcd20f9..8ca76594db 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -309,16 +309,16 @@ mod test_vote_extensions { address: EthAddress([0; 20]), }; - oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event_1.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_3); // check that we queue and de-duplicate events - oracle.send(event_2.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event_2.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); let [event_first, event_second, event_third]: [EthereumEvent; 3] = shell.new_ethereum_events().try_into().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index b03caaf314..8988afbda0 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,7 +8,7 @@ use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver as BoundedReceiver, UnboundedSender}; use tower::Service; use super::super::Shell; @@ -41,7 +41,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, From 2a6390d3328b6af90cbb06a27650bfc3e9c66fea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Oct 2022 15:16:56 +0000 Subject: [PATCH 0808/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 08abe4c67f..49455e617b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bridge_pool.wasm": "tx_bridge_pool.dee69ea94dca815418a893081463df0f98046ff8df3b18273e12093f29c845ab.wasm", - "tx_from_intent.wasm": "tx_from_intent.794cad2b4a3865dd4ab018331016b118af47ff22162abbaad75c59084766c268.wasm", - "tx_ibc.wasm": "tx_ibc.e48421975a3f732bb1ff120c40b3a71e87122543f436d6a59639c907a4a84d57.wasm", - "tx_init_account.wasm": "tx_init_account.55bc0adac8d731dc7ea4925fc0dd3b086c8d12bd0c2bbd8f4c9193f736dbf0e3.wasm", - "tx_init_nft.wasm": "tx_init_nft.6c5c808c8b033258a6cc68cef74104074ae9ba07ddfab157924fcade45350ee7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6d457961abc7aa59ac49dd2309b038649ee63df35b4b8e98e23a675357e81853.wasm", - "tx_init_validator.wasm": "tx_init_validator.9f3e98ae9ff9302d737db6ee197fc90477b6f2edd4ff89f71d04ba1a5518f9b7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.5c395cbaedf3b7055df1f341d6bc36f70f0375b62a3957462f6399b0ebcf1bb6.wasm", - "tx_transfer.wasm": "tx_transfer.982d522e4463dcb6c763ebfc6d00f31e1afb87af4430566f5604a279bdd158ad.wasm", - "tx_unbond.wasm": "tx_unbond.f016a1a4868e57cfe7d33997e0816870da025ad7c0fb6b71ddf3931dd599765e.wasm", - "tx_update_vp.wasm": "tx_update_vp.63ff8d0142f9f521a58236514d39032fa581002751d74750cdac502796a7667a.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.2d27405550e8926d76561153274419e3e12de0f6800cf0df5a5a1143f66b026f.wasm", - "tx_withdraw.wasm": "tx_withdraw.a200c6f0c5c7c9138690f32e858a0055115d06115531bfb956c09eceb1424436.wasm", - "vp_nft.wasm": "vp_nft.0dfc70e398dff7e16ef4ee9d8e7cfb3b1fccb55ff6f69b6318c6e9ea50403008.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.42c11427a99ed106169aa597c563b398a19a0f62381801beaebe46515731ba2b.wasm", - "vp_token.wasm": "vp_token.c85f437e5737f36cdb0c0158db178d1f0be476b3cf720d839251431dcf2337df.wasm", - "vp_user.wasm": "vp_user.2f3d129875637b3fa1de237917741f69c5eb42b817d9ed03a6a0b3345e2462ec.wasm" + "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_from_intent.wasm": "tx_from_intent.aacf3881273c1d322d9f67a2fee9cf9b5cab3661969b2adc388727a9ad7564f5.wasm", + "tx_ibc.wasm": "tx_ibc.66695a390c04405759d1128612556a0d8ce85ff3c6adf50546fcd991eaee6f41.wasm", + "tx_init_account.wasm": "tx_init_account.afc109717426c966943e0bf8f8d5f064908552335c109a4b0d81a72e7faa5113.wasm", + "tx_init_nft.wasm": "tx_init_nft.5f828560e5dd7be8a9bc25e5cfe5d258ea444ae3d097954398892562432ee0d6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2ce224b475520e48e19ba1dc70f0e01b8219c927fc9b60b7ab231fa833d09ab2.wasm", + "tx_init_validator.wasm": "tx_init_validator.4e84084907a3029d79833560b42c3ab69cf38020dc82c0a2f1ba323cbf372c6f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.788f29b1fb84835e2a905630dee2618be297bd53c68fcacf1c12fff81399dbc9.wasm", + "tx_transfer.wasm": "tx_transfer.7c84e04d3d636306bf09490744cdf4229ecc4954b10cffcff7d4beddd5f27c64.wasm", + "tx_unbond.wasm": "tx_unbond.6393cd64250c22ed82f32cfe9bde743d7ac57a9937e5ce0fc0ec8902301361f3.wasm", + "tx_update_vp.wasm": "tx_update_vp.e03daa135e498a83aabbd17109867ef138daa5e73a7d64904cb71da01c55ee06.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1cea6c13a9b78547363463e2c72c88b2cd7052e1d0da7c7bc67d6dffdbeafea2.wasm", + "tx_withdraw.wasm": "tx_withdraw.03da515f86aa2caeef33b81988f64675aef46257242cc5f42d73b986d04b94cf.wasm", + "vp_nft.wasm": "vp_nft.84395d98e82714782af42e7246bb000666ce29aa30e1952a97a87a3244fd7114.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ca4a51feebdb0313a8a87d6defd38679870e2a1be5d629979db9b11a0c60397.wasm", + "vp_token.wasm": "vp_token.227ccbdbd7401613bcd31e123e2021c12c4ed0f7ee746ba2f0296b9e18436eb0.wasm", + "vp_user.wasm": "vp_user.a174ba668bb9d868509632bed424aef09ddd016faf072f1cf5c2fcef1a468bd0.wasm" } \ No newline at end of file From ea61cadde625d7b10951c64ef2e109a676aed7dd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 3 Oct 2022 16:39:24 +0100 Subject: [PATCH 0809/1995] Log a warning when an unreleased version of Tendermint is used --- apps/src/lib/node/ledger/tendermint_node.rs | 44 +++++++++++++-------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index de6d56d8c2..b065bb7c59 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -60,7 +60,10 @@ async fn run_version_command(tendermint_path: &str) -> eyre::Result { fn parse_version(version_cmd_output: &str) -> eyre::Result { let version_str = version_cmd_output.trim_end().trim_start_matches('v'); Version::parse(version_str).wrap_err_with(|| { - eyre!("Failed to parse Tendermint version from string: {version_str}") + eyre!( + "Couldn't parse semantic version from Tendermint version string: \ + {version_str}" + ) }) } @@ -116,25 +119,32 @@ pub async fn run( ) -> Result<()> { let tendermint_path = from_env_or_default()?; - let version = get_version(&tendermint_path).await.map_err(|err| { - Error::Runtime(format!("Failed to check Tendermint version: {:?}", err)) - })?; let version_reqs = version_requirements(); - if version_reqs.matches(&version) { - tracing::info!( - %tendermint_path, - %version, - %version_reqs, - "Running with supported Tendermint version", - ); - } else { - tracing::warn!( + match get_version(&tendermint_path).await { + Ok(version) => { + if version_reqs.matches(&version) { + tracing::info!( + %tendermint_path, + %version, + %version_reqs, + "Running with supported Tendermint version", + ); + } else { + tracing::warn!( + %tendermint_path, + %version, + %version_reqs, + "Running with a Tendermint version which may not be supported - run at your own risk!", + ); + } + } + Err(error) => tracing::warn!( %tendermint_path, - %version, %version_reqs, - "Running with a Tendermint version which may not be supported - run at your own risk!", - ); - } + %error, + "Couldn't check if Tendermint version is supported - run at your own risk!", + ), + }; let home_dir_string = home_dir.to_string_lossy().to_string(); let mode = config.tendermint_mode.to_str().to_owned(); From 406b3b042d903663b3ca2f5c03fdaa1a4f265829 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 3 Oct 2022 17:07:24 +0100 Subject: [PATCH 0810/1995] Fix error msg --- apps/src/lib/client/tendermint_rpc_types.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 78f72d096f..fc92c76369 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -44,9 +44,9 @@ impl TxResponse { event_type: NamadaEventType, tx_hash: &str, ) -> Self { - let events = event.events.expect( - "We should have obtained Tx events from the websocket subscription", - ); + let events = event + .events + .expect("We should have obtained Tx events from the RPC"); let evt_key = event_type.to_string(); // Find the tx with a matching hash macro_rules! tx_error { From 9d13e8fea332687bd5128b65f1d103b808b6d435 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 09:11:55 +0100 Subject: [PATCH 0811/1995] Change block height signed over during vext crafting With ABCI+, we should sign over the chain's last height, since we are broadcasting vote extensions to our mempool during the Commit phase, after we have already called FinalizeBlock. --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index a337b7d15e..38d893a70b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -82,7 +82,10 @@ where .to_owned(); let ext = ethereum_events::Vext { + #[cfg(feature = "abcipp")] block_height: self.storage.get_current_decision_height(), + #[cfg(not(feature = "abcipp"))] + block_height: self.storage.last_height, ethereum_events: self.new_ethereum_events(), validator_addr, }; @@ -118,7 +121,10 @@ where // TODO: we need a way to map ethereum addresses to // namada validator addresses voting_powers: std::collections::HashMap::new(), + #[cfg(feature = "abcipp")] block_height: self.storage.get_current_decision_height(), + #[cfg(not(feature = "abcipp"))] + block_height: self.storage.last_height, }; let protocol_key = match &self.mode { From b86491ede97dd5821edb8eb721fef28c084f8f9d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 10:41:48 +0100 Subject: [PATCH 0812/1995] Remove explicit drop on http client --- apps/src/lib/client/tx.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 466c771a63..2df76709aa 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1147,8 +1147,6 @@ pub async fn broadcast_tx( // TODO: timeout? let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; - drop(rpc_cli); - if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); // Print the transaction identifiers to enable the extraction of From d38610197ca2cb454f9e312e4a27bd2f340f96c0 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 11:42:24 +0200 Subject: [PATCH 0813/1995] [fix]: Removed redundant check that oracle channel is closed. Renamed an import --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index d29c3db068..358eca2426 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -78,7 +78,7 @@ impl Oracle { return false; } } - !self.sender.is_closed() + true } /// Check if the receiver in the ledger has hung up. diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 8988afbda0..08b438bc85 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,7 +8,7 @@ use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; -use tokio::sync::mpsc::{Receiver as BoundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver, UnboundedSender}; use tower::Service; use super::super::Shell; From e618cf4b00c1924368cb8237d51747fcd3bc1f4e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 10:43:55 +0100 Subject: [PATCH 0814/1995] Improve TODO msg --- apps/src/lib/client/tx.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2df76709aa..2a4f612683 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1144,7 +1144,9 @@ pub async fn broadcast_tx( let rpc_cli = HttpClient::new(address)?; - // TODO: timeout? + // TODO: configure an explicit timeout value? we need to hack away at + // `tendermint-rs` for this, which is currently using a hard-coded 30s + // timeout. let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; if response.code == 0.into() { From 6d38d00854aeb31ac5e52e1f91450bf55995d5bf Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 11:55:16 +0200 Subject: [PATCH 0815/1995] [fix]: Formats and lints --- .../lib/node/ledger/ethereum_node/oracle.rs | 7 ++-- apps/src/lib/node/ledger/shell/mod.rs | 34 ++++++------------- .../shell/vote_extensions/eth_events.rs | 12 ++++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 358eca2426..498348bd0e 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use clarity::Address; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; -use tokio::sync::mpsc::{Sender as BoundedSender}; +use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -196,7 +196,10 @@ async fn run_oracle_aux(oracle: Oracle) { } }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)).await { + if !oracle + .send(process_queue(&latest_block, &mut pending)) + .await + { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 039b2ca7c9..4078baba71 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -775,11 +775,11 @@ mod test_utils { RequestInitChain, RequestProcessProposal, }; use crate::facade::tendermint_proto::google::protobuf::Timestamp; - use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; + use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; #[derive(Error, Debug)] pub enum TestError { @@ -861,11 +861,7 @@ mod test_utils { /// the Ethereum fullnode process pub fn new_at_height>( height: H, - ) -> ( - Self, - UnboundedReceiver>, - Sender, - ) { + ) -> (Self, UnboundedReceiver>, Sender) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); @@ -892,11 +888,8 @@ mod test_utils { /// Same as [`TestShell::new_at_height`], but returns a shell at block /// height 0. #[inline] - pub fn new() -> ( - Self, - UnboundedReceiver>, - Sender, - ) { + pub fn new() -> (Self, UnboundedReceiver>, Sender) + { Self::new_at_height(BlockHeight(1)) } @@ -965,11 +958,7 @@ mod test_utils { /// shell. pub(super) fn setup_at_height>( height: H, - ) -> ( - TestShell, - UnboundedReceiver>, - Sender, - ) { + ) -> (TestShell, UnboundedReceiver>, Sender) { let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); test.init_chain(RequestInitChain { @@ -985,11 +974,8 @@ mod test_utils { /// Same as [`setup`], but returns a shell at block height 0. #[inline] - pub(super) fn setup() -> ( - TestShell, - UnboundedReceiver>, - Sender, - ) { + pub(super) fn setup() + -> (TestShell, UnboundedReceiver>, Sender) { setup_at_height(BlockHeight(0)) } @@ -1017,7 +1003,8 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); // we have to use RocksDB for this test let (sender, _) = tokio::sync::mpsc::unbounded_channel(); - let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); + let (_, receiver) = + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let mut shell = Shell::::new( @@ -1077,7 +1064,8 @@ mod test_utils { // Drop the shell std::mem::drop(shell); - let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); + let (_, receiver) = + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); // Reboot the shell and check that the queue was restored from DB let shell = Shell::::new( config::Ledger::new( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 8ca76594db..32eda5fe25 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -309,16 +309,20 @@ mod test_vote_extensions { address: EthAddress([0; 20]), }; - tokio_test::block_on(oracle.send(event_1.clone())).expect("Test failed"); - tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_1.clone())) + .expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())) + .expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_3); // check that we queue and de-duplicate events - tokio_test::block_on(oracle.send(event_2.clone())).expect("Test failed"); - tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_2.clone())) + .expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())) + .expect("Test failed"); let [event_first, event_second, event_third]: [EthereumEvent; 3] = shell.new_ethereum_events().try_into().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 08b438bc85..78a0531cd5 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -41,7 +41,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, From 4acf72a8e1c5fcf65aa0db5e52c651c331950653 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Oct 2022 10:06:19 +0000 Subject: [PATCH 0816/1995] 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 de0f089b1f..e7474fb0ce 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", - "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", - "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", - "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", - "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", - "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", - "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", - "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", - "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", - "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", - "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" + "tx_from_intent.wasm": "tx_from_intent.136411cc6ba6e730773aba5f1f1a08e50f65802c74d952f44301f6dffa188fbe.wasm", + "tx_ibc.wasm": "tx_ibc.463bec148d39f2f37569f738172c518d193ac7316f08aa19fbe9c794fa560c1a.wasm", + "tx_init_account.wasm": "tx_init_account.12db68a8ca9a697b54185b367c633ffb3297b82f24aa09ed376057dc95d89601.wasm", + "tx_init_nft.wasm": "tx_init_nft.4a194461997ccb1523d7bc3f7cf60c001da2b7fabf0c9156811e24640311c327.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.9ac26fa7634cc7bb0aa0b347597bd469af969c3150950274354156b1839be024.wasm", + "tx_init_validator.wasm": "tx_init_validator.50ca8490f19236ccf094d6cc991b71fb263e434592fe8453fe424313eef603ff.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.6df69410df386b8ccc049cf9f60f3db37e1514cce61653799cbf5b46be96f519.wasm", + "tx_transfer.wasm": "tx_transfer.a5172a457ab06d4a7f6dbc4761c293bd8ac97ebfeba3206ba612e4f92ff5f67e.wasm", + "tx_unbond.wasm": "tx_unbond.ca3e11f29c9fe1a8f7b9629badc3205274621dafe7f039d1064620e7db805b09.wasm", + "tx_update_vp.wasm": "tx_update_vp.e03daa135e498a83aabbd17109867ef138daa5e73a7d64904cb71da01c55ee06.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ff65047ddb4207cfb9776498e585865fe956d614e0809220286fe1d9bff76b3e.wasm", + "tx_withdraw.wasm": "tx_withdraw.a96795427f54d1318f4ddfbf4d56512fda841d8240b8b23b33eaae14263f8397.wasm", + "vp_nft.wasm": "vp_nft.0021dad70a8a7a80b7d0f8a57161bbe2106a8d5aeec5f150f473b5eae8df8095.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8ceaf4ef496402c0c53916c0de4b27dae479004c144cee1c7aae81b45e673b04.wasm", + "vp_token.wasm": "vp_token.8432b06ec487e1e0bce13fd692690835e4c57638d97bc1f343b39d99b4e5cdd1.wasm", + "vp_user.wasm": "vp_user.d3f2195d8cfe8b9280bc809ebec5514b3846d750ea6b7768f92402d457be7b8b.wasm" } \ No newline at end of file From c12924fb144df7e6061bd69443fa2ba1570ba4ef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 11:15:13 +0100 Subject: [PATCH 0817/1995] Remove jsonpath crate --- Cargo.lock | 13 ------------- apps/Cargo.toml | 1 - 2 files changed, 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc54aa2b3a..c7feeb1de8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3222,17 +3222,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" -dependencies = [ - "log 0.4.17", - "serde 1.0.137", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.2" @@ -4390,7 +4379,6 @@ dependencies = [ "git2", "hex", "itertools 0.10.3", - "jsonpath_lib", "libc", "libloading", "libp2p", @@ -6429,7 +6417,6 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ - "indexmap", "itoa", "ryu", "serde 1.0.137", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index cd7a0f638f..be78e08479 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,7 +92,6 @@ file-lock = "2.0.2" futures = "0.3" hex = "0.4.3" itertools = "0.10.1" -jsonpath_lib = "0.3.0" libc = "0.2.97" libloading = "0.7.2" libp2p = "0.38.0" From bd28b2d71b62842e21dcc1fe69288f1663fad8a5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 13:07:38 +0100 Subject: [PATCH 0818/1995] Consistent param idents between cli and server for the events RPC method --- apps/src/lib/client/tx.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2a4f612683..96b6bc74f6 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1184,7 +1184,7 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let rpc_timeout = + let max_wait_time = if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_RPC_TIMEOUT) { if let Ok(timeout) = val.parse::() { Duration::from_secs(timeout) @@ -1203,7 +1203,7 @@ pub async fn submit_tx( let parsed = { let wrapper_query = Query::from(EventType::NewBlock) .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - let event = rpc_cli.events(wrapper_query, rpc_timeout).await?.into(); + let event = rpc_cli.events(wrapper_query, max_wait_time).await?.into(); let parsed = TxResponse::parse(event, NamadaEventType::Accepted, wrapper_hash); @@ -1219,7 +1219,7 @@ pub async fn submit_tx( let decrypted_query = Query::from(EventType::NewBlock) .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); let event = - rpc_cli.events(decrypted_query, rpc_timeout).await?.into(); + rpc_cli.events(decrypted_query, max_wait_time).await?.into(); let parsed = TxResponse::parse( event, NamadaEventType::Applied, From 0d1f04e2b9a4c7f32fe2083a543be4d719bb6221 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 13:10:38 +0100 Subject: [PATCH 0819/1995] Rename timeout env var for events RPC --- apps/src/lib/client/tx.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 96b6bc74f6..8e5ed68a3e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -55,12 +55,12 @@ const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; const VP_NFT: &str = "vp_nft.wasm"; /// Timeout for jsonrpc requests to the `/events` endpoint in Tendermint. -const ENV_VAR_ANOMA_TENDERMINT_RPC_TIMEOUT: &str = - "ANOMA_TENDERMINT_RPC_TIMEOUT"; +const ENV_VAR_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME: &str = + "ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME"; /// Default timeout in seconds for jsonrpc requests to the `/events` endpoint in /// Tendermint. -const DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT: u64 = 30; +const DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME: u64 = 30; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); @@ -1184,16 +1184,17 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let max_wait_time = - if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_RPC_TIMEOUT) { - if let Ok(timeout) = val.parse::() { - Duration::from_secs(timeout) - } else { - Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT) - } + let max_wait_time = if let Ok(val) = + env::var(ENV_VAR_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) + { + if let Ok(timeout) = val.parse::() { + Duration::from_secs(timeout) } else { - Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_RPC_TIMEOUT) - }; + Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) + } + } else { + Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) + }; tracing::debug!("Tenderming address: {:?}", address); let rpc_cli = HttpClient::new(address.clone())?; From f2444076f69abecb157a1a8f21e7fc82932ecdfe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 4 Oct 2022 13:17:22 +0100 Subject: [PATCH 0820/1995] Simplify parsing of env var --- apps/src/lib/client/tx.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8e5ed68a3e..fea14e607a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1184,17 +1184,12 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let max_wait_time = if let Ok(val) = + let max_wait_time = Duration::from_secs( env::var(ENV_VAR_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) - { - if let Ok(timeout) = val.parse::() { - Duration::from_secs(timeout) - } else { - Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) - } - } else { - Duration::from_secs(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) - }; + .ok() + .and_then(|val| val.parse().ok()) + .unwrap_or(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME), + ); tracing::debug!("Tenderming address: {:?}", address); let rpc_cli = HttpClient::new(address.clone())?; From fb0bc1bf731910ba1d76ac5e62454c3d1297ca39 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 14:54:00 +0200 Subject: [PATCH 0821/1995] [fix]: Fixed broken unit tests and corrected the min confirmations --- .../lib/node/ledger/ethereum_node/oracle.rs | 67 +++++++++++-------- .../node/ledger/ethereum_node/test_tools.rs | 17 +++-- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index d73537157c..5e49670145 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -14,7 +14,7 @@ use super::events::{signatures, PendingEvent}; use super::test_tools::mock_web3_client::Web3; /// Minimum number of confirmations needed to trust an Ethereum branch -pub(crate) const MIN_CONFIRMATIONS: u64 = 50; +pub(crate) const MIN_CONFIRMATIONS: u64 = 100; /// Dummy addresses for smart contracts const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); @@ -308,8 +308,9 @@ mod test_oracle { tokio_test::block_on(run_oracle_aux(oracle)); }); admin_channel - .send(TestCmd::NewHeight(Uint256::from(100u32))) + .send(TestCmd::NewHeight(Uint256::from(150u32))) .expect("Test failed"); + let mut time = std::time::Duration::from_secs(1); while time > std::time::Duration::from_millis(10) { assert!(eth_recv.try_recv().is_err()); @@ -335,7 +336,7 @@ mod test_oracle { }); // Increase height above [`MIN_CONFIRMATIONS`] admin_channel - .send(TestCmd::NewHeight(50u32.into())) + .send(TestCmd::NewHeight(100u32.into())) .expect("Test failed"); let new_event = ChangedContract { @@ -343,11 +344,13 @@ mod test_oracle { address: EthAddress([0; 20]), } .encode(); + let (sender, _) = channel(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, data: new_event, - height: 51, + height: 101, + seen: sender, }) .expect("Test failed"); // since height is not updating, we should not receive events @@ -366,7 +369,7 @@ mod test_oracle { fn test_wait_on_new_logs() { let TestPackage { oracle, - mut eth_recv, + eth_recv, admin_channel, .. } = setup(); @@ -375,45 +378,43 @@ mod test_oracle { }); // Increase height above [`MIN_CONFIRMATIONS`] admin_channel - .send(TestCmd::NewHeight(50u32.into())) + .send(TestCmd::NewHeight(100u32.into())) .expect("Test failed"); + // set the oracle to be unresponsive + admin_channel + .send(TestCmd::Unresponsive) + .expect("Test failed"); + // send a new event to the oracle let new_event = ChangedContract { name: "Test".to_string(), address: EthAddress([0; 20]), } .encode(); + let (sender, mut seen) = channel(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, data: new_event, - height: 100, + height: 150, + seen: sender, }) .expect("Test failed"); - - // we should not receive events even though the height is large - // enough - admin_channel - .send(TestCmd::Unresponsive) - .expect("Test failed"); + // set the height high enough to emit the event admin_channel - .send(TestCmd::NewHeight(Uint256::from(101u32))) + .send(TestCmd::NewHeight(Uint256::from(251u32))) .expect("Test failed"); + // the event should not be emitted even though the height is large + // enough let mut time = std::time::Duration::from_secs(1); while time > std::time::Duration::from_millis(10) { - assert!(eth_recv.try_recv().is_err()); + assert!(seen.try_recv().is_err()); time -= std::time::Duration::from_millis(10); } // check that when web3 becomes responsive, oracle sends event admin_channel.send(TestCmd::Normal).expect("Test failed"); - let event = eth_recv.blocking_recv().expect("Test failed"); - if let EthereumEvent::NewContract { name, address } = event { - assert_eq!(name.as_str(), "Test"); - assert_eq!(address.0, [0; 20]); - } else { - panic!("Test failed"); - } + seen.blocking_recv().expect("Test failed"); drop(eth_recv); oracle.join().expect("Test failed"); } @@ -433,17 +434,17 @@ mod test_oracle { }); // Increase height above [`MIN_CONFIRMATIONS`] admin_channel - .send(TestCmd::NewHeight(50u32.into())) + .send(TestCmd::NewHeight(100u32.into())) .expect("Test failed"); - // confirmed after 50 blocks + // confirmed after 100 blocks let first_event = ChangedContract { name: "Test".to_string(), address: EthAddress([0; 20]), } .encode(); - // confirmed after 75 blocks + // confirmed after 125 blocks let second_event = RawTransfersToEthereum { transfers: vec![TransferToEthereum { amount: Default::default(), @@ -451,30 +452,34 @@ mod test_oracle { receiver: EthAddress([1; 20]), }], nonce: 1.into(), - confirmations: 75, + confirmations: 125, } .encode(); // send in the events to the logs + let (sender, seen_second) = channel(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::TransferToEthereum, data: second_event, height: 125, + seen: sender, }) .expect("Test failed"); + let (sender, _recv) = channel(); admin_channel .send(TestCmd::NewEvent { event_type: MockEventType::NewContract, data: first_event, height: 100, + seen: sender, }) .expect("Test failed"); // increase block height so first event is confirmed but second is // not. admin_channel - .send(TestCmd::NewHeight(Uint256::from(105u32))) + .send(TestCmd::NewHeight(Uint256::from(200u32))) .expect("Test failed"); // check the correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); @@ -492,9 +497,15 @@ mod test_oracle { time -= std::time::Duration::from_millis(10); } + // increase block height so second event is emitted + admin_channel + .send(TestCmd::NewHeight(Uint256::from(225u32))) + .expect("Test failed"); + // wait until event is emitted + seen_second.blocking_recv().expect("Test failed"); // increase block height so second event is confirmed admin_channel - .send(TestCmd::NewHeight(Uint256::from(130u32))) + .send(TestCmd::NewHeight(Uint256::from(250u32))) .expect("Test failed"); // check correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index c6184bda99..c3ed803a47 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -121,6 +121,7 @@ pub mod mock_web3_client { use tokio::sync::mpsc::{ unbounded_channel, UnboundedReceiver, UnboundedSender, }; + use tokio::sync::oneshot::Sender; use web30::types::Log; use super::super::events::signatures::*; @@ -136,6 +137,7 @@ pub mod mock_web3_client { event_type: MockEventType, data: Vec, height: u32, + seen: Sender<()>, }, } @@ -162,7 +164,7 @@ pub mod mock_web3_client { cmd_channel: UnboundedReceiver, active: bool, latest_block_height: Uint256, - events: Vec<(MockEventType, Vec, u32)>, + events: Vec<(MockEventType, Vec, u32, Sender<()>)>, } impl Web3 { @@ -210,7 +212,8 @@ pub mod mock_web3_client { event_type: ty, data, height, - } => self.0.borrow_mut().events.push((ty, data, height)), + seen, + } => self.0.borrow_mut().events.push((ty, data, height, seen)), } } @@ -227,7 +230,7 @@ pub mod mock_web3_client { /// client has not been set to act unresponsive. pub async fn check_for_events( &self, - _: Uint256, + block_to_check: Uint256, _: Option, _: impl Debug, mut events: Vec<&str>, @@ -251,16 +254,16 @@ pub mod mock_web3_client { let mut events = vec![]; let mut client = self.0.borrow_mut(); std::mem::swap(&mut client.events, &mut events); - for (event_ty, data, height) in events.into_iter() { - if event_ty == ty - && client.latest_block_height >= Uint256::from(height) + for (event_ty, data, height, seen) in events.into_iter() { + if event_ty == ty && block_to_check >= Uint256::from(height) { + seen.send(()).unwrap(); logs.push(Log { data: data.into(), ..Default::default() }); } else { - client.events.push((event_ty, data, height)); + client.events.push((event_ty, data, height, seen)); } } Ok(logs) From d2a648006cbbc996c75bd7f47d49b7f4ebe2950c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 16:28:50 +0200 Subject: [PATCH 0822/1995] [fix]: Changed the mock oracle to await when sending events --- .../lib/node/ledger/ethereum_node/events.rs | 2 +- .../lib/node/ledger/ethereum_node/oracle.rs | 4 +- .../node/ledger/ethereum_node/test_tools.rs | 63 ++++++++++--------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 4071d6a0f0..0d176b875f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -202,7 +202,7 @@ pub mod eth_events { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations <= height.clone() - self.block_height.clone() + self.confirmations >= height.clone() - self.block_height.clone() } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 498348bd0e..75cde208ad 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -14,7 +14,7 @@ use super::events::{signatures, PendingEvent}; use super::test_tools::mock_web3_client::Web3; /// Minimum number of confirmations needed to trust an Ethereum branch -pub(crate) const MIN_CONFIRMATIONS: u64 = 100; +pub(crate) const MIN_CONFIRMATIONS: u64 = 50; /// Dummy addresses for smart contracts const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); @@ -481,7 +481,7 @@ mod test_oracle { // increase block height so first event is confirmed but second is // not. admin_channel - .send(TestCmd::NewHeight(Uint256::from(105u32))) + .send(TestCmd::NewHeight(Uint256::from(102u32))) .expect("Test failed"); // check the correct event is received let event = eth_recv.blocking_recv().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index 507f9c5a3d..a5d92b79dd 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -54,6 +54,7 @@ pub mod event_endpoint { use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; use tokio::sync::mpsc::Sender as BoundedSender; + use warp::reply::WithStatus; const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); @@ -63,44 +64,17 @@ pub mod event_endpoint { pub fn start_oracle( sender: BoundedSender, ) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { + tokio::task::spawn_local(async move { use warp::Filter; tracing::info!( ?ETHEREUM_EVENTS_ENDPOINT, "Ethereum event endpoint is starting" ); - let eth_events = warp::post() .and(warp::path(PATH)) .and(warp::body::bytes()) - .map(move |bytes: bytes::Bytes| { - tracing::info!(len = bytes.len(), "Received request"); - let event = match EthereumEvent::try_from_slice(&bytes) { - Ok(event) => event, - Err(error) => { - tracing::warn!(?error, "Couldn't handle request"); - return warp::reply::with_status( - "Bad request", - warp::http::StatusCode::BAD_REQUEST, - ); - } - }; - tracing::debug!("Serialized event - {:#?}", event); - match sender.try_send(event) { - Ok(()) => warp::reply::with_status( - "OK", - warp::http::StatusCode::OK, - ), - Err(error) => { - tracing::warn!(?error, "Couldn't send event"); - warp::reply::with_status( - "Internal server error", - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - } - } - }); + .then(move |bytes: bytes::Bytes| send(bytes, sender.clone())); warp::serve(eth_events).run(ETHEREUM_EVENTS_ENDPOINT).await; @@ -110,6 +84,37 @@ pub mod event_endpoint { ); }) } + + /// Callback to send out events from the oracle + async fn send( + bytes: bytes::Bytes, + sender: BoundedSender, + ) -> WithStatus<&'static str> { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event).await { + Ok(()) => { + warp::reply::with_status("OK", warp::http::StatusCode::OK) + } + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) + } + } + } } #[cfg(test)] From 98fb156e6e95b655fc1bf94be8dfa8f2f63580a3 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 3 Oct 2022 16:42:43 +0200 Subject: [PATCH 0823/1995] [fix]: Switched the ethereum oracle to use a bounded channel --- .../lib/node/ledger/ethereum_node/oracle.rs | 30 +++++++++++-------- .../node/ledger/ethereum_node/test_tools.rs | 10 +++---- apps/src/lib/node/ledger/mod.rs | 10 +++++-- .../lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 25 ++++++++-------- .../shell/vote_extensions/eth_events.rs | 8 ++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 4 +-- 7 files changed, 49 insertions(+), 40 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 5e49670145..8468dfdd2f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use clarity::Address; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::{Sender as BoundedSender}; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -28,7 +28,7 @@ pub struct Oracle { client: Web3, /// A channel for sending processed and confirmed /// events to the ledger process - sender: UnboundedSender, + sender: BoundedSender, /// A channel to signal that the ledger should shut down /// because the Oracle has stopped abort: Option>, @@ -55,7 +55,7 @@ impl Oracle { /// Initialize a new [`Oracle`] pub fn new( url: &str, - sender: UnboundedSender, + sender: BoundedSender, abort: Sender<()>, ) -> Self { Self { @@ -69,12 +69,16 @@ impl Oracle { /// ledger. Returns a boolean indicating that all sent /// successfully. If false is returned, the receiver /// has hung up. - fn send(&self, events: Vec) -> bool { - events - .into_iter() - .map(|event| self.sender.send(event)) - .all(|res| res.is_ok()) - && !self.sender.is_closed() + /// + /// N.B. this will block if the internal channel buffer + /// is full. + async fn send(&self, events: Vec) -> bool { + for event in events.into_iter() { + if self.sender.send(event).await.is_err() { + return false; + } + } + !self.sender.is_closed() } /// Check if the receiver in the ledger has hung up. @@ -88,7 +92,7 @@ impl Oracle { /// processes and forwards Ethereum events to the ledger pub fn run_oracle( url: impl AsRef, - sender: UnboundedSender, + sender: BoundedSender, abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); @@ -192,7 +196,7 @@ async fn run_oracle_aux(oracle: Oracle) { } }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)) { + if !oracle.send(process_queue(&latest_block, &mut pending)).await { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" @@ -240,14 +244,14 @@ mod test_oracle { struct TestPackage { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, - eth_recv: tokio::sync::mpsc::UnboundedReceiver, + eth_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); - let (eth_sender, eth_receiver) = tokio::sync::mpsc::unbounded_channel(); + let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index c3ed803a47..c3f78588dc 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -32,12 +32,12 @@ pub mod mock_oracle { use namada::types::ethereum_events::EthereumEvent; use tokio::macros::support::poll_fn; - use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; pub fn run_oracle( _: impl AsRef, - _: UnboundedSender, + _: BoundedSender, mut abort: Sender<()>, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { @@ -53,7 +53,7 @@ pub mod mock_oracle { pub mod event_endpoint { use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; - use tokio::sync::mpsc::UnboundedSender; + use tokio::sync::mpsc::Sender as BoundedSender; const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); @@ -61,7 +61,7 @@ pub mod event_endpoint { const PATH: &str = "eth_events"; pub fn start_oracle( - sender: UnboundedSender, + sender: BoundedSender, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { use warp::Filter; @@ -87,7 +87,7 @@ pub mod event_endpoint { } }; tracing::debug!("Serialized event - {:#?}", event); - match sender.send(event) { + match sender.try_send(event) { Ok(()) => warp::reply::with_status( "OK", warp::http::StatusCode::OK, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 369c938c1d..657f73e0b4 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -44,6 +44,10 @@ const ENV_VAR_TOKIO_THREADS: &str = "ANOMA_TOKIO_THREADS"; /// Env. var to set a number of Rayon global worker threads const ENV_VAR_RAYON_THREADS: &str = "ANOMA_RAYON_THREADS"; +/// The maximum number of Ethereum events the channel between +/// the oracle and the shell can hold. +const ORACLE_CHANNEL_BUFFER_SIZE: usize = 1000; + // Until ABCI++ is ready, the shim provides the service implementation. // We will add this part back in once the shim is no longer needed. //``` @@ -384,7 +388,7 @@ async fn run_aux_setup( /// a new OS thread, to drive the ABCI server. fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, - eth_receiver: Option>, + eth_receiver: Option>, wasm_dir: PathBuf, setup_data: RunAuxSetup, config: config::Ledger, @@ -610,7 +614,7 @@ async fn start_ethereum_node( config: &config::Ledger, ) -> ( task::JoinHandle>, - Option>, + Option>, task::JoinHandle<()>, ) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { @@ -660,7 +664,7 @@ async fn start_ethereum_node( }); // Start the oracle for listening to Ethereum events - let (eth_sender, eth_receiver) = mpsc::unbounded_channel(); + let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let oracle = match config.ethereum.mode { ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( ethereum_url, diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 642f2590f3..6ff72a1ec0 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -888,7 +888,7 @@ mod test_finalize_block { name: "Test".to_string(), address: EthAddress([0; 20]), }; - oracle.send(event.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event.clone())).expect("Test failed"); let [queued_event]: [EthereumEvent; 1] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(queued_event, event); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index eeead299f8..c9e5ff1601 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -47,7 +47,7 @@ use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use thiserror::Error; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver, UnboundedSender}; use super::protocol::ShellParams; use super::rpc; @@ -183,14 +183,14 @@ pub(super) enum ShellMode { /// and queueing them up for inclusion in vote extensions #[derive(Debug)] pub(super) struct EthereumReceiver { - channel: UnboundedReceiver, + channel: Receiver, queue: BTreeSet, } impl EthereumReceiver { /// Create a new [`EthereumReceiver`] from a channel connected /// to an Ethereum oracle - pub fn new(channel: UnboundedReceiver) -> Self { + pub fn new(channel: Receiver) -> Self { Self { channel, queue: BTreeSet::new(), @@ -333,7 +333,7 @@ where config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, @@ -768,13 +768,14 @@ mod test_utils { use namada::types::storage::{BlockHash, Epoch, Header}; use namada::types::transaction::Fee; use tempfile::tempdir; - use tokio::sync::mpsc::UnboundedReceiver; + use tokio::sync::mpsc::{Sender, UnboundedReceiver}; use super::*; use crate::facade::tendermint_proto::abci::{ RequestInitChain, RequestProcessProposal, }; use crate::facade::tendermint_proto::google::protobuf::Timestamp; + use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; @@ -863,11 +864,11 @@ mod test_utils { ) -> ( Self, UnboundedReceiver>, - UnboundedSender, + Sender, ) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (eth_sender, eth_receiver) = - tokio::sync::mpsc::unbounded_channel(); + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB @@ -894,7 +895,7 @@ mod test_utils { pub fn new() -> ( Self, UnboundedReceiver>, - UnboundedSender, + Sender, ) { Self::new_at_height(BlockHeight(1)) } @@ -967,7 +968,7 @@ mod test_utils { ) -> ( TestShell, UnboundedReceiver>, - UnboundedSender, + Sender, ) { let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); @@ -987,7 +988,7 @@ mod test_utils { pub(super) fn setup() -> ( TestShell, UnboundedReceiver>, - UnboundedSender, + Sender, ) { setup_at_height(BlockHeight(0)) } @@ -1016,7 +1017,7 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); // we have to use RocksDB for this test let (sender, _) = tokio::sync::mpsc::unbounded_channel(); - let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); + let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let mut shell = Shell::::new( @@ -1076,7 +1077,7 @@ mod test_utils { // Drop the shell std::mem::drop(shell); - let (_, receiver) = tokio::sync::mpsc::unbounded_channel(); + let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); // Reboot the shell and check that the queue was restored from DB let shell = Shell::::new( config::Ledger::new( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 99de036046..98764225af 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -344,16 +344,16 @@ mod test_vote_extensions { address: EthAddress([0; 20]), }; - oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event_1.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_3); // check that we queue and de-duplicate events - oracle.send(event_2.clone()).expect("Test failed"); - oracle.send(event_3.clone()).expect("Test failed"); + tokio_test::block_on(oracle.send(event_2.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); let [event_first, event_second, event_third]: [EthereumEvent; 3] = shell.new_ethereum_events().try_into().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index b03caaf314..8988afbda0 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,7 +8,7 @@ use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver as BoundedReceiver, UnboundedSender}; use tower::Service; use super::super::Shell; @@ -41,7 +41,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, From 69bb63f707f14e7b70783c46b70f708e73a05d90 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 11:42:24 +0200 Subject: [PATCH 0824/1995] [fix]: Removed redundant check that oracle channel is closed. Renamed an import --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 8468dfdd2f..b5c096cb1f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -78,7 +78,7 @@ impl Oracle { return false; } } - !self.sender.is_closed() + true } /// Check if the receiver in the ledger has hung up. diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 8988afbda0..08b438bc85 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,7 +8,7 @@ use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; -use tokio::sync::mpsc::{Receiver as BoundedReceiver, UnboundedSender}; +use tokio::sync::mpsc::{Receiver, UnboundedSender}; use tower::Service; use super::super::Shell; From 4c184afd8a7735919ee3659b34a78bd90ff5c4ea Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 11:55:16 +0200 Subject: [PATCH 0825/1995] [fix]: Formats and lints --- .../lib/node/ledger/ethereum_node/oracle.rs | 7 ++-- apps/src/lib/node/ledger/shell/mod.rs | 34 ++++++------------- .../shell/vote_extensions/eth_events.rs | 12 ++++--- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index b5c096cb1f..b46b8185db 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use clarity::Address; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; -use tokio::sync::mpsc::{Sender as BoundedSender}; +use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -196,7 +196,10 @@ async fn run_oracle_aux(oracle: Oracle) { } }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, &mut pending)).await { + if !oracle + .send(process_queue(&latest_block, &mut pending)) + .await + { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ receiver has hung up. Shutting down" diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index c9e5ff1601..547d60edf1 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -775,11 +775,11 @@ mod test_utils { RequestInitChain, RequestProcessProposal, }; use crate::facade::tendermint_proto::google::protobuf::Timestamp; - use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; use crate::node::ledger::shims::abcipp_shim_types::shim::request::{ FinalizeBlock, ProcessedTx, }; use crate::node::ledger::storage::{PersistentDB, PersistentStorageHasher}; + use crate::node::ledger::ORACLE_CHANNEL_BUFFER_SIZE; #[derive(Error, Debug)] pub enum TestError { @@ -861,11 +861,7 @@ mod test_utils { /// the Ethereum fullnode process pub fn new_at_height>( height: H, - ) -> ( - Self, - UnboundedReceiver>, - Sender, - ) { + ) -> (Self, UnboundedReceiver>, Sender) { let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); @@ -892,11 +888,8 @@ mod test_utils { /// Same as [`TestShell::new_at_height`], but returns a shell at block /// height 0. #[inline] - pub fn new() -> ( - Self, - UnboundedReceiver>, - Sender, - ) { + pub fn new() -> (Self, UnboundedReceiver>, Sender) + { Self::new_at_height(BlockHeight(1)) } @@ -965,11 +958,7 @@ mod test_utils { /// shell. pub(super) fn setup_at_height>( height: H, - ) -> ( - TestShell, - UnboundedReceiver>, - Sender, - ) { + ) -> (TestShell, UnboundedReceiver>, Sender) { let (mut test, receiver, eth_receiver) = TestShell::new_at_height(height); test.init_chain(RequestInitChain { @@ -985,11 +974,8 @@ mod test_utils { /// Same as [`setup`], but returns a shell at block height 0. #[inline] - pub(super) fn setup() -> ( - TestShell, - UnboundedReceiver>, - Sender, - ) { + pub(super) fn setup() + -> (TestShell, UnboundedReceiver>, Sender) { setup_at_height(BlockHeight(0)) } @@ -1017,7 +1003,8 @@ mod test_utils { let base_dir = tempdir().unwrap().as_ref().canonicalize().unwrap(); // we have to use RocksDB for this test let (sender, _) = tokio::sync::mpsc::unbounded_channel(); - let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); + let (_, receiver) = + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let mut shell = Shell::::new( @@ -1077,7 +1064,8 @@ mod test_utils { // Drop the shell std::mem::drop(shell); - let (_, receiver) = tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); + let (_, receiver) = + tokio::sync::mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); // Reboot the shell and check that the queue was restored from DB let shell = Shell::::new( config::Ledger::new( diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 98764225af..59717f4efd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -344,16 +344,20 @@ mod test_vote_extensions { address: EthAddress([0; 20]), }; - tokio_test::block_on(oracle.send(event_1.clone())).expect("Test failed"); - tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_1.clone())) + .expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())) + .expect("Test failed"); let [event_first, event_second]: [EthereumEvent; 2] = shell.new_ethereum_events().try_into().expect("Test failed"); assert_eq!(event_first, event_1); assert_eq!(event_second, event_3); // check that we queue and de-duplicate events - tokio_test::block_on(oracle.send(event_2.clone())).expect("Test failed"); - tokio_test::block_on(oracle.send(event_3.clone())).expect("Test failed"); + tokio_test::block_on(oracle.send(event_2.clone())) + .expect("Test failed"); + tokio_test::block_on(oracle.send(event_3.clone())) + .expect("Test failed"); let [event_first, event_second, event_third]: [EthereumEvent; 3] = shell.new_ethereum_events().try_into().expect("Test failed"); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 08b438bc85..78a0531cd5 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -41,7 +41,7 @@ impl AbcippShim { config: config::Ledger, wasm_dir: PathBuf, broadcast_sender: UnboundedSender>, - eth_receiver: Option>, + eth_receiver: Option>, db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, From a90f0635b46a6213d30000868d6d11bb3599e8ef Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 4 Oct 2022 16:28:50 +0200 Subject: [PATCH 0826/1995] [fix]: Changed the mock oracle to await when sending events --- .../lib/node/ledger/ethereum_node/events.rs | 2 +- .../node/ledger/ethereum_node/test_tools.rs | 63 ++++++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 4071d6a0f0..0d176b875f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -202,7 +202,7 @@ pub mod eth_events { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations <= height.clone() - self.block_height.clone() + self.confirmations >= height.clone() - self.block_height.clone() } } diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs index c3f78588dc..17dc762da0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools.rs @@ -54,6 +54,7 @@ pub mod event_endpoint { use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; use tokio::sync::mpsc::Sender as BoundedSender; + use warp::reply::WithStatus; const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); @@ -63,44 +64,17 @@ pub mod event_endpoint { pub fn start_oracle( sender: BoundedSender, ) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { + tokio::task::spawn_local(async move { use warp::Filter; tracing::info!( ?ETHEREUM_EVENTS_ENDPOINT, "Ethereum event endpoint is starting" ); - let eth_events = warp::post() .and(warp::path(PATH)) .and(warp::body::bytes()) - .map(move |bytes: bytes::Bytes| { - tracing::info!(len = bytes.len(), "Received request"); - let event = match EthereumEvent::try_from_slice(&bytes) { - Ok(event) => event, - Err(error) => { - tracing::warn!(?error, "Couldn't handle request"); - return warp::reply::with_status( - "Bad request", - warp::http::StatusCode::BAD_REQUEST, - ); - } - }; - tracing::debug!("Serialized event - {:#?}", event); - match sender.try_send(event) { - Ok(()) => warp::reply::with_status( - "OK", - warp::http::StatusCode::OK, - ), - Err(error) => { - tracing::warn!(?error, "Couldn't send event"); - warp::reply::with_status( - "Internal server error", - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - } - } - }); + .then(move |bytes: bytes::Bytes| send(bytes, sender.clone())); warp::serve(eth_events).run(ETHEREUM_EVENTS_ENDPOINT).await; @@ -110,6 +84,37 @@ pub mod event_endpoint { ); }) } + + /// Callback to send out events from the oracle + async fn send( + bytes: bytes::Bytes, + sender: BoundedSender, + ) -> WithStatus<&'static str> { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event).await { + Ok(()) => { + warp::reply::with_status("OK", warp::http::StatusCode::OK) + } + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) + } + } + } } #[cfg(test)] From d318bbb8eed2039a35bd17c6dd63ae11447078c9 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 6 Oct 2022 10:51:35 +0200 Subject: [PATCH 0827/1995] [fix]: Added a check that the channel used to send eth events to the ledger is closed. Otherwise, the oracle hangs --- apps/src/lib/node/ledger/ethereum_node/events.rs | 2 +- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 0d176b875f..4071d6a0f0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -202,7 +202,7 @@ pub mod eth_events { /// Check if the minimum number of confirmations has been /// reached at the input block height. pub fn is_confirmed(&self, height: &Uint256) -> bool { - self.confirmations >= height.clone() - self.block_height.clone() + self.confirmations <= height.clone() - self.block_height.clone() } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index b46b8185db..e97021962c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -73,6 +73,9 @@ impl Oracle { /// N.B. this will block if the internal channel buffer /// is full. async fn send(&self, events: Vec) -> bool { + if self.sender.is_closed() { + return false; + } for event in events.into_iter() { if self.sender.send(event).await.is_err() { return false; From 8a355b71f514ed93f6d0bfd9b034824a77853b9a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 15:05:05 +0100 Subject: [PATCH 0828/1995] Refactor events endpoint module into a new file --- .../test_tools/events_endpoint.rs | 59 ++++++++++++++++ .../{test_tools.rs => test_tools/mod.rs} | 69 +------------------ apps/src/lib/node/ledger/mod.rs | 2 +- 3 files changed, 62 insertions(+), 68 deletions(-) create mode 100644 apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs rename apps/src/lib/node/ledger/ethereum_node/{test_tools.rs => test_tools/mod.rs} (75%) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs new file mode 100644 index 0000000000..b5a3e8b4d6 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -0,0 +1,59 @@ +use borsh::BorshDeserialize; +use namada::types::ethereum_events::EthereumEvent; +use tokio::sync::mpsc::UnboundedSender; + +const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); + +/// The path to which Borsh-serialized Ethereum events should be submitted +const PATH: &str = "eth_events"; + +pub fn serve( + sender: UnboundedSender, +) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + use warp::Filter; + + tracing::info!( + ?DEFAULT_ENDPOINT, + "Ethereum event endpoint is starting" + ); + + let eth_events = warp::post() + .and(warp::path(PATH)) + .and(warp::body::bytes()) + .map(move |bytes: bytes::Bytes| { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event) { + Ok(()) => warp::reply::with_status( + "OK", + warp::http::StatusCode::OK, + ), + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) + } + } + }); + + warp::serve(eth_events).run(DEFAULT_ENDPOINT).await; + + tracing::info!( + ?DEFAULT_ENDPOINT, + "Ethereum event endpoint is no longer running" + ); + }) +} diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs similarity index 75% rename from apps/src/lib/node/ledger/ethereum_node/test_tools.rs rename to apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs index 17dc762da0..1788bc3982 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs @@ -1,3 +1,5 @@ +pub mod events_endpoint; + /// tools for running a mock ethereum fullnode process pub mod mock_eth_fullnode { use async_trait::async_trait; @@ -50,73 +52,6 @@ pub mod mock_oracle { } } -pub mod event_endpoint { - use borsh::BorshDeserialize; - use namada::types::ethereum_events::EthereumEvent; - use tokio::sync::mpsc::Sender as BoundedSender; - use warp::reply::WithStatus; - - const ETHEREUM_EVENTS_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); - - /// The path to which Borsh-serialized Ethereum events should be submitted - const PATH: &str = "eth_events"; - - pub fn start_oracle( - sender: BoundedSender, - ) -> tokio::task::JoinHandle<()> { - tokio::task::spawn_local(async move { - use warp::Filter; - - tracing::info!( - ?ETHEREUM_EVENTS_ENDPOINT, - "Ethereum event endpoint is starting" - ); - let eth_events = warp::post() - .and(warp::path(PATH)) - .and(warp::body::bytes()) - .then(move |bytes: bytes::Bytes| send(bytes, sender.clone())); - - warp::serve(eth_events).run(ETHEREUM_EVENTS_ENDPOINT).await; - - tracing::info!( - ?ETHEREUM_EVENTS_ENDPOINT, - "Ethereum event endpoint is no longer running" - ); - }) - } - - /// Callback to send out events from the oracle - async fn send( - bytes: bytes::Bytes, - sender: BoundedSender, - ) -> WithStatus<&'static str> { - tracing::info!(len = bytes.len(), "Received request"); - let event = match EthereumEvent::try_from_slice(&bytes) { - Ok(event) => event, - Err(error) => { - tracing::warn!(?error, "Couldn't handle request"); - return warp::reply::with_status( - "Bad request", - warp::http::StatusCode::BAD_REQUEST, - ); - } - }; - tracing::debug!("Serialized event - {:#?}", event); - match sender.send(event).await { - Ok(()) => { - warp::reply::with_status("OK", warp::http::StatusCode::OK) - } - Err(error) => { - tracing::warn!(?error, "Couldn't send event"); - warp::reply::with_status( - "Internal server error", - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - } - } - } -} - #[cfg(test)] pub mod mock_web3_client { use std::cell::RefCell; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 657f73e0b4..3808e26861 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -672,7 +672,7 @@ async fn start_ethereum_node( abort_sender, ), ethereum::Mode::EventsEndpoint => { - ethereum_node::test_tools::event_endpoint::start_oracle(eth_sender) + ethereum_node::test_tools::events_endpoint::serve(eth_sender) } ethereum::Mode::Off => { ethereum_node::test_tools::mock_oracle::run_oracle( From 4a8f17689ab9b6dff66bf141393e480bb3f09b79 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 15:16:29 +0100 Subject: [PATCH 0829/1995] Change default endpoint to be 0.0.0.0:3030 --- .../lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index b5a3e8b4d6..d48c977c3f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -2,7 +2,7 @@ use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; use tokio::sync::mpsc::UnboundedSender; -const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([127, 0, 0, 1], 3030); +const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); /// The path to which Borsh-serialized Ethereum events should be submitted const PATH: &str = "eth_events"; From 61cebed170370d90f06c0cfe7b8b15554d348713 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 15:48:32 +0100 Subject: [PATCH 0830/1995] Ensure events endpoint shuts down correctly --- .../test_tools/events_endpoint.rs | 89 ++++++++++--------- apps/src/lib/node/ledger/mod.rs | 5 +- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index d48c977c3f..879a651d9c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -1,6 +1,8 @@ use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; +use tokio::macros::support::poll_fn; use tokio::sync::mpsc::UnboundedSender; +use warp::Filter; const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); @@ -9,51 +11,52 @@ const PATH: &str = "eth_events"; pub fn serve( sender: UnboundedSender, + mut abort_sender: tokio::sync::oneshot::Sender<()>, ) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { - use warp::Filter; - - tracing::info!( - ?DEFAULT_ENDPOINT, - "Ethereum event endpoint is starting" - ); - - let eth_events = warp::post() - .and(warp::path(PATH)) - .and(warp::body::bytes()) - .map(move |bytes: bytes::Bytes| { - tracing::info!(len = bytes.len(), "Received request"); - let event = match EthereumEvent::try_from_slice(&bytes) { - Ok(event) => event, - Err(error) => { - tracing::warn!(?error, "Couldn't handle request"); - return warp::reply::with_status( - "Bad request", - warp::http::StatusCode::BAD_REQUEST, - ); - } - }; - tracing::debug!("Serialized event - {:#?}", event); - match sender.send(event) { - Ok(()) => warp::reply::with_status( - "OK", - warp::http::StatusCode::OK, - ), - Err(error) => { - tracing::warn!(?error, "Couldn't send event"); - warp::reply::with_status( - "Internal server error", - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - } + let eth_events = warp::post() + .and(warp::path(PATH)) + .and(warp::body::bytes()) + .map(move |bytes: bytes::Bytes| { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event) { + Ok(()) => { + warp::reply::with_status("OK", warp::http::StatusCode::OK) + } + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) } - }); + } + }); - warp::serve(eth_events).run(DEFAULT_ENDPOINT).await; + let (_, server) = warp::serve(eth_events).bind_with_graceful_shutdown( + DEFAULT_ENDPOINT, + async move { + tracing::info!( + ?DEFAULT_ENDPOINT, + "Starting to listen for Borsh-serialized Ethereum events" + ); + poll_fn(|cx| abort_sender.poll_closed(cx)).await; + tracing::info!( + ?DEFAULT_ENDPOINT, + "Stopping listening for Borsh-serialized Ethereum events" + ); + }, + ); - tracing::info!( - ?DEFAULT_ENDPOINT, - "Ethereum event endpoint is no longer running" - ); - }) + tokio::task::spawn(server) } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3808e26861..062a65343b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -672,7 +672,10 @@ async fn start_ethereum_node( abort_sender, ), ethereum::Mode::EventsEndpoint => { - ethereum_node::test_tools::events_endpoint::serve(eth_sender) + ethereum_node::test_tools::events_endpoint::serve( + eth_sender, + abort_sender, + ) } ethereum::Mode::Off => { ethereum_node::test_tools::mock_oracle::run_oracle( From 70db5d72044ad1f5202a206e589238aeb403a9c0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 15:51:26 +0100 Subject: [PATCH 0831/1995] Add some docstrings --- .../node/ledger/ethereum_node/test_tools/events_endpoint.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index 879a651d9c..856260fec7 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -4,11 +4,15 @@ use tokio::macros::support::poll_fn; use tokio::sync::mpsc::UnboundedSender; use warp::Filter; +/// The default IP address and port on which the events endpoint will listen const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); -/// The path to which Borsh-serialized Ethereum events should be submitted +/// The path to which Borsh-serialized Ethereum events should be POSTed const PATH: &str = "eth_events"; +/// Starts a [`warp::Server`] that listens for Borsh-serialized Ethereum events +/// and then forwards them to `sender`. It shuts down if `abort_sender` is +/// closed. pub fn serve( sender: UnboundedSender, mut abort_sender: tokio::sync::oneshot::Sender<()>, From a2cc798d622e9b5a26d3604a46bfb7ed6c063c16 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 16:43:40 +0100 Subject: [PATCH 0832/1995] Apply suggested renames --- .../ethereum_node/test_tools/events_endpoint.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index 856260fec7..cd0b7e7bf8 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -4,11 +4,12 @@ use tokio::macros::support::poll_fn; use tokio::sync::mpsc::UnboundedSender; use warp::Filter; -/// The default IP address and port on which the events endpoint will listen -const DEFAULT_ENDPOINT: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); +/// The default IP address and port on which the events endpoint will listen. +const DEFAULT_LISTEN_ADDR: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); -/// The path to which Borsh-serialized Ethereum events should be POSTed -const PATH: &str = "eth_events"; +/// The endpoint to which Borsh-serialized Ethereum events should be sent to, +/// via an HTTP POST request. +const EVENTS_POST_ENDPOINT: &str = "eth_events"; /// Starts a [`warp::Server`] that listens for Borsh-serialized Ethereum events /// and then forwards them to `sender`. It shuts down if `abort_sender` is @@ -18,7 +19,7 @@ pub fn serve( mut abort_sender: tokio::sync::oneshot::Sender<()>, ) -> tokio::task::JoinHandle<()> { let eth_events = warp::post() - .and(warp::path(PATH)) + .and(warp::path(EVENTS_POST_ENDPOINT)) .and(warp::body::bytes()) .map(move |bytes: bytes::Bytes| { tracing::info!(len = bytes.len(), "Received request"); @@ -48,15 +49,15 @@ pub fn serve( }); let (_, server) = warp::serve(eth_events).bind_with_graceful_shutdown( - DEFAULT_ENDPOINT, + DEFAULT_LISTEN_ADDR, async move { tracing::info!( - ?DEFAULT_ENDPOINT, + ?DEFAULT_LISTEN_ADDR, "Starting to listen for Borsh-serialized Ethereum events" ); poll_fn(|cx| abort_sender.poll_closed(cx)).await; tracing::info!( - ?DEFAULT_ENDPOINT, + ?DEFAULT_LISTEN_ADDR, "Stopping listening for Borsh-serialized Ethereum events" ); }, From dba2e2bea1672b307f96f70dddb3abf3c1c262cd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 6 Oct 2022 10:33:10 +0100 Subject: [PATCH 0833/1995] Fix up events endpoint with bounded channel changes --- .../test_tools/events_endpoint.rs | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index cd0b7e7bf8..864865b2e0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -1,7 +1,8 @@ use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; use tokio::macros::support::poll_fn; -use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::Sender as BoundedSender; +use warp::reply::WithStatus; use warp::Filter; /// The default IP address and port on which the events endpoint will listen. @@ -15,38 +16,14 @@ const EVENTS_POST_ENDPOINT: &str = "eth_events"; /// and then forwards them to `sender`. It shuts down if `abort_sender` is /// closed. pub fn serve( - sender: UnboundedSender, + sender: BoundedSender, mut abort_sender: tokio::sync::oneshot::Sender<()>, ) -> tokio::task::JoinHandle<()> { + tracing::info!(?DEFAULT_LISTEN_ADDR, "Ethereum event endpoint is starting"); let eth_events = warp::post() .and(warp::path(EVENTS_POST_ENDPOINT)) .and(warp::body::bytes()) - .map(move |bytes: bytes::Bytes| { - tracing::info!(len = bytes.len(), "Received request"); - let event = match EthereumEvent::try_from_slice(&bytes) { - Ok(event) => event, - Err(error) => { - tracing::warn!(?error, "Couldn't handle request"); - return warp::reply::with_status( - "Bad request", - warp::http::StatusCode::BAD_REQUEST, - ); - } - }; - tracing::debug!("Serialized event - {:#?}", event); - match sender.send(event) { - Ok(()) => { - warp::reply::with_status("OK", warp::http::StatusCode::OK) - } - Err(error) => { - tracing::warn!(?error, "Couldn't send event"); - warp::reply::with_status( - "Internal server error", - warp::http::StatusCode::INTERNAL_SERVER_ERROR, - ) - } - } - }); + .then(move |bytes: bytes::Bytes| send(bytes, sender.clone())); let (_, server) = warp::serve(eth_events).bind_with_graceful_shutdown( DEFAULT_LISTEN_ADDR, @@ -65,3 +42,32 @@ pub fn serve( tokio::task::spawn(server) } + +/// Callback to send out events from the oracle +async fn send( + bytes: bytes::Bytes, + sender: BoundedSender, +) -> WithStatus<&'static str> { + tracing::info!(len = bytes.len(), "Received request"); + let event = match EthereumEvent::try_from_slice(&bytes) { + Ok(event) => event, + Err(error) => { + tracing::warn!(?error, "Couldn't handle request"); + return warp::reply::with_status( + "Bad request", + warp::http::StatusCode::BAD_REQUEST, + ); + } + }; + tracing::debug!("Serialized event - {:#?}", event); + match sender.send(event).await { + Ok(()) => warp::reply::with_status("OK", warp::http::StatusCode::OK), + Err(error) => { + tracing::warn!(?error, "Couldn't send event"); + warp::reply::with_status( + "Internal server error", + warp::http::StatusCode::INTERNAL_SERVER_ERROR, + ) + } + } +} From bb23503b6387a7f489463d79a57894ff95499098 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 7 Sep 2022 10:27:28 +0100 Subject: [PATCH 0834/1995] Preliminary Ethereum bridge smart contract specs --- .../ethereum-bridge/bootstrapping.md | 14 +- .../ethereum_smart_contracts.md | 203 +++++++++++++++--- 2 files changed, 187 insertions(+), 30 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md index 1a1f141db3..876f83ec09 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/bootstrapping.md @@ -3,8 +3,12 @@ ## Overview The Ethereum bridge is not enabled at the launch of a Namada chain. Instead, -there is a governance parameter, `eth_bridge_proxy_address`, which is -initialized to the zero Ethereum address +there are two governance parameters: + +- `eth_bridge_proxy_address` +- `eth_bridge_wnam_address` + +Both are initialized to the zero Ethereum address (`"0x0000000000000000000000000000000000000000"`). An overview of the steps to enable the Ethereum bridge for a given Namada chain are: @@ -58,8 +62,7 @@ do the Ethereum bridge smart contract deployment. If for some reason the validity of the smart contract deployment cannot be agreed upon by the validators who will responsible for restarting Namada, it must remain possible to restart the chain with the Ethereum bridge still not -enabled i.e. with `eth_bridge_proxy_address = -"0x0000000000000000000000000000000000000000"`. +enabled. ## Example @@ -102,7 +105,8 @@ validator set does not change at any point. - Validators coordinate to craft a new genesis file for the chain restart at `3400`, with the governance parameter `eth_bridge_proxy_address` set to - `0x00000000000000000000000000000000DeaDBeef` + `0x00000000000000000000000000000000DeaDBeef` and `eth_bridge_wnam_address` at + `0x000000000000000000000000000000000000Cafe` - The chain restarts at `3400` (the first block of epoch `34`) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 962b64149d..3ceee927c9 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -1,27 +1,180 @@ # Ethereum Smart Contracts -The set of Ethereum contracts should perform the following functions: - -- Verify bridge header proofs from Namada so that Namada messages can - be submitted to the contract. -- Verify and maintain evolving validator sets with corresponding stake - and public keys. -- Emit log messages readable by Namada -- Handle ICS20-style token transfer messages appropriately with escrow & - unescrow on the Ethereum side -- Allow for message batching - -Furthermore, the Ethereum contracts will whitelist ETH and tokens that -flow across the bridge as well as ensure limits on transfer volume per epoch. - -An Ethereum smart contract should perform the following steps to verify -a proof from Namada: - -1. Check the epoch included in the proof. -2. Look up the validator set corresponding to said epoch. -3. Verify that the signatures included amount to at least 2 / 3 of the - total stake. -4. Check the validity of each signature. - -If all the above verifications succeed, the contract may affect the -appropriate state change, emit logs, etc. \ No newline at end of file +## Contracts + +There are five smart contracts that make up an Ethereum bridge deployment. + +- Proxy +- Bridge +- Governance +- Vault +- wNAM + +### Proxy + +The _Proxy_ contract serves as a dumb storage for holding the addresses of other +contracts, specifically the _Governance_ contract, the _Vault_ contract and the +current _Bridge_ contract. Once deployed, it is modifiable only by the +_Governance_ contract, to update the address for which contract is the current +_Bridge_ contract. + +The _Proxy_ contract is fixed forever once the bridge has been deployed. + +### Bridge + +The _Bridge_ contract is the only contract that unprivileged users of the bridge +may interact with. It provides methods for transferring ERC20s to Namada +(holding them in escrow in the _Vault_), as well as releasing escrowed ERC20s +from the _Vault_ for transfers made from Namada to Ethereum. It holds a +whitelist of ERC20s that may cross the bridge, and this whitelist may be updated +by the _Governance_ contract. + +### Governance + +The _Governance_ contract may "upgrade" the bridge by updating the _Proxy_ +contract to point to a new _Bridge_ contract. It may also withdraw all funds +from the _Vault_ to any specified Ethereum address, if a quorum of validators +choose to do so. + +### wNAM + +The _wNAM_ contract is a simple ERC20 token with a fixed supply, which is all +minted when the bridge is first deployed. After initial deployment, the entire +supply of _wNAM_ belongs to the _Vault_ contract. As NAM is transferred from +Namada to Ethereum, wNAM may be released from the _Vault_ by the _Bridge_. + +The _wNAM_ contract is fixed forever once the bridge has been deployed. + +### Vault + +The _Vault_ contract holds in escrow any ERC20 tokens that have been sent over +the bridge to Namada, as well as a supply of _wNAM_ ERC20s to represent NAM that +has been sent from Namada to Ethereum. Funds held by the _Vault_ may only be +spendable by the current _Bridge_ contract. When ERC20 tokens are transferred +from Ethereum to Namada, they must be deposited to the _Vault_ via the _Bridge_ +contract. + +The _Vault_ contract is fixed forever once the bridge has been deployed. + +## Namada-side configuration + +When an account on Namada becomes a validator, they must provide two Ethereum +secp256k1 keys: + +- a "hot" key for normal operations +- a "cold" key for exceptional operations, like emergency withdrawal from the + bridge + +These keys are used to control the bridge smart contracts, via signing of +messages. Validators may be challenged periodically to prove they still retain +knowledge of these keys. + +## Deployment + +The contracts should be deployable by anyone to any EVM chain using an automated +script. The following configuration should be agreed up front by Namada +governance before deployment: + +- details of the initial active validator set that will control the bridge - + specifically, for each validator: + - their hot Ethereum address + - their cold Ethereum address + - their voting power on Namada for the epoch when the bridge will launch +- the total supply of the wNAM ERC20 token, which will represent Namada-native + NAM on the EVM chain +- an initial whitelist of ERC20 tokens that may cross the bridge from Ethereum + to Namada - specifically, for each whitelisted ERC20: + - the Ethereum address of the ERC20 contract + - a cap on the total amount that may cross the bridge, in units of ERC20 + +After a deployment has finished successfully, the deployer must not have any +privileged control of any of the contracts deployed. Any privileged actions must +only be possible via a message signed by a validator set that the smart +contracts are storing details of. + +## Communication + +### From Ethereum to Namada + +A Namada chain's validators are configured to listen to events emitted by the +smart contracts pointed to by the _Proxy_ contract. The address of the _Proxy_ +contract is set in a governance parameter in Namada storage. Namada validators +treat emitted events as authoritative and take action on them. Namada also knows +the address of the _wNAM_ ERC20 contract via a governance parameter, and treats +transfers of this ERC20 to Namada as an indication to release native NAM from +the `#EthBridgeEscrow` account on Namada, rather than to mint a wrapped ERC20 as +is the case with all other ERC20s. + +### From Namada to Ethereum + +At any time, the _Governance_ and _Bridge_ contracts must store: + +- a hash of the current Namada epoch's active validator set +- a hash of another epoch's active validator set. When the bridge is first + deployed, this will also be the current Namada epoch's active validator set, + but after the first valset update is submitted to the _Governance_ smart + contract, this hash will always be an adjacent Namada epoch's active validator + set i.e. either the previous epoch's, or the next epoch's + +In the case of the _Governance_ contract, these are hashes of a map of +validator's _cold_ key addresses to their voting powers, while for the _Bridge_ +contract it is hashes of a map of validator's _hot_ key addresses to their +voting powers. Namada validators may post signatures as on chain of relevant +messages to be relayed to the Ethereum bridge smart contracts (e.g. validator +set updates, pending transfers, etc.). Methods of the Ethereum bridge smart +contracts should generally accept: + +- some message +- full details of some active validator set (i.e. relevant Ethereum addresses + + voting powers) +- signatures over the message by validators from the this active validator set + +Given this data, anyone should be able to make the relevant Ethereum smart +contract method call, if they are willing to pay the Ethereum gas. A call is +then authorized to happen if: + +- the active validator set specified in the call hashes to *either* of the + validator set hashes stored in the smart contract +- a quorum (i.e. more than 2/3 by voting power) of the signatures over the + message are valid + +### Valset updates + +Initial deployment aside, at the beginning of each epoch, the smart contracts +will contain details of the current epoch's validator set and the previous +epoch's validator set. Namada validators must endeavor to sign details of the +next epoch's validator set and post them on Namada chain in a protocol +transaction. Details of the next epoch's validator set and a quorum of +signatures over it by validators from the current epoch's validator set must +then be relayed to the _Governance_ contract before the end of the epoch, which +will update both the _Governance_ and _Bridge_ smart contracts to have the hash +of the next epoch's validator set rather than the previous epoch's validator +set. This should happen before the current Namada epoch ends. If this does not +happen, then the Namada chain must either halt or not progress to the next +epoch, to avoid losing control of the bridge. + +When a valset update is submitted, the hashes for the oldest valset are +effectively "evicted" from the _Governance_ and _Bridge_ smart contracts. At +that point, messages signed by that evicted valset will no longer be accepted by +the bridge. + +#### Example flow + +- Namada epoch `10` begins. Currently, the _Governance_ contract knows the + hashes of the valsets for epochs `9` and `10`, as does the _Bridge_ contract. +- Validators for epoch `10` post signatures over the hash of details of the + valset for epoch `11` to Namada as protocol transactions +- A point is reached during epoch `10` at which a quorum of such signatures is + present on the Namada chain +- A relayer submits a valset update for epoch `11` to _Governance_, using a + quorum of signatures from the Namada chain +- The _Governance_ and _Bridge_ contracts now know the hashes of the valsets for + epochs `10` and `11`, and will accept messages signed by either of them. It + will no longer accept messages signed by the valset for epoch `9`. +- Namada progresses to epoch `11`, and the flow repeats + +NB: the flow for when the bridge has just launched is similar, except the +contracts know the details of only one epoch's valset - the launch epoch's. E.g. +if the bridge launches at epoch `10`, then initially the contracts know the hash +only for epoch `10` and not epochs `10` and `11`, until the first valset update +has been submitted \ No newline at end of file From d6d0dcba1df0088c9feadfc1e10f6e26097f2369 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 20 Sep 2022 10:13:14 +0100 Subject: [PATCH 0835/1995] Use terms 'bridge' and 'governance' keys --- .../ethereum-bridge/ethereum_smart_contracts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 3ceee927c9..6ca2852ced 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -61,8 +61,8 @@ The _Vault_ contract is fixed forever once the bridge has been deployed. When an account on Namada becomes a validator, they must provide two Ethereum secp256k1 keys: -- a "hot" key for normal operations -- a "cold" key for exceptional operations, like emergency withdrawal from the +- the bridge key - a hot key for normal operations +- the governance key - a cold key for exceptional operations, like emergency withdrawal from the bridge These keys are used to control the bridge smart contracts, via signing of From 6f66e56a221a5fc53731f03b45499b764eb037a6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 20 Sep 2022 10:15:31 +0100 Subject: [PATCH 0836/1995] Replace usages of valset with validator set --- .../ethereum_smart_contracts.md | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 6ca2852ced..01270a856b 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -62,8 +62,8 @@ When an account on Namada becomes a validator, they must provide two Ethereum secp256k1 keys: - the bridge key - a hot key for normal operations -- the governance key - a cold key for exceptional operations, like emergency withdrawal from the - bridge +- the governance key - a cold key for exceptional operations, like emergency + withdrawal from the bridge These keys are used to control the bridge smart contracts, via signing of messages. Validators may be challenged periodically to prove they still retain @@ -112,9 +112,9 @@ At any time, the _Governance_ and _Bridge_ contracts must store: - a hash of the current Namada epoch's active validator set - a hash of another epoch's active validator set. When the bridge is first deployed, this will also be the current Namada epoch's active validator set, - but after the first valset update is submitted to the _Governance_ smart - contract, this hash will always be an adjacent Namada epoch's active validator - set i.e. either the previous epoch's, or the next epoch's + but after the first validator set update is submitted to the _Governance_ + smart contract, this hash will always be an adjacent Namada epoch's active + validator set i.e. either the previous epoch's, or the next epoch's In the case of the _Governance_ contract, these are hashes of a map of validator's _cold_ key addresses to their voting powers, while for the _Bridge_ @@ -138,7 +138,7 @@ then authorized to happen if: - a quorum (i.e. more than 2/3 by voting power) of the signatures over the message are valid -### Valset updates +### Validator set updates Initial deployment aside, at the beginning of each epoch, the smart contracts will contain details of the current epoch's validator set and the previous @@ -153,28 +153,30 @@ set. This should happen before the current Namada epoch ends. If this does not happen, then the Namada chain must either halt or not progress to the next epoch, to avoid losing control of the bridge. -When a valset update is submitted, the hashes for the oldest valset are -effectively "evicted" from the _Governance_ and _Bridge_ smart contracts. At -that point, messages signed by that evicted valset will no longer be accepted by -the bridge. +When a validator set update is submitted, the hashes for the oldest validator +set are effectively "evicted" from the _Governance_ and _Bridge_ smart +contracts. At that point, messages signed by that evicted validator set will no +longer be accepted by the bridge. #### Example flow - Namada epoch `10` begins. Currently, the _Governance_ contract knows the - hashes of the valsets for epochs `9` and `10`, as does the _Bridge_ contract. + hashes of the validator sets for epochs `9` and `10`, as does the _Bridge_ + contract. - Validators for epoch `10` post signatures over the hash of details of the - valset for epoch `11` to Namada as protocol transactions + validator set for epoch `11` to Namada as protocol transactions - A point is reached during epoch `10` at which a quorum of such signatures is present on the Namada chain -- A relayer submits a valset update for epoch `11` to _Governance_, using a - quorum of signatures from the Namada chain -- The _Governance_ and _Bridge_ contracts now know the hashes of the valsets for - epochs `10` and `11`, and will accept messages signed by either of them. It - will no longer accept messages signed by the valset for epoch `9`. +- A relayer submits a validator set update for epoch `11` to _Governance_, using + a quorum of signatures from the Namada chain +- The _Governance_ and _Bridge_ contracts now know the hashes of the validator + sets for epochs `10` and `11`, and will accept messages signed by either of + them. It will no longer accept messages signed by the validator set for epoch + `9`. - Namada progresses to epoch `11`, and the flow repeats NB: the flow for when the bridge has just launched is similar, except the -contracts know the details of only one epoch's valset - the launch epoch's. E.g. -if the bridge launches at epoch `10`, then initially the contracts know the hash -only for epoch `10` and not epochs `10` and `11`, until the first valset update -has been submitted \ No newline at end of file +contracts know the details of only one epoch's validator set - the launch +epoch's. E.g. if the bridge launches at epoch `10`, then initially the contracts +know the hash only for epoch `10` and not epochs `10` and `11`, until the first +validator set update has been submitted \ No newline at end of file From bdfbb618c4269d3a75d596bc8581848254062232 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 20 Sep 2022 10:32:25 +0100 Subject: [PATCH 0837/1995] Mention governance contract can upgrade itself --- .../ethereum-bridge/ethereum_smart_contracts.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 01270a856b..7428d3697e 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -32,9 +32,9 @@ by the _Governance_ contract. ### Governance The _Governance_ contract may "upgrade" the bridge by updating the _Proxy_ -contract to point to a new _Bridge_ contract. It may also withdraw all funds -from the _Vault_ to any specified Ethereum address, if a quorum of validators -choose to do so. +contract to point to a new _Bridge_ contract and/or a new _Governance_ contract. +It may also withdraw all funds from the _Vault_ to any specified Ethereum +address, if a quorum of validators choose to do so. ### wNAM From 5d85690980202180795536f7fb9f806af3256039 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 20 Sep 2022 10:34:09 +0100 Subject: [PATCH 0838/1995] Fix capitalization --- .../ethereum-bridge/ethereum_smart_contracts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 7428d3697e..7010042536 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -133,9 +133,9 @@ Given this data, anyone should be able to make the relevant Ethereum smart contract method call, if they are willing to pay the Ethereum gas. A call is then authorized to happen if: -- the active validator set specified in the call hashes to *either* of the +- The active validator set specified in the call hashes to *either* of the validator set hashes stored in the smart contract -- a quorum (i.e. more than 2/3 by voting power) of the signatures over the +- A quorum (i.e. more than 2/3 by voting power) of the signatures over the message are valid ### Validator set updates From 824cb43b5af11aba360bdadf036e24ae13613a35 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 14:38:07 +0100 Subject: [PATCH 0839/1995] Add Ethereum bridge mode for using a remote endpoint Co-authored-by: James Hiew --- apps/src/lib/config/ethereum.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum.rs index 52d757c67b..d02df90184 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum.rs @@ -14,12 +14,15 @@ pub enum Mode { /// oracle is configured to listen for events from the Ethereum bridge /// smart contracts using this endpoint. Managed, + /// Do not run `geth`. The oracle will listen to the Ethereum JSON-RPC + /// endpoint as specified in the `oracle_rpc_endpoint` setting. + Remote, /// Do not start a managed `geth` subprocess. Instead of the oracle /// listening for events using a Ethereum JSON-RPC endpoint, an endpoint /// will be exposed by the ledger itself for submission of Borsh- /// serialized [`EthereumEvent`]s. Mostly useful for testing purposes. EventsEndpoint, - /// Do not run any components of the Ethereum bridge + /// Do not run any components of the Ethereum bridge. Off, } From b5dab6aee574d49fb682b776ec804b8f39cc6e13 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 4 Oct 2022 11:38:05 +0100 Subject: [PATCH 0840/1995] Mention that validators should be challenged for their cold key periodically --- .../ethereum-bridge/ethereum_smart_contracts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md index 7010042536..669122b420 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_smart_contracts.md @@ -66,8 +66,8 @@ secp256k1 keys: withdrawal from the bridge These keys are used to control the bridge smart contracts, via signing of -messages. Validators may be challenged periodically to prove they still retain -knowledge of these keys. +messages. Validators should be challenged periodically to prove they still retain +knowledge of their governance key, which is not regularly used. ## Deployment From c85ed34f0784f13af240f2d51ec80f47c2237875 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Sep 2022 14:42:23 +0100 Subject: [PATCH 0841/1995] Handle remote endpoint Ethereum mode during ledger startup Co-authored-by: James Hiew --- apps/src/lib/node/ledger/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 062a65343b..80fc9ecdb0 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -666,11 +666,13 @@ async fn start_ethereum_node( // Start the oracle for listening to Ethereum events let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); let oracle = match config.ethereum.mode { - ethereum::Mode::Managed => ethereum_node::oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ), + ethereum::Mode::Managed | ethereum::Mode::Remote => { + ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ) + } ethereum::Mode::EventsEndpoint => { ethereum_node::test_tools::events_endpoint::serve( eth_sender, From 1f75022eb4f6aba07ff74afcd0dc004a14842bb5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 9 Sep 2022 12:58:21 +0100 Subject: [PATCH 0842/1995] Make an attempt to rate limit the oracle --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index e97021962c..c9cb356fed 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -132,11 +132,21 @@ async fn run_oracle_aux(oracle: Oracle) { // awaiting a certain number of confirmations let mut latest_block; let mut pending: Vec = Vec::new(); + const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); loop { + tokio::time::sleep(SLEEP_DUR).await; // update the latest block height latest_block = loop { - if let Ok(height) = oracle.eth_block_number().await { - break height; + match oracle.eth_block_number().await { + Ok(height) => break height, + Err(error) => { + tracing::warn!( + ?error, + "Couldn't get the latest Ethereum block height, will \ + keep trying" + ); + tokio::time::sleep(SLEEP_DUR).await; + } } if !oracle.connected() { tracing::info!( @@ -146,6 +156,7 @@ async fn run_oracle_aux(oracle: Oracle) { return; } }; + tracing::debug!(?latest_block, "Got latest Ethereum block height"); // No blocks in existence yet with enough confirmations if Uint256::from(MIN_CONFIRMATIONS) > latest_block { if !oracle.connected() { From d71088ec2bf5e5cce4a11ad4bf7e24d7936a77a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 23 Sep 2022 10:05:51 +0100 Subject: [PATCH 0843/1995] Remove mutable binding --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index c9cb356fed..d26aaadea7 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -130,13 +130,12 @@ async fn run_oracle_aux(oracle: Oracle) { // Initialize our local state. This includes // the latest block height seen and a queue of events // awaiting a certain number of confirmations - let mut latest_block; let mut pending: Vec = Vec::new(); const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); loop { tokio::time::sleep(SLEEP_DUR).await; // update the latest block height - latest_block = loop { + let latest_block = loop { match oracle.eth_block_number().await { Ok(height) => break height, Err(error) => { From 0d15fb38625fa21e44a517e1df9587e6452c78db Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 7 Oct 2022 11:13:33 +0200 Subject: [PATCH 0844/1995] [feat]: Added multi-store logic and brige pool merkle tree --- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 4 +- .../lib/node/ledger/shell/prepare_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 4 +- .../shell/vote_extensions/eth_events.rs | 2 +- .../shell/vote_extensions/val_set_update.rs | 2 +- apps/src/lib/node/ledger/storage/mod.rs | 2 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 10 +- shared/src/ledger/storage/ics23_specs.rs | 2 + shared/src/ledger/storage/merkle_tree.rs | 465 +++++++++--------- shared/src/ledger/storage/mod.rs | 44 +- shared/src/ledger/storage/traits.rs | 52 +- shared/src/types/hash.rs | 36 -- tests/src/native_vp/mod.rs | 2 +- tests/src/native_vp/pos.rs | 2 +- tests/src/vm_host_env/ibc.rs | 2 +- tests/src/vm_host_env/vp.rs | 2 +- wasm/checksums.json | 33 +- wasm/tx_template/Cargo.lock | 162 +----- wasm/vp_template/Cargo.lock | 162 +----- wasm/wasm_source/Cargo.lock | 162 +----- wasm_for_tests/wasm_source/Cargo.lock | 162 +----- 23 files changed, 386 insertions(+), 932 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 90bb6994b1..4d21e63ca4 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -11,7 +11,7 @@ use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::{DB, DBIter, Storage, traits::StorageHasher}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2be5913d96..aff407ba6b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -28,7 +28,7 @@ use namada::ledger::pos::namada_proof_of_stake::types::{ use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ - DBIter, Sha256Hasher, Storage, StorageHasher, DB, + DBIter, traits::Sha256Hasher, Storage, traits::StorageHasher, DB, }; use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; @@ -760,7 +760,7 @@ mod test_utils { #[cfg(not(feature = "abcipp"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; + use namada::ledger::storage::{BlockStateWrite, MerkleTree, traits::Sha256Hasher}; use namada::types::address::{xan, EstablishedAddressGen}; use namada::types::chain::ChainId; use namada::types::hash::Hash; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index de33ebc38b..6880bd0598 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,6 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d4ade3b380..5d04cef45c 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -152,7 +152,7 @@ where for PrefixValue { key, value } in &values { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(p) => { @@ -221,7 +221,7 @@ where let proof_ops = if is_proven { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(proof) => Some({ diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index fe1bcd20f9..ba8a96c9e5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index fc07a19f76..358bafa838 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; use namada::types::voting_power::FractionalVotingPower; diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 2876236bca..8017c6b7e4 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -9,7 +9,7 @@ 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 namada::ledger::storage::{Storage, traits::StorageHasher}; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 6af0011dcc..128900240c 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -930,7 +930,7 @@ mod imp { #[cfg(test)] mod test { - use namada::ledger::storage::{MerkleTree, Sha256Hasher}; + use namada::ledger::storage::{MerkleTree, traits::Sha256Hasher}; use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index c09bdcb4e8..f900efb595 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -509,7 +509,7 @@ mod test_bridge_pool_tree { transfer: TransferToEthereum { asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), - amount: (1 as u64).into(), + amount: 1u64.into(), nonce: 42u64.into(), }, gas_fee: GasFee { @@ -529,7 +529,7 @@ mod test_bridge_pool_tree { transfer: TransferToEthereum { asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), - amount: (1 as u64).into(), + amount: 1u64.into(), nonce: 42u64.into(), }, gas_fee: GasFee { @@ -557,7 +557,7 @@ mod test_bridge_pool_tree { transfer: TransferToEthereum { asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), - amount: (1 as u64).into(), + amount: 1.into(), nonce: 42u64.into(), }, gas_fee: GasFee { @@ -571,7 +571,7 @@ mod test_bridge_pool_tree { transfer: TransferToEthereum { asset: EthAddress([1; 20]), recipient: EthAddress([0; 20]), - amount: (1 as u64).into(), + amount: 1u64.into(), nonce: 42u64.into(), }, gas_fee: GasFee { @@ -812,7 +812,7 @@ mod test_bridge_pool_tree { addrs.into_iter().map(| (addr, nonce)| PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress(addr.clone()), + asset: EthAddress(addr), recipient: EthAddress(addr), amount: Default::default(), nonce: nonce.into() diff --git a/shared/src/ledger/storage/ics23_specs.rs b/shared/src/ledger/storage/ics23_specs.rs index 3bb1680bad..00691bd30e 100644 --- a/shared/src/ledger/storage/ics23_specs.rs +++ b/shared/src/ledger/storage/ics23_specs.rs @@ -45,6 +45,7 @@ pub fn ibc_leaf_spec() -> LeafOp { } /// Get the proof specs for ibc +#[allow(dead_code)] pub fn ibc_proof_specs() -> Vec { let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { @@ -59,6 +60,7 @@ pub fn ibc_proof_specs() -> Vec { } /// Get the proof specs +#[allow(dead_code)] pub fn proof_specs() -> Vec { let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 92637476b8..ba20544d7d 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -365,7 +365,7 @@ impl MerkleTree { "No keys provided for existence proof.".into(), ) })?; - let (store_type, _) = StoreType::sub_key(first_key)?; + let (store_type, sub_key) = StoreType::sub_key(first_key)?; if !keys.iter().all(|k| { if let Ok((s, _)) = StoreType::sub_key(k) { s == store_type @@ -380,7 +380,7 @@ impl MerkleTree { )); } self.tree(&store_type) - .subtree_membership_proof(keys, values) + .subtree_membership_proof(std::array::from_ref(&sub_key), values) } /// Get the non-existence proof @@ -567,224 +567,243 @@ impl From for Error { } } -// #[cfg(test)] -// mod test { -// use super::*; -// use crate::types::storage::KeySeg; -// -// #[test] -// fn test_crud_value() { -// 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(); -// -// assert!(!tree.has_key(&ibc_key).unwrap()); -// assert!(!tree.has_key(&pos_key).unwrap()); -// -// update IBC tree -// tree.update(&ibc_key, [1u8; 8]).unwrap(); -// assert!(tree.has_key(&ibc_key).unwrap()); -// assert!(!tree.has_key(&pos_key).unwrap()); -// update another tree -// tree.update(&pos_key, [2u8; 8]).unwrap(); -// assert!(tree.has_key(&pos_key).unwrap()); -// -// delete a value on IBC tree -// tree.delete(&ibc_key).unwrap(); -// assert!(!tree.has_key(&ibc_key).unwrap()); -// assert!(tree.has_key(&pos_key).unwrap()); -// } -// -// #[test] -// fn test_restore_tree() { -// 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(); -// -// tree.update(&ibc_key, [1u8; 8]).unwrap(); -// tree.update(&pos_key, [2u8; 8]).unwrap(); -// -// 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).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()); -// assert!(restored_tree.has_key(&pos_key).unwrap()); -// } -// -// #[test] -// fn test_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.clone()).unwrap(); -// let pos_val = [2u8; 8].to_vec(); -// tree.update(&pos_key, pos_val).unwrap(); -// -// 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(), store_type.to_string()]; -// let mut sub_root = ibc_val.clone(); -// let mut value = ibc_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_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_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_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 nep_verification_res = ics23::verify_non_membership( -// &nep_commitment_proof, -// &specs[0], -// &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); -// } -// } +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::ics23_specs::{ibc_proof_specs, proof_specs}; + use crate::ledger::storage::traits::Sha256Hasher; + use crate::types::storage::KeySeg; + + #[test] + fn test_crud_value() { + 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(); + + assert!(!tree.has_key(&ibc_key).unwrap()); + assert!(!tree.has_key(&pos_key).unwrap()); + + // update IBC tree + tree.update(&ibc_key, [1u8; 8]).unwrap(); + assert!(tree.has_key(&ibc_key).unwrap()); + assert!(!tree.has_key(&pos_key).unwrap()); + // update another tree + tree.update(&pos_key, [2u8; 8]).unwrap(); + assert!(tree.has_key(&pos_key).unwrap()); + + // delete a value on IBC tree + tree.delete(&ibc_key).unwrap(); + assert!(!tree.has_key(&ibc_key).unwrap()); + assert!(tree.has_key(&pos_key).unwrap()); + } + + #[test] + fn test_restore_tree() { + 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(); + + tree.update(&ibc_key, [1u8; 8]).unwrap(); + tree.update(&pos_key, [2u8; 8]).unwrap(); + + 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).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()); + assert!(restored_tree.has_key(&pos_key).unwrap()); + } + + #[test] + fn test_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.clone()).unwrap(); + let pos_val = [2u8; 8].to_vec(); + tree.update(&pos_key, pos_val).unwrap(); + + let specs = ibc_proof_specs::(); + let proof = match tree + .get_sub_tree_existence_proof( + std::array::from_ref(&ibc_key), + vec![ibc_val.clone().into()], + ) + .unwrap(){ + MembershipProof::ICS23(proof) => proof, + _ => panic!("Test failed"), + }; + let proof = tree.get_tendermint_proof(&ibc_key, proof).unwrap(); + let (store_type, sub_key) = StoreType::sub_key(&ibc_key).unwrap(); + let paths = vec![sub_key.to_string(), store_type.to_string()]; + let mut sub_root = ibc_val.clone(); + let mut value = ibc_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_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 = proof_specs::(); + let proof = match tree.get_sub_tree_existence_proof( + std::array::from_ref(&pos_key), + vec![pos_val.clone().into()], + ) + .unwrap() + { + MembershipProof::ICS23(proof) => proof, + _ => panic!("Test failed"), + }; + + let proof = tree.get_tendermint_proof(&pos_key, proof).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_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_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 = ibc_proof_specs::(); + + let nep_verification_res = ics23::verify_non_membership( + &nep_commitment_proof, + &specs[0], + &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 74bde36b0f..1dd03af3d9 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -506,25 +506,47 @@ where (self.block.hash.clone(), BLOCK_HASH_LENGTH as _) } - /// Get the existence proof + /// Get a Tendermint-compatible existence proof. + /// + /// Proofs from the Ethereum bridge pool are not + /// Tendermint-compatible. Requesting for a key + /// belonging to the bridge pool will cause this + /// method to error. pub fn get_existence_proof( &self, key: &Key, value: MerkleValue, height: BlockHeight, - ) -> Result { + ) -> Result { if height >= self.get_block_height().0 { - Ok(self.block.tree.get_sub_tree_existence_proof( - array::from_ref(key), - vec![value], - )?) + if let MembershipProof::ICS23(proof) = self + .block + .tree + .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) + .map_err(Error::MerkleTreeError)? { + self.block + .tree + .get_tendermint_proof(key, proof) + .map_err(Error::MerkleTreeError) + } else { + Err(Error::MerkleTreeError(MerkleTreeError::TendermintProof)) + } } else { match self.db.read_merkle_tree_stores(height)? { - Some(stores) => Ok(MerkleTree::::new(stores) - .get_sub_tree_existence_proof( - array::from_ref(key), - vec![value], - )?), + Some(stores) => { + let tree = MerkleTree::::new(stores); + if let MembershipProof::ICS23(proof) = tree + .get_sub_tree_existence_proof( + array::from_ref(key), + vec![value], + ) + .map_err(Error::MerkleTreeError)? { + tree.get_tendermint_proof(key, proof) + .map_err(Error::MerkleTreeError) + } else { + Err(Error::MerkleTreeError(MerkleTreeError::TendermintProof)) + } + } None => Err(Error::NoMerkleTree { height }), } } diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 907aab2ed9..fb7b5b662c 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -1,6 +1,6 @@ //! Traits needed to provide a uniform interface over //! all the different Merkle trees used for storage -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use std::fmt; use arse_merkle_tree::traits::{Hasher, Value}; @@ -45,9 +45,10 @@ pub trait SubTreeWrite { impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { fn subtree_has_key(&self, key: &Key) -> Result { - self.get(&H::hash(key.to_string()).into()) - .and(Ok(true)) - .map_err(|err| Error::MerkleTree(err.to_string())) + match self.get(&H::hash(key.to_string()).into()) { + Ok(hash) => Ok(!hash.is_zero()), + Err(e) => Err(Error::MerkleTree(e.to_string())), + } } fn subtree_membership_proof( @@ -88,11 +89,10 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { value: MerkleValue, ) -> Result { let value = match value { - MerkleValue::Bytes(bytes) => Hash::try_from(bytes.as_slice()) - .map_err(|_| Error::InvalidValue)?, + MerkleValue::Bytes(bytes) => H::hash(bytes.as_slice()), _ => return Err(Error::InvalidValue), }; - self.update(H::hash(key.to_string()).into(), value) + self.update(H::hash(key.to_string()).into(), value.into()) .map(Hash::from) .map_err(|err| Error::MerkleTree(err.to_string())) } @@ -108,9 +108,10 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { fn subtree_has_key(&self, key: &Key) -> Result { let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; - self.get(&key) - .and(Ok(true)) - .map_err(|err| Error::MerkleTree(err.to_string())) + match self.get(&key) { + Ok(hash) => Ok(!hash.is_zero()), + Err(e) => Err(Error::MerkleTree(e.to_string())), + } } fn subtree_membership_proof( @@ -237,6 +238,35 @@ impl TreeKey for StringKey { } } +impl Value for Hash { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + Hash([0u8; 32]) + } +} + +impl From for H256 { + fn from(hash: Hash) -> Self { + hash.0.into() + } +} + +impl From for Hash { + fn from(hash: H256) -> Self { + Self(hash.into()) + } +} + +impl From<&H256> for Hash { + fn from(hash: &H256) -> Self { + let hash = hash.to_owned(); + Self(hash.into()) + } +} + impl Value for TreeBytes { fn as_slice(&self) -> &[u8] { self.0.as_slice() @@ -262,7 +292,7 @@ impl Hasher for Sha256Hasher { self.0.update(h) } - fn finish(self) -> arse_merkle_tree::H256 { + fn finish(self) -> H256 { let hash = self.0.finalize(); let bytes: [u8; 32] = hash .as_slice() diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 5a6abd146c..4e63af1ad9 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,7 +4,6 @@ use std::fmt::{self, Display}; use std::ops::Deref; use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; use serde::{Deserialize, Serialize}; @@ -133,38 +132,3 @@ 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.0.into() - } -} - -impl From for TreeHash { - fn from(hash: Hash) -> Self { - Self::from(hash.0) - } -} - -impl Value for Hash { - fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } - - fn zero() -> Self { - Hash([0u8; 32]) - } -} diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index be450a7086..5540ac26d4 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -2,7 +2,7 @@ mod pos; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; -use namada::ledger::storage::Sha256Hasher; +use namada::ledger::storage::traits::Sha256Hasher; use namada::vm::wasm::compilation_cache; use namada::vm::wasm::compilation_cache::common::Cache; use namada::vm::{wasm, WasmCacheRwAccess}; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index be1844c6cd..7be49ac538 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -411,7 +411,7 @@ mod tests { // 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 = vp_env.validate_tx(PosVP::new, |_tx_data| {}); // Put the tx_env back before checking the result tx_host_env::set(vp_env.tx_env); let result = diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index e1c372a85f..8e6fa0a254 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -59,7 +59,7 @@ 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::storage::traits::Sha256Hasher; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 61b87e1b3b..06cd6d0981 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -88,7 +88,7 @@ mod native_vp_host_env { // TODO replace with `std::concat_idents` once stabilized (https://github.com/rust-lang/rust/issues/29599) use concat_idents::concat_idents; - use namada::ledger::storage::Sha256Hasher; + use namada::ledger::storage::traits::Sha256Hasher; use namada::vm::host_env::*; use namada::vm::WasmCacheRwAccess; diff --git a/wasm/checksums.json b/wasm/checksums.json index 29ccd4fd2b..0c091181df 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { + "tx_bond.wasm": "tx_bond.aac575fac2ddaba8fca6f341b2ca6a77dd430767aae628e75aebf0b05470173f.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.de38def08090ac1a912d24a1cf0dd03b0b1ad70eca06dcd6745af9c7175be450.wasm", - "tx_ibc.wasm": "tx_ibc.8fd090bdd02727239c63ff6a2a880124320d744972723315983ce855201c605e.wasm", - "tx_init_account.wasm": "tx_init_account.abfeeca2aeecdacf3ff3db022c9aaf85040b38164703a4ee7ad6e42afacdd961.wasm", - "tx_init_nft.wasm": "tx_init_nft.0be8aba244a529f8bb65692bbf0c939550ca9bdd00de6b8df68f1780dd8d7c2d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.67cc820c203ed6f379110c94b7ad16544c03a4d94c77fbbacba1c02dd0869b0b.wasm", - "tx_init_validator.wasm": "tx_init_validator.c9d864bac4cc3090b456f07deb956b2763d8c9fbda38762953398332182ac791.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.453eed7e258e8bda5ff3136ca89e90f3dcc601d1e62dd0991c1d18e404e96d73.wasm", - "tx_transfer.wasm": "tx_transfer.cdad1f1189e5a56aa9cbd542fc983397fd24172c99368623b28001b6c440d2e1.wasm", - "tx_unbond.wasm": "tx_unbond.7498c085a34e8507d46e4eebbd9dcb442d3431685aba15da406f3b74d214e4cf.wasm", - "tx_update_vp.wasm": "tx_update_vp.0e6fe53decf185f6c8715b70a610177c035827370fe472e71a55ff5bf04d7273.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.cfa3277e9be0442f20496a115b31ec8ea96fbc98a26ea65129a68166464a4c40.wasm", - "tx_withdraw.wasm": "tx_withdraw.63b3513b411659e4ab6c63c61bb56aff33f6f39cb42d9f8618cd373320dcc087.wasm", - "vp_nft.wasm": "vp_nft.2f21e99208c9c0842106b67fa4f113e9aa76ce009a486788e5458247fd7ef1ba.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7135a8139a99f1e6cdb3c44e5a6824ddfb8c3db158b3284191d4f53d18f38710.wasm", - "vp_token.wasm": "vp_token.0cd22c3ba1c234bfbfc3a0d0913a9dc230c22ec7f629ba74907e8af40de687a4.wasm", - "vp_user.wasm": "vp_user.112577f72fcaf46c25389f67a4a643e71d61f7fb01447970acdf96a8ad72e9ec.wasm" + "tx_from_intent.wasm": "tx_from_intent.4c77cf173ef5e3f71991aa2b5ea8a2ca4c81ed55367eb6b16cae899e6552fda2.wasm", + "tx_ibc.wasm": "tx_ibc.ad63ce4b85a0885124ab0ecec94824de76aa1ef276a0a91851c6159f7720a5f7.wasm", + "tx_init_account.wasm": "tx_init_account.14762c8749535ec551e8d8678f882297eda4786ed7e5417f925dfc555df060c5.wasm", + "tx_init_nft.wasm": "tx_init_nft.563859381ec97298028d1efa446a10152159d241ce776a06948df541258cbf01.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.28b5d79cfe0625c88d1537f93806fe2a773019b0869c379750f00d5eab127d68.wasm", + "tx_init_validator.wasm": "tx_init_validator.90a284f98ade8d39f834ffd80f395d1471a657a6a9c8e43fce248814a92591cb.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9a392ed8d1aa98d3c767609d99fc9ba828426175160ea5ecabaed837e7045603.wasm", + "tx_transfer.wasm": "tx_transfer.39eb3118c1d59a567580bcb8a78d5639edba4dccd723867854f8739268b55f71.wasm", + "tx_unbond.wasm": "tx_unbond.b4e1d04dea54fcfde4faee0645a968f74328c07fd860bc4893640b9a5d7cd41f.wasm", + "tx_update_vp.wasm": "tx_update_vp.0af0bab850894a75585463b7569acf025dddb775cffbe1ae896d1ac7e39b350f.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.17caf0ac3b5ef72883f131996c2c0520d6512868372095f9f4091f0d400bb8c9.wasm", + "tx_withdraw.wasm": "tx_withdraw.ccb05868b2591271bdf50b2bfb6e985b5472551b760840c515c8c20fa554a3d1.wasm", + "vp_nft.wasm": "vp_nft.3ce385731fe6a691be4cbae0bc99e51d290bbf06310058a5d696b0877b669994.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1f22309dfd457d3e6ff3b0af014193855b807fffbe32bec19e9486e9342ff0ce.wasm", + "vp_token.wasm": "vp_token.c0a629ec9f93db0f09ff56b7089813c0a87a5cd4ad2d8e35012819cdc9b2c63c.wasm", + "vp_user.wasm": "vp_user.43ca141419537eae40363ea0d87e10b04a8d0a507ee2182a65c6c02f5e42b264.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 3afe9398ee..6415ecf977 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -546,18 +534,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" @@ -568,16 +544,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" @@ -639,15 +605,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" @@ -716,18 +673,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" @@ -770,24 +715,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" @@ -911,16 +838,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 = "fixed-hash" version = "0.7.0" @@ -1070,17 +987,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" @@ -1134,20 +1040,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1174,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1305,19 +1201,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" @@ -2181,17 +2064,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" @@ -2368,18 +2240,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" @@ -2509,10 +2369,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" @@ -2634,7 +2490,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2642,12 +2498,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2664,7 +2518,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2677,7 +2531,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2690,7 +2544,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2707,7 +2561,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2731,7 +2585,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 711a7dfa61..3f0160f2e8 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -546,18 +534,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" @@ -568,16 +544,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" @@ -639,15 +605,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" @@ -716,18 +673,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" @@ -770,24 +715,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" @@ -911,16 +838,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 = "fixed-hash" version = "0.7.0" @@ -1070,17 +987,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" @@ -1134,20 +1040,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1174,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1305,19 +1201,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" @@ -2181,17 +2064,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" @@ -2368,18 +2240,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" @@ -2509,10 +2369,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" @@ -2634,7 +2490,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2642,12 +2498,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2664,7 +2518,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2677,7 +2531,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2690,7 +2544,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2707,7 +2561,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2731,7 +2585,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index a71a76a13c..a156f2291b 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -193,12 +193,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" @@ -417,12 +411,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" @@ -564,18 +552,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.6" @@ -586,16 +562,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" @@ -656,15 +622,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" @@ -733,18 +690,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.5.2" @@ -787,24 +732,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" -[[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" @@ -928,16 +855,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 = "fixed-hash" version = "0.7.0" @@ -1086,17 +1003,6 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -[[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" @@ -1159,16 +1065,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 = "iana-time-zone" version = "0.1.47" @@ -1186,7 +1082,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1213,7 +1109,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1343,19 +1239,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.2" @@ -2238,17 +2121,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" @@ -2425,18 +2297,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" @@ -2566,10 +2426,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" @@ -2685,7 +2541,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2693,12 +2549,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2715,7 +2569,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2728,7 +2582,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2741,7 +2595,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2758,7 +2612,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2782,7 +2636,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 654c0b9898..7521fbccca 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -184,12 +184,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" @@ -406,12 +400,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" @@ -547,18 +535,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" @@ -569,16 +545,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" @@ -640,15 +606,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" @@ -717,18 +674,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.1" @@ -771,24 +716,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" @@ -912,16 +839,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 = "fixed-hash" version = "0.7.0" @@ -1071,17 +988,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.1" @@ -1144,20 +1050,10 @@ 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 = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "bytes", "derive_more", @@ -1184,7 +1080,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=6255c4e01db11781e5a8cdb89737f55a8ad81d63#6255c4e01db11781e5a8cdb89737f55a8ad81d63" dependencies = [ "base64", "bytes", @@ -1315,19 +1211,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" @@ -2212,17 +2095,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" @@ -2399,18 +2271,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" @@ -2540,10 +2400,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" @@ -2665,7 +2521,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "async-trait", "bytes", @@ -2673,12 +2529,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", @@ -2695,7 +2549,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "flex-error", "serde", @@ -2708,7 +2562,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "derive_more", "flex-error", @@ -2721,7 +2575,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2738,7 +2592,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "bytes", "flex-error", @@ -2762,7 +2616,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" +source = "git+https://github.com/heliaxdev/tendermint-rs?branch=bat/abciplus#2e9e0d058a68e2534974ff7d22b9058d4ebda3be" dependencies = [ "ed25519-dalek", "gumdrop", From 510e74ce54fee4839c3e70d9aa5556752427d754 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 7 Oct 2022 16:27:58 +0100 Subject: [PATCH 0845/1995] Update TransferToNamada event signature --- .../lib/node/ledger/ethereum_node/events.rs | 198 ++++++++++++------ 1 file changed, 135 insertions(+), 63 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 4071d6a0f0..0ffd91167f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -1,6 +1,6 @@ pub mod signatures { pub const TRANSFER_TO_NAMADA_SIG: &str = - "TransferToNamada(uint256,address[],string[],uint256[],uint32)"; + "TransferToNamada(uint256,(address,uint256,string)[],uint256)"; pub const TRANSFER_TO_ETHEREUM_SIG: &str = "TransferToErc(uint256,address[],address[],uint256[],uint32)"; pub const VALIDATOR_SET_UPDATE_SIG: &str = @@ -236,57 +236,54 @@ pub mod eth_events { /// Parse ABI serialized data from an Ethereum event into /// an instance of [`RawTransfersToNamada`] fn decode(data: &[u8]) -> Result { - let [nonce, assets, receivers, amounts, confs]: [Token; 5] = - decode( - &[ + let [nonce, transfers, confs]: [Token; 3] = decode( + &[ + ParamType::Uint(256), + ParamType::Array(Box::new(ParamType::Tuple(vec![ + ParamType::Address, ParamType::Uint(256), - ParamType::Array(Box::new(ParamType::Address)), - ParamType::Array(Box::new(ParamType::String)), - ParamType::Array(Box::new(ParamType::Uint(256))), - ParamType::Uint(32), - ], - data, - ) - .map_err(|err| Error::Decode(format!("{:?}", err)))? - .try_into() - .map_err(|_| { - Error::Decode( - "TransferToNamada signature should contain five types" - .to_string(), - ) - })?; - - let assets = assets.parse_eth_address_array()?; - let receivers = receivers.parse_address_array()?; - let amounts = amounts.parse_amount_array()?; - if assets.len() != amounts.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ - transfer amounts" - .into(), - )) - } else if receivers.len() != assets.len() { - Err(Error::Decode( - "Number of source addresses is different from number of \ - target addresses" - .into(), + ParamType::String, + ]))), + ParamType::Uint(256), + ], + data, + ) + .map_err(|err| Error::Decode(format!("{:#?}", err)))? + .try_into() + .map_err(|error| { + Error::Decode(format!( + "TransferToNamada signature should contain three types: \ + {:?}", + error )) - } else { - Ok(Self { - transfers: assets - .into_iter() - .zip(receivers.into_iter()) - .zip(amounts.into_iter()) - .map(|((asset, receiver), amount)| TransferToNamada { - amount, - asset, - receiver, - }) - .collect(), - nonce: nonce.parse_uint256()?, - confirmations: confs.parse_u32()?, - }) + })?; + + let transfers = transfers.parse_namada_transfer_array()?; + + let mut assets = vec![]; + let mut amounts = vec![]; + let mut receivers = vec![]; + + for (asset, amount, receiver) in transfers.into_iter() { + assets.push(asset); + amounts.push(amount); + receivers.push(receiver); } + + Ok(Self { + transfers: assets + .into_iter() + .zip(receivers.into_iter()) + .zip(amounts.into_iter()) + .map(|((asset, receiver), amount)| TransferToNamada { + amount, + asset, + receiver, + }) + .collect(), + nonce: nonce.parse_uint256()?, + confirmations: confs.parse_u32()?, + }) } /// Serialize an instance [`RawTransfersToNamada`] using Ethereum's @@ -298,31 +295,27 @@ pub mod eth_events { nonce, confirmations, } = self; - let amounts: Vec = transfers - .iter() - .map(|TransferToNamada { amount, .. }| { - Token::Uint(u64::from(*amount).into()) - }) - .collect(); - let (assets, receivers): (Vec, Vec) = transfers + + let transfers = transfers .into_iter() .map( |TransferToNamada { - asset, receiver, .. + asset, + receiver, + amount, }| { - ( + Token::Tuple(vec![ Token::Address(asset.0.into()), + Token::Uint(u64::from(amount).into()), Token::String(receiver.to_string()), - ) + ]) }, ) - .unzip(); + .collect(); encode(&[ Token::Uint(nonce.into()), - Token::Array(assets), - Token::Array(receivers), - Token::Array(amounts), + Token::Array(transfers), Token::Uint(confirmations.into()), ]) } @@ -582,6 +575,11 @@ pub mod eth_events { fn parse_eth_address_array(self) -> Result>; fn parse_address_array(self) -> Result>; fn parse_string_array(self) -> Result>; + fn parse_namada_transfer_array( + self, + ) -> Result>; + fn parse_namada_transfer(self) + -> Result<(EthAddress, Amount, Address)>; } impl Parse for Token { @@ -711,6 +709,41 @@ pub mod eth_events { Ok(addrs) } + fn parse_namada_transfer_array( + self, + ) -> Result> { + let array = if let Token::Array(array) = self { + array + } else { + return Err(Error::Decode(format!( + "Expected type `Array`, got {:?}", + self + ))); + }; + let mut transfers = vec![]; + for token in array.into_iter() { + let transfer = token.parse_namada_transfer()?; + transfers.push(transfer); + } + Ok(transfers) + } + + fn parse_namada_transfer( + self, + ) -> Result<(EthAddress, Amount, Address)> { + if let Token::Tuple(tuple) = self { + let asset = tuple[0].clone().parse_eth_address()?; + let amount = tuple[1].clone().parse_amount()?; + let receiver = tuple[2].clone().parse_address()?; + Ok((asset, amount, receiver)) + } else { + Err(Error::Decode(format!( + "Expected type `Tuple`, got {:?}", + self + ))) + } + } + fn parse_address_array(self) -> Result> { let array = if let Token::Array(array) = self { array @@ -750,6 +783,45 @@ pub mod eth_events { mod test_events { use super::*; + #[test] + fn test_transfer_to_namada_decode() { + let data: Vec = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 95, 189, 178, 49, 86, 120, 175, 236, 179, 103, 240, + 50, 217, 63, 100, 47, 100, 24, 10, 163, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 84, 97, 116, 101, 115, 116, 49, 118, 52, 101, 104, + 103, 119, 51, 54, 120, 117, 117, 110, 119, 100, 54, 57, 56, 57, + 112, 114, 119, 100, 102, 107, 120, 113, 109, 110, 118, 115, + 102, 106, 120, 115, 54, 110, 118, 118, 54, 120, 120, 117, 99, + 114, 115, 51, 102, 51, 120, 99, 109, 110, 115, 51, 102, 99, + 120, 100, 122, 114, 118, 118, 122, 57, 120, 118, 101, 114, 122, + 118, 122, 114, 53, 54, 108, 101, 56, 102, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + ]; + + let raw = RawTransfersToNamada::decode(&data); + + let raw = raw.unwrap(); + assert_eq!( + raw.transfers, + vec![TransferToNamada { + amount: Amount::from(100), + asset: EthAddress::from_str("0x5FbDB2315678afecb367f032d93F642f64180aa3").unwrap(), + receiver: Address::decode("atest1v4ehgw36xuunwd6989prwdfkxqmnvsfjxs6nvv6xxucrs3f3xcmns3fcxdzrvvz9xverzvzr56le8f").unwrap(), + }] + ) + } /// For each of the basic types, test that roundtrip /// encoding - decoding is a no-op #[test] From c074f640deca520aac6bb6b45937ef237654970e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 7 Oct 2022 16:44:47 +0100 Subject: [PATCH 0846/1995] Collapse types --- .../lib/node/ledger/ethereum_node/events.rs | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 0ffd91167f..bcb2b3dfe5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -258,29 +258,8 @@ pub mod eth_events { )) })?; - let transfers = transfers.parse_namada_transfer_array()?; - - let mut assets = vec![]; - let mut amounts = vec![]; - let mut receivers = vec![]; - - for (asset, amount, receiver) in transfers.into_iter() { - assets.push(asset); - amounts.push(amount); - receivers.push(receiver); - } - Ok(Self { - transfers: assets - .into_iter() - .zip(receivers.into_iter()) - .zip(amounts.into_iter()) - .map(|((asset, receiver), amount)| TransferToNamada { - amount, - asset, - receiver, - }) - .collect(), + transfers: transfers.parse_transfer_to_namada_array()?, nonce: nonce.parse_uint256()?, confirmations: confs.parse_u32()?, }) @@ -575,11 +554,10 @@ pub mod eth_events { fn parse_eth_address_array(self) -> Result>; fn parse_address_array(self) -> Result>; fn parse_string_array(self) -> Result>; - fn parse_namada_transfer_array( + fn parse_transfer_to_namada_array( self, - ) -> Result>; - fn parse_namada_transfer(self) - -> Result<(EthAddress, Amount, Address)>; + ) -> Result>; + fn parse_transfer_to_namada(self) -> Result; } impl Parse for Token { @@ -709,9 +687,9 @@ pub mod eth_events { Ok(addrs) } - fn parse_namada_transfer_array( + fn parse_transfer_to_namada_array( self, - ) -> Result> { + ) -> Result> { let array = if let Token::Array(array) = self { array } else { @@ -722,20 +700,22 @@ pub mod eth_events { }; let mut transfers = vec![]; for token in array.into_iter() { - let transfer = token.parse_namada_transfer()?; + let transfer = token.parse_transfer_to_namada()?; transfers.push(transfer); } Ok(transfers) } - fn parse_namada_transfer( - self, - ) -> Result<(EthAddress, Amount, Address)> { + fn parse_transfer_to_namada(self) -> Result { if let Token::Tuple(tuple) = self { let asset = tuple[0].clone().parse_eth_address()?; let amount = tuple[1].clone().parse_amount()?; let receiver = tuple[2].clone().parse_address()?; - Ok((asset, amount, receiver)) + Ok(TransferToNamada { + asset, + amount, + receiver, + }) } else { Err(Error::Decode(format!( "Expected type `Tuple`, got {:?}", From 470dda3514fa2183ea7aa47c78e494b22ead22d9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 09:53:08 +0100 Subject: [PATCH 0847/1995] parse_transfer_to_namada: don't use clone --- apps/src/lib/node/ledger/ethereum_node/events.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index bcb2b3dfe5..69f7716d66 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -707,10 +707,10 @@ pub mod eth_events { } fn parse_transfer_to_namada(self) -> Result { - if let Token::Tuple(tuple) = self { - let asset = tuple[0].clone().parse_eth_address()?; - let amount = tuple[1].clone().parse_amount()?; - let receiver = tuple[2].clone().parse_address()?; + if let Token::Tuple(mut items) = self { + let asset = items.remove(0).parse_eth_address()?; + let amount = items.remove(0).parse_amount()?; + let receiver = items.remove(0).parse_address()?; Ok(TransferToNamada { asset, amount, From 9bccd81502be42f992bb823081254780792615ea Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 10 Oct 2022 11:05:02 +0200 Subject: [PATCH 0848/1995] [chore]: Merging in some changes from code review --- shared/src/ledger/storage/merkle_tree.rs | 77 +++++++++++++++++++++--- shared/src/ledger/storage/traits.rs | 15 ++--- 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index ba20544d7d..5e1efa6c74 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -335,7 +335,12 @@ impl MerkleTree { /// 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.tree_mut(&store_type).subtree_delete(&sub_key) + let sub_root = self.tree_mut(&store_type).subtree_delete(&sub_key)?; + if store_type != StoreType::Base { + let base_key = H::hash(&store_type.to_string()); + self.base.update(base_key.into(), sub_root)?; + } + Ok(()) } /// Get the root @@ -402,10 +407,12 @@ impl MerkleTree { 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(ibc_leaf_spec::()); + if let Some(left) = left.as_mut() { + left.leaf = Some(ibc_leaf_spec::()); + } + if let Some(right) = right.as_mut() { + right.leaf = Some(ibc_leaf_spec::()); + } } _ => unreachable!(), } @@ -580,6 +587,7 @@ mod test { let key_prefix: Key = Address::Internal(InternalAddress::Ibc).to_db_key().into(); let ibc_key = key_prefix.push(&"test".to_string()).unwrap(); + let ibc_non_key = key_prefix.push(&"test2".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(); @@ -595,10 +603,65 @@ mod test { tree.update(&pos_key, [2u8; 8]).unwrap(); assert!(tree.has_key(&pos_key).unwrap()); + // update IBC tree + tree.update(&ibc_non_key, [2u8; 8]).unwrap(); + assert!(tree.has_key(&ibc_non_key).unwrap()); + assert!(tree.has_key(&ibc_key).unwrap()); + assert!(tree.has_key(&pos_key).unwrap()); // delete a value on IBC tree - tree.delete(&ibc_key).unwrap(); - assert!(!tree.has_key(&ibc_key).unwrap()); + tree.delete(&ibc_non_key).unwrap(); + assert!(!tree.has_key(&ibc_non_key).unwrap()); + assert!(tree.has_key(&ibc_key).unwrap()); assert!(tree.has_key(&pos_key).unwrap()); + + // get and verify non-existence proof for the deleted key + 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 = ibc_proof_specs::(); + + let nep_verification_res = ics23::verify_non_membership( + &nep_commitment_proof, + &specs[0], + &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); } #[test] diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index fb7b5b662c..5491b78685 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -40,7 +40,7 @@ pub trait SubTreeWrite { value: MerkleValue, ) -> Result; /// Delete a key from the sub-tree - fn subtree_delete(&mut self, key: &Key) -> Result<(), Error>; + fn subtree_delete(&mut self, key: &Key) -> Result; } impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { @@ -97,10 +97,10 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { .map_err(|err| Error::MerkleTree(err.to_string())) } - fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + fn subtree_delete(&mut self, key: &Key) -> Result { let value = Hash::zero(); self.update(H::hash(key.to_string()).into(), value) - .and(Ok(())) + .map(Hash::from) .map_err(|err| Error::MerkleTree(err.to_string())) } } @@ -156,11 +156,11 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { .map_err(|err| Error::MerkleTree(err.to_string())) } - fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + fn subtree_delete(&mut self, key: &Key) -> Result { let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = TreeBytes::zero(); self.update(key, value) - .and(Ok(())) + .map(Hash::from) .map_err(|err| Error::MerkleTree(format!("{:?}", err))) } } @@ -203,9 +203,10 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { } } - fn subtree_delete(&mut self, key: &Key) -> Result<(), Error> { + fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) - .map_err(|err| Error::MerkleTree(err.to_string())) + .map_err(|err| Error::MerkleTree(err.to_string()))?; + Ok(self.root()) } } From 35bb9d4c2b5cfb257d898d322eabeb137c1072dd Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 10 Oct 2022 14:56:17 +0200 Subject: [PATCH 0849/1995] [feat]: Small cleanups. Added the signed merkle tree root to accounts storage --- shared/src/ledger/storage/merkle_tree.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 5e1efa6c74..21f16031ad 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -17,7 +17,7 @@ use thiserror::Error; use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; -use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; +use crate::ledger::eth_bridge::storage::bridge_pool::{BridgePoolTree, get_signed_root_key}; use crate::ledger::storage::ics23_specs::ibc_leaf_spec; use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; @@ -177,7 +177,13 @@ impl StoreType { Ok((StoreType::Ibc, key.sub_key()?)) } InternalAddress::EthBridgePool => { - Ok((StoreType::BridgePool, key.sub_key()?)) + // the root of this sub-tree is kept in accounts + // storage along with a quorum of validator signatures + if *key == get_signed_root_key() { + Ok((StoreType::Account, key.clone())) + } else { + Ok((StoreType::BridgePool, key.sub_key()?)) + } } // use the same key for Parameters _ => Ok((StoreType::Account, key.clone())), From b67aacd5cf62a4021fb2448aecb4234e2c563e3f Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 0850/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b7b8cf1cf2..5d6f4d7177 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -228,8 +211,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -240,9 +223,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -252,9 +233,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f900efb595..47e4f62a66 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool #[derive( Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index db7cada8ec..08ee160662 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From bedf293d856598a60ad2f4a68677eeb45f048e3d Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 16:28:22 +0200 Subject: [PATCH 0851/1995] [feat]: Refactor our merkle trees as a multistore --- apps/src/lib/node/ledger/protocol/mod.rs | 3 +- .../transactions/ethereum_events/events.rs | 3 +- .../transactions/ethereum_events/mod.rs | 5 +- .../transactions/ethereum_events/read.rs | 3 +- .../transactions/ethereum_events/update.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/prepare_proposal.rs | 3 +- apps/src/lib/node/ledger/shell/queries.rs | 4 +- .../shell/vote_extensions/eth_events.rs | 3 +- .../shell/vote_extensions/val_set_update.rs | 3 +- apps/src/lib/node/ledger/storage/mod.rs | 3 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 3 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 6 +- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- shared/src/ledger/governance/mod.rs | 3 +- shared/src/ledger/governance/parameters.rs | 2 +- shared/src/ledger/governance/utils.rs | 3 +- shared/src/ledger/governance/vp.rs | 3 +- shared/src/ledger/ibc/handler.rs | 13 +- shared/src/ledger/ibc/mod.rs | 3 +- shared/src/ledger/ibc/vp/channel.rs | 3 +- shared/src/ledger/ibc/vp/client.rs | 3 +- shared/src/ledger/ibc/vp/connection.rs | 3 +- shared/src/ledger/ibc/vp/mod.rs | 3 +- shared/src/ledger/ibc/vp/packet.rs | 3 +- shared/src/ledger/ibc/vp/port.rs | 3 +- shared/src/ledger/ibc/vp/sequence.rs | 3 +- shared/src/ledger/ibc/vp/token.rs | 3 +- shared/src/ledger/native_vp.rs | 3 +- shared/src/ledger/parameters/mod.rs | 19 +- shared/src/ledger/pos/mod.rs | 3 +- shared/src/ledger/pos/storage.rs | 3 +- shared/src/ledger/pos/vp.rs | 3 +- shared/src/ledger/storage/ics23_specs.rs | 12 +- shared/src/ledger/storage/merkle_tree.rs | 514 ++++++------------ shared/src/ledger/storage/mod.rs | 40 +- shared/src/ledger/storage/traits.rs | 276 ++++++++++ shared/src/ledger/storage/write_log.rs | 3 +- shared/src/ledger/treasury/mod.rs | 3 +- shared/src/ledger/treasury/parameters.rs | 2 +- shared/src/ledger/vp_env.rs | 3 +- shared/src/types/hash.rs | 42 -- shared/src/types/storage.rs | 45 +- shared/src/vm/host_env.rs | 6 +- shared/src/vm/wasm/host_env.rs | 3 +- shared/src/vm/wasm/run.rs | 3 +- tests/src/native_vp/mod.rs | 2 +- tests/src/vm_host_env/ibc.rs | 2 +- tests/src/vm_host_env/vp.rs | 2 +- 49 files changed, 622 insertions(+), 470 deletions(-) create mode 100644 shared/src/ledger/storage/traits.rs diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 8e46225a13..9ffc1cd367 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -12,8 +12,9 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index 2564830a2c..b639a01fd2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -4,7 +4,8 @@ use std::collections::BTreeSet; use eyre::Result; use namada::ledger::eth_bridge::storage::wrapped_erc20s; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::storage::Key; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index a9b24deb39..8d9ea01eff 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -14,7 +14,8 @@ use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::eth_msgs::Keys; use namada::ledger::pos::types::WeightedValidator; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::{self, BlockHeight}; @@ -306,7 +307,7 @@ mod tests { use namada::ledger::pos::types::ValidatorSet; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; - use namada::ledger::storage::Sha256Hasher; + use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs index 5f377f8133..90cc960509 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -1,7 +1,8 @@ //! Helpers for reading from storage use borsh::BorshDeserialize; use eyre::{eyre, Result}; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index d6aebac3b6..092dada10a 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -1,7 +1,8 @@ //! Helpers for writing to storage use borsh::{BorshDeserialize, BorshSerialize}; use eyre::Result; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 547d60edf1..2936cf4db9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -26,10 +26,9 @@ use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, }; use namada::ledger::pos::namada_proof_of_stake::PosBase; +use namada::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{ - DBIter, Sha256Hasher, Storage, StorageHasher, DB, -}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; @@ -760,7 +759,8 @@ mod test_utils { #[cfg(not(feature = "abcipp"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::{BlockStateWrite, MerkleTree}; use namada::types::address::{xan, EstablishedAddressGen}; use namada::types::chain::ChainId; use namada::types::hash::Hash; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index de33ebc38b..521c704dca 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d4ade3b380..5d04cef45c 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -152,7 +152,7 @@ where for PrefixValue { key, value } in &values { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(p) => { @@ -221,7 +221,7 @@ where let proof_ops = if is_proven { match self.storage.get_existence_proof( key, - value.clone(), + value.clone().into(), height, ) { Ok(proof) => Some({ diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 59717f4efd..f1caae112a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -3,7 +3,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 474be66787..542bf6b81f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,7 +4,8 @@ use std::collections::HashMap; use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 2876236bca..373da98d5a 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -9,7 +9,8 @@ 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 namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::Storage; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 6af0011dcc..069b6ed0a7 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -930,7 +930,8 @@ mod imp { #[cfg(test)] mod test { - use namada::ledger::storage::{MerkleTree, Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::MerkleTree; use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b05b4db7be..b7b8cf1cf2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -18,7 +18,8 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -191,8 +192,9 @@ mod test_bridge_pool_vp { use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; + use crate::ledger::storage::traits::Sha256Hasher; use crate::ledger::storage::write_log::WriteLog; - use crate::ledger::storage::{Sha256Hasher, Storage}; + use crate::ledger::storage::Storage; use crate::proto::Tx; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index cdcd1815ea..0e62f7270f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::StorageHasher; +use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::Amount; diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 72aa7fb3ce..b565970c76 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -16,7 +16,8 @@ pub use vp::Result; use self::storage as gov_storage; use crate::ledger::native_vp::{Ctx, NativeVp}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as m1t, Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token as token_storage; diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 79c3b4d5b6..f860242a74 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -47,7 +47,7 @@ impl GovParams { pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + H: storage::traits::StorageHasher, { let Self { min_proposal_fund, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aaca277f91..e6377e4fa6 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -9,7 +9,8 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; -use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult}; use crate::types::storage::{Epoch, Key}; diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..fbd129b43c 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -6,7 +6,8 @@ use thiserror::Error; 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::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as m1t, Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index cb4e3212ea..93768ec92a 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -36,7 +36,6 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics03_connection::version::Version as ConnVersion; use crate::ibc::core::ics04_channel::channel::{ ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, }; @@ -1030,9 +1029,7 @@ pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { ConnState::Init, msg.client_id.clone(), msg.counterparty.clone(), - msg.version - .clone() - .map_or_else(|| vec![ConnVersion::default()], |v| vec![v]), + vec![msg.version.clone().unwrap_or_default()], msg.delay_period, ) } @@ -1112,17 +1109,17 @@ pub fn packet_from_message( /// Returns a commitment from the given packet pub fn commitment(packet: &Packet) -> PacketCommitment { - let mut input = packet + let input = packet .timeout_timestamp .nanoseconds() .to_be_bytes() .to_vec(); let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - input.append(&mut revision_number.to_vec()); + let input = [input.as_slice(), revision_number.as_slice()].concat(); let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - input.append(&mut revision_height.to_vec()); + let input = [input.as_slice(), revision_height.as_slice()].concat(); let data = sha2::Sha256::digest(&packet.data); - input.append(&mut data.to_vec()); + let input = [input.as_slice(), data.as_slice()].concat(); sha2::Sha256::digest(&input).to_vec().into() } diff --git a/shared/src/ledger/ibc/mod.rs b/shared/src/ledger/ibc/mod.rs index 5fb599d979..f2a422236a 100644 --- a/shared/src/ledger/ibc/mod.rs +++ b/shared/src/ledger/ibc/mod.rs @@ -9,7 +9,8 @@ use storage::{ connection_counter_key, }; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage, Storage}; /// Initialize storage in the genesis block. pub fn init_genesis_storage(storage: &mut Storage) diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 3a43834da5..a0dafebc00 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -53,7 +53,8 @@ use crate::ibc::proofs::Proofs; use crate::ibc::timestamp::Timestamp; use crate::ledger::native_vp::Error as NativeVpError; use crate::ledger::parameters; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::tendermint::Time; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{ diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 4b89e1ce30..8b1fa8b73d 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -31,7 +31,8 @@ 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::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::{BlockHeight, Key}; diff --git a/shared/src/ledger/ibc/vp/connection.rs b/shared/src/ledger/ibc/vp/connection.rs index 2e721bed08..4037d1b02a 100644 --- a/shared/src/ledger/ibc/vp/connection.rs +++ b/shared/src/ledger/ibc/vp/connection.rs @@ -27,7 +27,8 @@ 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::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::{BlockHeight, Epoch, Key}; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 7e2baf4666..6797a61054 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -18,7 +18,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::proto::SignedTxData; use crate::types::address::{Address, InternalAddress}; use crate::types::ibc::IbcEvent as WrappedIbcEvent; diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 207727e91c..1182d4b821 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -32,7 +32,8 @@ use crate::ibc::core::ics24_host::identifier::{ }; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; -use crate::ledger::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs index da5ef4e25f..5efade84bd 100644 --- a/shared/src/ledger/ibc/vp/port.rs +++ b/shared/src/ledger/ibc/vp/port.rs @@ -16,7 +16,8 @@ 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::ibc::core::ics26_routing::context::ModuleId; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/sequence.rs b/shared/src/ledger/ibc/vp/sequence.rs index 0e751ea0de..a47bb4c4ca 100644 --- a/shared/src/ledger/ibc/vp/sequence.rs +++ b/shared/src/ledger/ibc/vp/sequence.rs @@ -8,7 +8,8 @@ use crate::ibc::core::ics04_channel::channel::Order; use crate::ibc::core::ics04_channel::context::ChannelReader; use crate::ibc::core::ics24_host::identifier::PortChannelId; use crate::ledger::ibc::handler::packet_from_message; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index f56382b360..806e26711f 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -11,7 +11,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::proto::SignedTxData; use crate::types::address::{Address, Error as AddressError, InternalAddress}; use crate::types::ibc::data::{ diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 1819cde903..594db7f8d1 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -8,8 +8,9 @@ use eyre::Context; use thiserror::Error; use crate::ledger::gas::VpGasMeter; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{Storage, StorageHasher}; +use crate::ledger::storage::Storage; use crate::ledger::{storage, vp_env}; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index fdc2a110d0..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -11,7 +11,8 @@ use super::governance::vp::is_proposal_accepted; use super::storage::types::{decode, encode}; use super::storage::{types, Storage}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::time::DurationSecs; @@ -150,7 +151,7 @@ impl Parameters { pub fn init_storage(&self, storage: &mut Storage) where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // write epoch parameters let epoch_key = storage::get_epoch_storage_key(); @@ -198,7 +199,7 @@ pub fn update_max_expected_time_per_block_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_max_expected_time_per_block_key(); update(storage, value, key) @@ -212,7 +213,7 @@ pub fn update_vp_whitelist_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_vp_whitelist_storage_key(); update(storage, &value, key) @@ -226,7 +227,7 @@ pub fn update_tx_whitelist_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_tx_whitelist_storage_key(); update(storage, &value, key) @@ -240,7 +241,7 @@ pub fn update_epoch_parameter( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { let key = storage::get_epoch_storage_key(); update(storage, value, key) @@ -255,7 +256,7 @@ pub fn update( ) -> std::result::Result where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, T: BorshSerialize, { let serialized_value = value @@ -273,7 +274,7 @@ pub fn read_epoch_parameter( ) -> std::result::Result<(EpochDuration, u64), ReadError> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // read epoch let epoch_key = storage::get_epoch_storage_key(); @@ -293,7 +294,7 @@ pub fn read( ) -> std::result::Result<(Parameters, u64), ReadError> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: ledger_storage::StorageHasher, + H: StorageHasher, { // read epoch let (epoch_duration, gas_epoch) = read_epoch_parameter(storage) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index c980da81da..95a4de4ffa 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -13,7 +13,8 @@ use namada_proof_of_stake::PosBase; pub use storage::*; pub use vp::PosVP; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage, Storage}; use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::Epoch; use crate::types::{key, token}; diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index cfe1126b88..9ed2fcbecc 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -10,8 +10,9 @@ use super::{ BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, ADDRESS, }; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::types::{decode, encode}; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::types::address::Address; use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::{key, token}; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..e5cbf9198a 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -32,8 +32,9 @@ use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, }; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::types::decode; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::{key, token}; diff --git a/shared/src/ledger/storage/ics23_specs.rs b/shared/src/ledger/storage/ics23_specs.rs index e1cede8ec2..00691bd30e 100644 --- a/shared/src/ledger/storage/ics23_specs.rs +++ b/shared/src/ledger/storage/ics23_specs.rs @@ -1,9 +1,7 @@ //! A module that contains use arse_merkle_tree::H256; -use ics23::{ - CommitmentProof, ExistenceProof, HashOp, LeafOp, LengthOp, ProofSpec, -}; +use ics23::{HashOp, LeafOp, LengthOp, ProofSpec}; use super::traits::StorageHasher; @@ -47,6 +45,7 @@ pub fn ibc_leaf_spec() -> LeafOp { } /// Get the proof specs for ibc +#[allow(dead_code)] pub fn ibc_proof_specs() -> Vec { let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { @@ -54,21 +53,22 @@ pub fn ibc_proof_specs() -> Vec { ..spec.clone() }; let base_tree_spec = ProofSpec { - leaf_spec: Some(base_leaf_spec()), + leaf_spec: Some(base_leaf_spec::()), ..spec }; vec![sub_tree_spec, base_tree_spec] } /// Get the proof specs +#[allow(dead_code)] pub fn proof_specs() -> Vec { let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { - leaf_spec: Some(leaf_spec::()), + leaf_spec: Some(leaf_spec::()), ..spec.clone() }; let base_tree_spec = ProofSpec { - leaf_spec: Some(base_leaf_spec::()), + leaf_spec: Some(base_leaf_spec::()), ..spec }; vec![sub_tree_spec, base_tree_spec] diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index d5a6d11ab5..4441e0451a 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -1,34 +1,29 @@ //! The merkle tree in the storage -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, Value}; use arse_merkle_tree::{ Hash as SmtHash, Key as TreeKey, 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 ics23::{CommitmentProof, ExistenceProof, NonExistenceProof}; use prost::Message; -use sha2::{Digest, Sha256}; use thiserror::Error; +use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; +use crate::ledger::storage::ics23_specs::{self, ibc_leaf_spec}; use crate::ledger::storage::types; use crate::tendermint::merkle::proof::{Proof, ProofOp}; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, TreeBytes, + DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, + StringKey, TreeBytes, }; #[allow(missing_docs)] @@ -41,11 +36,17 @@ pub enum Error { #[error("Empty Key: {0}")] EmptyKey(String), #[error("Merkle Tree error: {0}")] - MerkleTree(MtError), + MerkleTree(String), #[error("Invalid store type: {0}")] StoreType(String), #[error("Non-existence proofs not supported for store type: {0}")] NonExistenceProof(String), + #[error("Invalid value given to sub-tree storage")] + InvalidValue, + #[error("ICS23 commitment proofs do not support multiple leaves")] + Ics23MultiLeaf, + #[error("A Tendermint proof can only be constructed from an ICS23 proof.")] + TendermintProof, } /// Result for functions that may fail @@ -253,42 +254,38 @@ impl MerkleTree { } } - fn tree(&self, store_type: &StoreType) -> Either<&Smt, &Amt> { + fn tree(&self, store_type: &StoreType) -> Box { + match store_type { + StoreType::Base => Box::new(&self.base), + StoreType::Account => Box::new(&self.account), + StoreType::Ibc => Box::new(&self.ibc), + StoreType::PoS => Box::new(&self.pos), + } + } + + fn tree_mut( + &mut self, + store_type: &StoreType, + ) -> Box { match store_type { - StoreType::Base => Either::Left(&self.base), - StoreType::Account => Either::Left(&self.account), - StoreType::Ibc => Either::Right(&self.ibc), - StoreType::PoS => Either::Left(&self.pos), + StoreType::Base => Box::new(&mut self.base), + StoreType::Account => Box::new(&mut self.account), + StoreType::Ibc => Box::new(&mut self.ibc), + StoreType::PoS => Box::new(&mut self.pos), } } fn update_tree( &mut self, store_type: &StoreType, - key: MerkleKey, - value: Either, + key: &Key, + value: MerkleValue, ) -> Result<()> { - let sub_root = match store_type { - StoreType::Account => self - .account - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - StoreType::Ibc => self - .ibc - .update(key.try_into()?, value.unwrap_right()) - .map_err(Error::MerkleTree)?, - StoreType::PoS => self - .pos - .update(key.try_into()?, value.unwrap_left()) - .map_err(Error::MerkleTree)?, - // base tree should not be directly updated - StoreType::Base => unreachable!(), - }; - + let sub_root = self.tree_mut(store_type).subtree_update(key, value)?; // 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.into(), Hash::from(sub_root))?; + self.base.update(base_key.into(), sub_root)?; } Ok(()) } @@ -296,41 +293,28 @@ 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 value = match self.tree(&store_type) { - Either::Left(smt) => { - 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)?.is_zero() - } - }; - Ok(!value) + self.tree(&store_type).subtree_has_key(&sub_key) } /// 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; - 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) + pub fn update( + &mut self, + key: &Key, + value: impl Into, + ) -> Result<()> { + let (store_type, sub_key) = StoreType::sub_key(key)?; + self.update_tree(&store_type, &sub_key, value.into()) } /// 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; - 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) + let (store_type, sub_key) = StoreType::sub_key(key)?; + let sub_root = self.tree_mut(&store_type).subtree_delete(&sub_key)?; + if store_type != StoreType::Base { + let base_key = H::hash(&store_type.to_string()); + self.base.update(base_key.into(), sub_root)?; + } + Ok(()) } /// Get the root @@ -348,88 +332,71 @@ impl MerkleTree { } } - /// Get the existence proof - pub fn get_existence_proof( + /// Get the existence proof from a sub-tree + pub fn get_sub_tree_existence_proof( &self, - key: &Key, - value: Vec, - ) -> Result { - let (store_type, sub_key) = StoreType::sub_key(key)?; - 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 = - 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 - match cp.proof.expect("The proof should exist") { - Ics23Proof::Exist(ep) => CommitmentProof { - proof: Some(Ics23Proof::Exist(ExistenceProof { - leaf: Some(self.ibc_leaf_spec()), - ..ep - })), - }, - _ => unreachable!(), - } + keys: &[Key], + values: Vec, + ) -> Result { + let first_key = keys.iter().next().ok_or_else(|| { + Error::InvalidMerkleKey( + "No keys provided for existence proof.".into(), + ) + })?; + let (store_type, sub_key) = StoreType::sub_key(first_key)?; + if !keys.iter().all(|k| { + if let Ok((s, _)) = StoreType::sub_key(k) { + s == store_type + } else { + false } - }; - self.get_proof(key, sub_proof) + }) { + return Err(Error::InvalidMerkleKey( + "Cannot construct inclusion proof for keys in separate \ + sub-trees." + .into(), + )); + } + self.tree(&store_type) + .subtree_membership_proof(std::array::from_ref(&sub_key), values) } /// 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 sub_proof = match self.tree(&store_type) { - Either::Left(_) => { - return Err(Error::NonExistenceProof(store_type.to_string())); - } - Either::Right(amt) => { - let key = - StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; - let mut nep = amt.non_membership_proof(&key)?; - // 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(self.ibc_leaf_spec()); - } - _ => unreachable!(), + if store_type != StoreType::Ibc { + return Err(Error::NonExistenceProof(store_type.to_string())); + } + + let string_key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = self.ibc.non_membership_proof(&string_key)?; + // 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; + if let Some(left) = left.as_mut() { + left.leaf = Some(ibc_leaf_spec::()); + } + if let Some(right) = right.as_mut() { + right.leaf = Some(ibc_leaf_spec::()); } } - nep + _ => unreachable!(), } - }; + } + // Get a proof of the sub tree - self.get_proof(key, sub_proof) + self.get_tendermint_proof(key, nep) } /// Get the Tendermint proof with the base proof - fn get_proof( + pub fn get_tendermint_proof( &self, key: &Key, sub_proof: CommitmentProof, @@ -454,7 +421,7 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: base_key.as_bytes().to_vec(), - leaf: Some(self.base_leaf_spec()), + leaf: Some(ics23_specs::base_leaf_spec::()), ..ep })), }, @@ -477,73 +444,6 @@ impl MerkleTree { ops: vec![sub_proof_op, base_proof_op], }) } - - /// Get the proof specs - pub fn 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.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 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: H::hash_op().into(), - prehash_value: HashOp::NoHash.into(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } - - /// Get the leaf spec for the subtree. Non-hashed values are used for the - /// 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(), - length: LengthOp::NoPrefix.into(), - prefix: H256::zero().as_slice().to_vec(), - } - } } /// The root hash of the merkle tree as bytes @@ -568,45 +468,6 @@ impl fmt::Display for MerkleRoot { } } -impl From<(StoreType, Key)> for MerkleKey { - fn from((store, key): (StoreType, Key)) -> Self { - match store { - StoreType::Base | StoreType::Account | StoreType::PoS => { - MerkleKey::Sha256(key, PhantomData) - } - StoreType::Ibc => MerkleKey::Raw(key), - } - } -} - -impl TryFrom> for SmtHash { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), - _ => Err(Error::InvalidMerkleKey( - "This key is for a sparse merkle tree".into(), - )), - } - } -} - -impl TryFrom> for StringKey { - type Error = Error; - - fn try_from(value: MerkleKey) -> Result { - match value { - MerkleKey::Raw(key) => { - Self::try_from_bytes(key.to_string().as_bytes()) - } - _ => Err(Error::InvalidMerkleKey( - "This is not an key for the IBC subtree".into(), - )), - } - } -} - /// The root and store pairs to restore the trees #[derive(Default)] pub struct MerkleTreeStoresRead { @@ -668,93 +529,6 @@ impl<'a> MerkleTreeStoresWrite<'a> { } } -impl TreeKey for StringKey { - type Error = Error; - - fn as_slice(&self) -> &[u8] { - &self.original.as_slice()[..self.length] - } - - fn try_from_bytes(bytes: &[u8]) -> Result { - 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 { - return Err(Error::InvalidMerkleKey( - "Input IBC key is too large".into(), - )); - } - original[i] = *byte; - tree_key[i] = byte.wrapping_add(1); - length += 1; - } - Ok(Self { - original, - tree_key: tree_key.into(), - length, - }) - } -} - -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 - fn hash(value: impl AsRef<[u8]>) -> H256; -} - -/// The storage hasher used for the merkle tree. -#[derive(Default)] -pub struct Sha256Hasher(Sha256); - -impl Hasher for Sha256Hasher { - fn write_bytes(&mut self, h: &[u8]) { - self.0.update(h) - } - - 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 { - ics23::HashOp::Sha256 - } -} - -impl StorageHasher for Sha256Hasher { - fn hash(value: impl AsRef<[u8]>) -> H256 { - let mut hasher = Sha256::new(); - hasher.update(value.as_ref()); - let hash = hasher.finalize(); - let bytes: [u8; 32] = hash - .as_slice() - .try_into() - .expect("Sha256 output conversion to fixed array shouldn't fail"); - bytes.into() - } -} - -impl fmt::Debug for Sha256Hasher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Sha256Hasher") - } -} - impl From for Error { fn from(error: StorageError) -> Self { Error::InvalidKey(error) @@ -763,13 +537,15 @@ impl From for Error { impl From for Error { fn from(error: MtError) -> Self { - Error::MerkleTree(error) + Error::MerkleTree(error.to_string()) } } #[cfg(test)] mod test { use super::*; + use crate::ledger::storage::ics23_specs::{ibc_proof_specs, proof_specs}; + use crate::ledger::storage::traits::Sha256Hasher; use crate::types::storage::KeySeg; #[test] @@ -778,6 +554,7 @@ mod test { let key_prefix: Key = Address::Internal(InternalAddress::Ibc).to_db_key().into(); let ibc_key = key_prefix.push(&"test".to_string()).unwrap(); + let ibc_non_key = key_prefix.push(&"test2".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(); @@ -793,10 +570,65 @@ mod test { tree.update(&pos_key, [2u8; 8]).unwrap(); assert!(tree.has_key(&pos_key).unwrap()); + // update IBC tree + tree.update(&ibc_non_key, [2u8; 8]).unwrap(); + assert!(tree.has_key(&ibc_non_key).unwrap()); + assert!(tree.has_key(&ibc_key).unwrap()); + assert!(tree.has_key(&pos_key).unwrap()); // delete a value on IBC tree - tree.delete(&ibc_key).unwrap(); - assert!(!tree.has_key(&ibc_key).unwrap()); + tree.delete(&ibc_non_key).unwrap(); + assert!(!tree.has_key(&ibc_non_key).unwrap()); + assert!(tree.has_key(&ibc_key).unwrap()); assert!(tree.has_key(&pos_key).unwrap()); + + // get and verify non-existence proof for the deleted key + 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 = ibc_proof_specs::(); + + let nep_verification_res = ics23::verify_non_membership( + &nep_commitment_proof, + &specs[0], + &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); } #[test] @@ -840,9 +672,14 @@ mod test { let pos_val = [2u8; 8].to_vec(); tree.update(&pos_key, pos_val).unwrap(); - let specs = tree.ibc_proof_specs(); - let proof = - tree.get_existence_proof(&ibc_key, ibc_val.clone()).unwrap(); + let specs = ibc_proof_specs::(); + let MembershipProof::ICS23(proof) = tree + .get_sub_tree_existence_proof( + std::array::from_ref(&ibc_key), + vec![ibc_val.clone().into()], + ) + .unwrap(); + let proof = tree.get_tendermint_proof(&ibc_key, proof).unwrap(); let (store_type, sub_key) = StoreType::sub_key(&ibc_key).unwrap(); let paths = vec![sub_key.to_string(), store_type.to_string()]; let mut sub_root = ibc_val.clone(); @@ -890,9 +727,14 @@ mod test { 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 specs = proof_specs::(); + let MembershipProof::ICS23(proof) = tree + .get_sub_tree_existence_proof( + std::array::from_ref(&pos_key), + vec![pos_val.clone().into()], + ) + .unwrap(); + let proof = tree.get_tendermint_proof(&pos_key, proof).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(); @@ -959,7 +801,7 @@ mod test { }; let (store_type, sub_key) = StoreType::sub_key(&ibc_non_key).expect("Test failed"); - let specs = tree.ibc_proof_specs(); + let specs = ibc_proof_specs::(); let nep_verification_res = ics23::verify_non_membership( &nep_commitment_proof, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1df873edd6..585727342d 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1,13 +1,17 @@ //! Ledger's state storage with key-value backed store and a merkle tree +mod ics23_specs; mod merkle_tree; #[cfg(any(test, feature = "testing"))] pub mod mockdb; +pub mod traits; pub mod types; pub mod write_log; use core::fmt::Debug; +use std::array; +use tendermint::merkle::proof::Proof; use thiserror::Error; use super::parameters; @@ -18,17 +22,16 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, Sha256Hasher, - StorageHasher, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, }; -use crate::tendermint::merkle::proof::Proof; +use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ BlockHash, BlockHeight, Epoch, Epochs, Header, Key, KeySeg, - BLOCK_HASH_LENGTH, + MembershipProof, MerkleValue, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; @@ -507,15 +510,32 @@ where pub fn get_existence_proof( &self, key: &Key, - value: Vec, + value: MerkleValue, height: BlockHeight, ) -> Result { if height >= self.get_block_height().0 { - Ok(self.block.tree.get_existence_proof(key, value)?) + let MembershipProof::ICS23(proof) = self + .block + .tree + .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) + .map_err(Error::MerkleTreeError)?; + self.block + .tree + .get_tendermint_proof(key, proof) + .map_err(Error::MerkleTreeError) } else { match self.db.read_merkle_tree_stores(height)? { - Some(stores) => Ok(MerkleTree::::new(stores) - .get_existence_proof(key, value)?), + Some(stores) => { + let tree = MerkleTree::::new(stores); + let MembershipProof::ICS23(proof) = tree + .get_sub_tree_existence_proof( + array::from_ref(key), + vec![value], + ) + .map_err(Error::MerkleTreeError)?; + tree.get_tendermint_proof(key, proof) + .map_err(Error::MerkleTreeError) + } None => Err(Error::NoMerkleTree { height }), } } @@ -694,11 +714,9 @@ impl From for Error { /// Helpers for testing components that depend on storage #[cfg(any(test, feature = "testing"))] pub mod testing { - use merkle_tree::Sha256Hasher; - use super::mockdb::MockDB; use super::*; - + use crate::ledger::storage::traits::Sha256Hasher; /// Storage with a mock DB for testing pub type TestStorage = Storage; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs new file mode 100644 index 0000000000..e382f34d73 --- /dev/null +++ b/shared/src/ledger/storage/traits.rs @@ -0,0 +1,276 @@ +//! Traits needed to provide a uniform interface over +//! all the different Merkle trees used for storage +use std::convert::TryInto; +use std::fmt; + +use arse_merkle_tree::traits::{Hasher, Value}; +use arse_merkle_tree::{Key as TreeKey, H256}; +use ics23::commitment_proof::Proof as Ics23Proof; +use ics23::{CommitmentProof, ExistenceProof}; +use sha2::{Digest, Sha256}; + +use super::merkle_tree::{Amt, Error, Smt}; +use super::{ics23_specs, IBC_KEY_LIMIT}; +use crate::types::hash::Hash; +use crate::types::storage::{ + Key, MembershipProof, MerkleValue, StringKey, TreeBytes, +}; + +/// Trait for reading from a merkle tree that is a sub-tree +/// of the global merkle tree. +pub trait SubTreeRead { + /// Check if a key is present in the sub-tree + fn subtree_has_key(&self, key: &Key) -> Result; + /// Get a membership proof for various key-value pairs + fn subtree_membership_proof( + &self, + keys: &[Key], + values: Vec, + ) -> Result; +} + +/// Trait for updating a merkle tree that is a sub-tree +/// of the global merkle tree +pub trait SubTreeWrite { + /// Add a key-value pair to the sub-tree + fn subtree_update( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result; + /// Delete a key from the sub-tree + fn subtree_delete(&mut self, key: &Key) -> Result; +} + +impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { + fn subtree_has_key(&self, key: &Key) -> Result { + match self.get(&H::hash(key.to_string()).into()) { + Ok(hash) => Ok(!hash.is_zero()), + Err(e) => Err(Error::MerkleTree(e.to_string())), + } + } + + fn subtree_membership_proof( + &self, + keys: &[Key], + mut values: Vec, + ) -> Result { + if keys.len() != 1 || values.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + let key: &Key = &keys[0]; + let MerkleValue::Bytes(value) = values.remove(0); + let cp = self.membership_proof(&H::hash(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) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + key: key.to_string().as_bytes().to_vec(), + value, + leaf: Some(ics23_specs::leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } + } +} + +impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { + fn subtree_update( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + let value = match value { + MerkleValue::Bytes(bytes) => H::hash(bytes.as_slice()), + }; + self.update(H::hash(key.to_string()).into(), value.into()) + .map(Hash::from) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn subtree_delete(&mut self, key: &Key) -> Result { + let value = Hash::zero(); + self.update(H::hash(key.to_string()).into(), value) + .map(Hash::from) + .map_err(|err| Error::MerkleTree(err.to_string())) + } +} + +impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { + fn subtree_has_key(&self, key: &Key) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + match self.get(&key) { + Ok(hash) => Ok(!hash.is_zero()), + Err(e) => Err(Error::MerkleTree(e.to_string())), + } + } + + fn subtree_membership_proof( + &self, + keys: &[Key], + _: Vec, + ) -> Result { + if keys.len() != 1 { + return Err(Error::Ics23MultiLeaf); + } + + let key = StringKey::try_from_bytes(keys[0].to_string().as_bytes())?; + let cp = self.membership_proof(&key)?; + // Replace the values and the leaf op for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => Ok(CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + leaf: Some(ics23_specs::ibc_leaf_spec::()), + ..ep + })), + } + .into()), + // the proof should have an ExistenceProof + _ => unreachable!(), + } + } +} + +impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { + fn subtree_update( + &mut self, + key: &Key, + value: MerkleValue, + ) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = match value { + MerkleValue::Bytes(bytes) => TreeBytes::from(bytes), + }; + self.update(key, value) + .map(Into::into) + .map_err(|err| Error::MerkleTree(err.to_string())) + } + + fn subtree_delete(&mut self, key: &Key) -> Result { + let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; + let value = TreeBytes::zero(); + self.update(key, value) + .map(Hash::from) + .map_err(|err| Error::MerkleTree(format!("{:?}", err))) + } +} + +impl TreeKey for StringKey { + type Error = Error; + + fn as_slice(&self) -> &[u8] { + &self.original.as_slice()[..self.length] + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + 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 { + return Err(Error::InvalidMerkleKey( + "Input IBC key is too large".into(), + )); + } + original[i] = *byte; + tree_key[i] = byte.wrapping_add(1); + length += 1; + } + Ok(Self { + original, + tree_key: tree_key.into(), + length, + }) + } +} + +impl Value for Hash { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + Hash([0u8; 32]) + } +} + +impl From for H256 { + fn from(hash: Hash) -> Self { + hash.0.into() + } +} + +impl From for Hash { + fn from(hash: H256) -> Self { + Self(hash.into()) + } +} + +impl From<&H256> for Hash { + fn from(hash: &H256) -> Self { + let hash = hash.to_owned(); + Self(hash.into()) + } +} + +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 + fn hash(value: impl AsRef<[u8]>) -> H256; +} + +/// The storage hasher used for the merkle tree. +#[derive(Default)] +pub struct Sha256Hasher(Sha256); + +impl Hasher for Sha256Hasher { + fn write_bytes(&mut self, h: &[u8]) { + self.0.update(h) + } + + fn finish(self) -> 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 { + ics23::HashOp::Sha256 + } +} + +impl StorageHasher for Sha256Hasher { + fn hash(value: impl AsRef<[u8]>) -> H256 { + let mut hasher = Sha256::new(); + hasher.update(value.as_ref()); + let hash = hasher.finalize(); + let bytes: [u8; 32] = hash + .as_slice() + .try_into() + .expect("Sha256 output conversion to fixed array shouldn't fail"); + bytes.into() + } +} + +impl fmt::Debug for Sha256Hasher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Sha256Hasher") + } +} diff --git a/shared/src/ledger/storage/write_log.rs b/shared/src/ledger/storage/write_log.rs index 098204b707..45940493a9 100644 --- a/shared/src/ledger/storage/write_log.rs +++ b/shared/src/ledger/storage/write_log.rs @@ -6,7 +6,8 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use thiserror::Error; use crate::ledger; -use crate::ledger::storage::{Storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::Storage; use crate::types::address::{Address, EstablishedAddressGen}; use crate::types::ibc::IbcEvent; use crate::types::storage; diff --git a/shared/src/ledger/treasury/mod.rs b/shared/src/ledger/treasury/mod.rs index 071019059b..35b4ce5022 100644 --- a/shared/src/ledger/treasury/mod.rs +++ b/shared/src/ledger/treasury/mod.rs @@ -12,7 +12,8 @@ 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::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as nam, Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; diff --git a/shared/src/ledger/treasury/parameters.rs b/shared/src/ledger/treasury/parameters.rs index 2ccb142d09..11d3b6db5a 100644 --- a/shared/src/ledger/treasury/parameters.rs +++ b/shared/src/ledger/treasury/parameters.rs @@ -35,7 +35,7 @@ impl TreasuryParams { pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, + H: storage::traits::StorageHasher, { let max_proposal_fund_transfer_key = treasury_storage::get_max_transferable_fund_key(); diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 1f59613e54..aafd4b135a 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -8,8 +8,9 @@ use thiserror::Error; use super::gas::MIN_STORAGE_GAS; use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; +use crate::ledger::storage::{self, write_log, Storage}; use crate::proto::Tx; use crate::types::hash::Hash; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e8ef5577cb..718cecee83 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -3,8 +3,6 @@ use std::fmt::{self, Display}; use std::ops::Deref; -use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -98,11 +96,6 @@ 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 { @@ -110,38 +103,3 @@ 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.0.into() - } -} - -impl From for TreeHash { - fn from(hash: Hash) -> Self { - Self::from(hash.0) - } -} - -impl Value for Hash { - 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 b7e2005bb7..d28260b541 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -2,20 +2,20 @@ 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, Div, Mul, Rem, Sub}; use std::str::FromStr; use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ics23::CommitmentProof; use serde::{Deserialize, Serialize}; use thiserror::Error; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; -use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; +use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -31,6 +31,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Could not parse string: '{0}' into requested type: {1}")] + ParseError(String, String), } /// Result for functions that may fail @@ -233,14 +235,25 @@ 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), +/// An enum representing the different types of values +/// that can be passed into Anoma's storage. +/// +/// This is a multi-store organized as +/// several Merkle trees, each of which is +/// responsible for understanding how to parse +/// this value. +pub enum MerkleValue { + /// raw bytes + Bytes(Vec), +} + +impl From for MerkleValue +where + T: AsRef<[u8]>, +{ + fn from(bytes: T) -> Self { + Self::Bytes(bytes.as_ref().to_owned()) + } } /// Storage keys that are utf8 encoded strings @@ -322,6 +335,18 @@ impl From for Vec { } } +/// Type of membership proof from a merkle tree +pub enum MembershipProof { + /// ICS23 compliant membership proof + ICS23(CommitmentProof), +} + +impl From for MembershipProof { + fn from(proof: CommitmentProof) -> Self { + Self::ICS23(proof) + } +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ac9f89c31e..88bd81b918 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -13,8 +13,9 @@ use super::wasm::TxCache; use super::wasm::VpCache; use super::WasmCacheAccess; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::{self, WriteLog}; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::ledger::vp_env; use crate::proto::Tx; use crate::types::address::{self, Address}; @@ -1759,7 +1760,8 @@ pub mod testing { use std::collections::BTreeSet; use super::*; - use crate::ledger::storage::{self, StorageHasher}; + use crate::ledger::storage::traits::StorageHasher; + use crate::ledger::storage::{self}; use crate::vm::memory::testing::NativeMemory; /// Setup a transaction environment diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 3b6715f383..3736c8a295 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -8,7 +8,8 @@ use wasmer::{ WasmerEnv, }; -use crate::ledger::storage::{self, StorageHasher}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{self}; use crate::vm::host_env::{TxEnv, VpEnv, VpEvaluator}; use crate::vm::wasm::memory::WasmMemory; use crate::vm::{host_env, WasmCacheAccess}; diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 75fbfb4add..dbfdb4c961 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -11,8 +11,9 @@ use wasmer::BaseTunables; use super::memory::{Limit, WasmMemory}; use super::TxCache; use crate::ledger::gas::{BlockGasMeter, VpGasMeter}; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::write_log::WriteLog; -use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage::{self, Storage}; use crate::proto::Tx; use crate::types::address::Address; use crate::types::internal::HostEnvResult; diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index be450a7086..5540ac26d4 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -2,7 +2,7 @@ mod pos; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; -use namada::ledger::storage::Sha256Hasher; +use namada::ledger::storage::traits::Sha256Hasher; use namada::vm::wasm::compilation_cache; use namada::vm::wasm::compilation_cache::common::Cache; use namada::vm::{wasm, WasmCacheRwAccess}; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index e1c372a85f..8e6fa0a254 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -59,7 +59,7 @@ 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::storage::traits::Sha256Hasher; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 61b87e1b3b..06cd6d0981 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -88,7 +88,7 @@ mod native_vp_host_env { // TODO replace with `std::concat_idents` once stabilized (https://github.com/rust-lang/rust/issues/29599) use concat_idents::concat_idents; - use namada::ledger::storage::Sha256Hasher; + use namada::ledger::storage::traits::Sha256Hasher; use namada::vm::host_env::*; use namada::vm::WasmCacheRwAccess; From acbb77e42c2a2682b59aa2babb759f81d93b7ab7 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 0852/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- apps/src/lib/node/ledger/protocol/mod.rs | 3 +- .../transactions/ethereum_events/events.rs | 3 +- .../transactions/ethereum_events/mod.rs | 5 +- .../transactions/ethereum_events/read.rs | 3 +- .../transactions/ethereum_events/update.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/prepare_proposal.rs | 3 +- .../shell/vote_extensions/eth_events.rs | 3 +- .../shell/vote_extensions/val_set_update.rs | 3 +- apps/src/lib/node/ledger/storage/mod.rs | 3 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 3 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 ++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 223 ++++++++++-------- shared/src/ledger/storage/merkle_tree.rs | 18 +- shared/src/ledger/storage/mod.rs | 10 +- shared/src/types/eth_bridge_pool.rs | 6 +- tests/src/native_vp/pos.rs | 3 +- 17 files changed, 302 insertions(+), 208 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 35ca06fc60..9ffc1cd367 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -12,8 +12,9 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{DB, DBIter, Storage, traits::StorageHasher}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index 2564830a2c..b639a01fd2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -4,7 +4,8 @@ use std::collections::BTreeSet; use eyre::Result; use namada::ledger::eth_bridge::storage::wrapped_erc20s; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::storage::Key; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index a9b24deb39..8d9ea01eff 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -14,7 +14,8 @@ use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::eth_msgs::Keys; use namada::ledger::pos::types::WeightedValidator; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::{self, BlockHeight}; @@ -306,7 +307,7 @@ mod tests { use namada::ledger::pos::types::ValidatorSet; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; - use namada::ledger::storage::Sha256Hasher; + use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs index 5f377f8133..90cc960509 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -1,7 +1,8 @@ //! Helpers for reading from storage use borsh::BorshDeserialize; use eyre::{eyre, Result}; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index d6aebac3b6..092dada10a 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -1,7 +1,8 @@ //! Helpers for writing to storage use borsh::{BorshDeserialize, BorshSerialize}; use eyre::Result; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1f179c0db9..2936cf4db9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -26,10 +26,9 @@ use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, }; use namada::ledger::pos::namada_proof_of_stake::PosBase; +use namada::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{ - DBIter, traits::Sha256Hasher, Storage, traits::StorageHasher, DB, -}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; @@ -760,7 +759,8 @@ mod test_utils { #[cfg(not(feature = "abcipp"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::{BlockStateWrite, MerkleTree, traits::Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::{BlockStateWrite, MerkleTree}; use namada::types::address::{xan, EstablishedAddressGen}; use namada::types::chain::ChainId; use namada::types::hash::Hash; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6880bd0598..521c704dca 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 2ced18ae08..f1caae112a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -3,7 +3,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 91d581337f..542bf6b81f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,7 +4,8 @@ use std::collections::HashMap; use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 8017c6b7e4..373da98d5a 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -9,7 +9,8 @@ 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, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::Storage; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 128900240c..069b6ed0a7 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -930,7 +930,8 @@ mod imp { #[cfg(test)] mod test { - use namada::ledger::storage::{MerkleTree, traits::Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::MerkleTree; use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 5d6f4d7177..d347755e76 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -268,19 +267,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -336,10 +337,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -356,10 +354,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -368,13 +368,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -385,13 +387,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -402,13 +406,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -419,13 +425,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -436,58 +444,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -498,13 +531,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -535,11 +573,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 47e4f62a66..c0ccf75f68 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; @@ -150,7 +148,10 @@ impl BridgePoolTree { for (key, value) in keys.iter().zip(values.iter()) { let hash = Self::parse_key(key)?; if hash != value.keccak256() { - return Err(eyre!("Hashes of keys did not match hashes of values.").into()); + return Err(eyre!( + "Hashes of keys did not match hashes of values." + ) + .into()); } leaves.insert(hash); } @@ -351,8 +352,8 @@ mod test_bridge_pool_tree { use proptest::prelude::*; use super::*; - use crate::types::ethereum_events::EthAddress; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use crate::types::ethereum_events::EthAddress; /// An established user address for testing & development fn bertha_address() -> Address { @@ -368,7 +369,7 @@ mod test_bridge_pool_tree { assert_eq!(tree.root().0, [0; 32]); let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([1;20]), + asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -376,10 +377,11 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); - let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + let root = + KeccakHash::from(tree.update_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); } @@ -390,21 +392,23 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } - let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()).into(); + let expected: Hash = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) + .into(); assert_eq!(tree.root(), expected); } @@ -416,26 +420,29 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let hashes: BTreeSet = transfers.iter().map(|t| t.keccak256()).collect(); + let hashes: BTreeSet = + transfers.iter().map(|t| t.keccak256()).collect(); assert_eq!(hashes, tree.store); - let left_hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); - let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); + let left_hash = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); + let right_hash = + hash_pair(transfers[2].keccak256(), Default::default()); let expected: Hash = hash_pair(left_hash, right_hash).into(); assert_eq!(tree.root(), expected); } @@ -447,7 +454,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([1;20]), + asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -455,10 +462,11 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); - let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + let root = + KeccakHash::from(tree.update_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); tree.delete_key(&key).expect("Test failed"); assert_eq!(tree.root().0, [0; 32]); @@ -472,15 +480,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -491,7 +499,9 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[2].keccak256()).into(); + let expected: Hash = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) + .into(); assert_eq!(tree.root(), expected); } @@ -508,11 +518,14 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let expected = transfer.keccak256(); let key = Key::from(&transfer); - assert_eq!(BridgePoolTree::parse_key(&key).expect("Test failed"), expected); + assert_eq!( + BridgePoolTree::parse_key(&key).expect("Test failed"), + expected + ); } /// Test that parsing a key with multiple segments fails @@ -528,24 +541,31 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let hash = transfer.keccak256().to_string(); - let key = Key{segments: vec![DbKeySeg::AddressSeg(bertha_address()), DbKeySeg::StringSeg(hash)]}; + let key = Key { + segments: vec![ + DbKeySeg::AddressSeg(bertha_address()), + DbKeySeg::StringSeg(hash), + ], + }; assert!(BridgePoolTree::parse_key(&key).is_err()); } /// Test that parsing a key that is not a hash fails #[test] fn test_key_not_hash() { - let key = Key{segments: vec![DbKeySeg::StringSeg("bloop".into())]}; + let key = Key { + segments: vec![DbKeySeg::StringSeg("bloop".into())], + }; assert!(BridgePoolTree::parse_key(&key).is_err()); } /// Test that [`contains_key`] works correctly #[test] fn test_contains_key() { - let mut tree = BridgePoolTree::default(); + let mut tree = BridgePoolTree::default(); let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), @@ -556,10 +576,13 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; tree.update_key(&Key::from(&transfer)).expect("Test failed"); - assert!(tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + assert!( + tree.contains_key(&Key::from(&transfer)) + .expect("Test failed") + ); let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), @@ -570,9 +593,13 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; - assert!(!tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + assert!( + !tree + .contains_key(&Key::from(&transfer)) + .expect("Test failed") + ); } /// Test that the empty proof works. @@ -581,7 +608,9 @@ mod test_bridge_pool_tree { let tree = BridgePoolTree::default(); let keys = vec![]; let values = vec![]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(Default::default())); } @@ -593,17 +622,19 @@ mod test_bridge_pool_tree { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), amount: 0.into(), - nonce: 0.into() + nonce: 0.into(), }, gas_fee: GasFee { amount: 0.into(), - payer: bertha_address() - } + payer: bertha_address(), + }, }; let mut tree = BridgePoolTree::default(); let key = Key::from(&transfer); let _ = tree.update_key(&key).expect("Test failed"); - let proof = tree.get_membership_proof(array::from_ref(&key), vec![transfer]).expect("Test failed"); + let proof = tree + .get_membership_proof(array::from_ref(&key), vec![transfer]) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -616,15 +647,15 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -632,11 +663,12 @@ mod test_bridge_pool_tree { let _ = tree.update_key(&key).expect("Test failed"); } let key = Key::from(&transfers[0]); - let proof = tree.get_membership_proof( - array::from_ref(&key), - vec![transfers.remove(0)] - ) - .expect("Test failed"); + let proof = tree + .get_membership_proof( + array::from_ref(&key), + vec![transfers.remove(0)], + ) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -648,15 +680,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -666,7 +698,9 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let keys = vec![Key::from(&transfers[0]), Key::from(&transfers[1])]; let values = vec![transfers[0].clone(), transfers[1].clone()]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -678,15 +712,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -694,7 +728,9 @@ mod test_bridge_pool_tree { } let keys = vec![]; let values = vec![]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())) } @@ -706,15 +742,15 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -722,7 +758,9 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, transfers) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -734,15 +772,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -750,7 +788,9 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, transfers) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -762,64 +802,57 @@ mod test_bridge_pool_tree { for i in 0..5 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys: Vec<_> = transfers - .iter() - .step_by(2) - .map(Key::from) - .collect(); - let values: Vec<_> = transfers - .iter() - .step_by(2) - .cloned() - .collect(); - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let keys: Vec<_> = transfers.iter().step_by(2).map(Key::from).collect(); + let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } /// Create a random set of transfers. - fn random_transfers(number: usize) -> impl Strategy> { + fn random_transfers( + number: usize, + ) -> impl Strategy> { prop::collection::vec( - ( - prop::array::uniform20(0u8..), - prop::num::u64::ANY, - ), + (prop::array::uniform20(0u8..), prop::num::u64::ANY), 0..=number, ) - .prop_flat_map( | addrs | + .prop_flat_map(|addrs| { Just( - addrs.into_iter().map(| (addr, nonce)| - PendingTransfer { + addrs + .into_iter() + .map(|(addr, nonce)| PendingTransfer { transfer: TransferToEthereum { asset: EthAddress(addr), recipient: EthAddress(addr), amount: Default::default(), - nonce: nonce.into() + nonce: nonce.into(), }, gas_fee: GasFee { amount: Default::default(), - payer: bertha_address() - } - }, - ) - .dedup() - .collect::>() + payer: bertha_address(), + }, + }) + .dedup() + .collect::>(), ) - ) + }) } prop_compose! { @@ -836,7 +869,7 @@ mod test_bridge_pool_tree { } } - proptest!{ + proptest! { /// Given a random tree and a subset of leaves, /// verify that the constructed multi-proof correctly /// verifies. @@ -859,4 +892,4 @@ mod test_bridge_pool_tree { assert!(proof.verify(tree.root().into())); } } -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 21f16031ad..1d175e83d2 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -17,7 +17,9 @@ use thiserror::Error; use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; -use crate::ledger::eth_bridge::storage::bridge_pool::{BridgePoolTree, get_signed_root_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_signed_root_key, BridgePoolTree, +}; use crate::ledger::storage::ics23_specs::ibc_leaf_spec; use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; @@ -717,7 +719,8 @@ mod test { std::array::from_ref(&ibc_key), vec![ibc_val.clone().into()], ) - .unwrap(){ + .unwrap() + { MembershipProof::ICS23(proof) => proof, _ => panic!("Test failed"), }; @@ -770,11 +773,12 @@ mod test { tree.update(&pos_key, pos_val.clone()).unwrap(); let specs = proof_specs::(); - let proof = match tree.get_sub_tree_existence_proof( - std::array::from_ref(&pos_key), - vec![pos_val.clone().into()], - ) - .unwrap() + let proof = match tree + .get_sub_tree_existence_proof( + std::array::from_ref(&pos_key), + vec![pos_val.clone().into()], + ) + .unwrap() { MembershipProof::ICS23(proof) => proof, _ => panic!("Test failed"), diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1dd03af3d9..61f9ced6be 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -523,7 +523,8 @@ where .block .tree .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) - .map_err(Error::MerkleTreeError)? { + .map_err(Error::MerkleTreeError)? + { self.block .tree .get_tendermint_proof(key, proof) @@ -540,11 +541,14 @@ where array::from_ref(key), vec![value], ) - .map_err(Error::MerkleTreeError)? { + .map_err(Error::MerkleTreeError)? + { tree.get_tendermint_proof(key, proof) .map_err(Error::MerkleTreeError) } else { - Err(Error::MerkleTreeError(MerkleTreeError::TendermintProof)) + Err(Error::MerkleTreeError( + MerkleTreeError::TendermintProof, + )) } } None => Err(Error::NoMerkleTree { height }), diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index e7c55df8d8..36a378b8db 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -68,7 +68,11 @@ impl keccak::encode::Encode for PendingTransfer { impl From<&PendingTransfer> for Key { fn from(transfer: &PendingTransfer) -> Self { - Key{segments: vec![DbKeySeg::StringSeg(transfer.keccak256().to_string())]} + Key { + segments: vec![DbKeySeg::StringSeg( + transfer.keccak256().to_string(), + )], + } } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 7be49ac538..e7c7daf7c1 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -411,7 +411,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); let vp_env = TestNativeVpEnv::new(tx_env); - let result: Result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + let result: Result = + vp_env.validate_tx(PosVP::new, |_tx_data| {}); // Put the tx_env back before checking the result tx_host_env::set(vp_env.tx_env); let result = From 8302e24f272a01501d3ba8a8c3c5e2e4e853e706 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 0853/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From e3bd15a8c153b7a1a2ecfe7e7462e38c76d9b63a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:32:34 +0000 Subject: [PATCH 0854/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index de0f089b1f..401ee95e6a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", - "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", - "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", - "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", - "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", - "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", - "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", + "tx_from_intent.wasm": "tx_from_intent.6e402bb01b9420855cca0e39e9819afae73c087712836ce105e9f1464d7c8855.wasm", + "tx_ibc.wasm": "tx_ibc.0bd4545d5d30bde90a184a780467e638134121e13a08ba2e103d5b9c7c4174cf.wasm", + "tx_init_account.wasm": "tx_init_account.2cd5b1383ec9d2d0a01de9ad6169955e6b079be6bb8a56cb086233dc7351f0e2.wasm", + "tx_init_nft.wasm": "tx_init_nft.eb3827069201023d9ca452cf72bef59074dd75f1db3799c1dc090dfe99953df6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.05eef918cb9cbb335d6ec11719213beb7f9307a75e40f9b12d5e014ee647b1cd.wasm", + "tx_init_validator.wasm": "tx_init_validator.9d0d926060817b70bf2971141b7b42991a16ba9dab8fee3b00c7590f07a7b0cf.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.daf3a15b0d69e2943b6013626d3041882409fe798b949c24a8e9316a3a229b6d.wasm", + "tx_transfer.wasm": "tx_transfer.3eaf1f0e185e3b86c2c7f3489c1fb7b257394d21d82e4c16e77660f0e20c1174.wasm", + "tx_unbond.wasm": "tx_unbond.629aed5b42b7a24cb8317e2ead1a52968c0a57830c6f136522a1dd769f68f12c.wasm", "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", - "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.53e60d0e7283d07d6b3467f825a7c4b9ed1e78dd14fa8f8869bb064532c14789.wasm", + "tx_withdraw.wasm": "tx_withdraw.aeac73c6d037fa8bb35323abe79c333cee9e5e08c8c6a4a127adeddea13c0e17.wasm", + "vp_nft.wasm": "vp_nft.eb40e0cc90833eb0856af8c37dd1d1157910e20121c976a181c86e38768843b5.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.763b0f628543f1f9864a7e901ede01f7db69f9b4dbdeabc1b8eb4c66113a52c9.wasm", "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", - "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" + "vp_user.wasm": "vp_user.3a1d788882564bda81e8423254cb75c8d9ef1ca25ae6620b19765b0d171c195a.wasm" } \ No newline at end of file From 649f68c20c56fe92384ffe27e9d8e338a400e177 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:32:58 +0000 Subject: [PATCH 0855/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 057829c654..73038833d5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.aac575fac2ddaba8fca6f341b2ca6a77dd430767aae628e75aebf0b05470173f.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", - "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", - "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", - "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", - "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", - "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", - "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", - "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", - "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", - "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", - "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" + "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", + "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", + "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", + "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", + "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", + "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", + "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", + "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", + "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", + "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" } \ No newline at end of file From cf7bdfe190f1275b66c320f45b20c1ec971afb84 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:33:01 +0000 Subject: [PATCH 0856/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 73038833d5..4754de3ff7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", - "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", - "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", - "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", - "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", - "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", - "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", + "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", + "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", + "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", + "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", + "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", + "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", - "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", - "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", + "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", + "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" + "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" } \ No newline at end of file From f3f1a32b2b3608328fcfade63fb25d55d3c63efe Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 10:39:22 +0200 Subject: [PATCH 0857/1995] [fix]: Fixed some broken imports --- apps/src/lib/node/ledger/protocol/mod.rs | 3 +- .../transactions/ethereum_events/events.rs | 3 +- .../transactions/ethereum_events/mod.rs | 5 +- .../transactions/ethereum_events/read.rs | 3 +- .../transactions/ethereum_events/update.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 8 +- .../lib/node/ledger/shell/prepare_proposal.rs | 3 +- .../shell/vote_extensions/eth_events.rs | 3 +- .../shell/vote_extensions/val_set_update.rs | 3 +- apps/src/lib/node/ledger/storage/mod.rs | 3 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 3 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 221 ++++++++++-------- shared/src/ledger/storage/merkle_tree.rs | 18 +- shared/src/ledger/storage/mod.rs | 10 +- shared/src/types/eth_bridge_pool.rs | 6 +- tests/src/native_vp/pos.rs | 3 +- 16 files changed, 178 insertions(+), 120 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 35ca06fc60..9ffc1cd367 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -12,8 +12,9 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{DB, DBIter, Storage, traits::StorageHasher}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index 2564830a2c..b639a01fd2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -4,7 +4,8 @@ use std::collections::BTreeSet; use eyre::Result; use namada::ledger::eth_bridge::storage::wrapped_erc20s; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::storage::Key; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index a9b24deb39..8d9ea01eff 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -14,7 +14,8 @@ use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::eth_msgs::Keys; use namada::ledger::pos::types::WeightedValidator; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::{self, BlockHeight}; @@ -306,7 +307,7 @@ mod tests { use namada::ledger::pos::types::ValidatorSet; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; - use namada::ledger::storage::Sha256Hasher; + use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs index 5f377f8133..90cc960509 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs @@ -1,7 +1,8 @@ //! Helpers for reading from storage use borsh::BorshDeserialize; use eyre::{eyre, Result}; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs index d6aebac3b6..092dada10a 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs @@ -1,7 +1,8 @@ //! Helpers for writing to storage use borsh::{BorshDeserialize, BorshSerialize}; use eyre::Result; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::storage; use namada::types::token::Amount; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1f179c0db9..2936cf4db9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -26,10 +26,9 @@ use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, }; use namada::ledger::pos::namada_proof_of_stake::PosBase; +use namada::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use namada::ledger::storage::write_log::WriteLog; -use namada::ledger::storage::{ - DBIter, traits::Sha256Hasher, Storage, traits::StorageHasher, DB, -}; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; @@ -760,7 +759,8 @@ mod test_utils { #[cfg(not(feature = "abcipp"))] use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::{BlockStateWrite, MerkleTree, traits::Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::{BlockStateWrite, MerkleTree}; use namada::types::address::{xan, EstablishedAddressGen}; use namada::types::chain::ChainId; use namada::types::hash::Hash; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6880bd0598..521c704dca 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 2ced18ae08..f1caae112a 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -3,7 +3,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 91d581337f..542bf6b81f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,7 +4,8 @@ use std::collections::HashMap; use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage::{DB, DBIter, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 8017c6b7e4..373da98d5a 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -9,7 +9,8 @@ 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, traits::StorageHasher}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::Storage; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 128900240c..069b6ed0a7 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -930,7 +930,8 @@ mod imp { #[cfg(test)] mod test { - use namada::ledger::storage::{MerkleTree, traits::Sha256Hasher}; + use namada::ledger::storage::traits::Sha256Hasher; + use namada::ledger::storage::MerkleTree; use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f900efb595..66cdc346ec 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -157,7 +157,10 @@ impl BridgePoolTree { for (key, value) in keys.iter().zip(values.iter()) { let hash = Self::parse_key(key)?; if hash != value.keccak256() { - return Err(eyre!("Hashes of keys did not match hashes of values.").into()); + return Err(eyre!( + "Hashes of keys did not match hashes of values." + ) + .into()); } leaves.insert(hash); } @@ -358,8 +361,8 @@ mod test_bridge_pool_tree { use proptest::prelude::*; use super::*; - use crate::types::ethereum_events::EthAddress; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use crate::types::ethereum_events::EthAddress; /// An established user address for testing & development fn bertha_address() -> Address { @@ -375,7 +378,7 @@ mod test_bridge_pool_tree { assert_eq!(tree.root().0, [0; 32]); let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([1;20]), + asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -383,10 +386,11 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); - let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + let root = + KeccakHash::from(tree.update_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); } @@ -397,21 +401,23 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } - let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()).into(); + let expected: Hash = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) + .into(); assert_eq!(tree.root(), expected); } @@ -423,26 +429,29 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let hashes: BTreeSet = transfers.iter().map(|t| t.keccak256()).collect(); + let hashes: BTreeSet = + transfers.iter().map(|t| t.keccak256()).collect(); assert_eq!(hashes, tree.store); - let left_hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); - let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); + let left_hash = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); + let right_hash = + hash_pair(transfers[2].keccak256(), Default::default()); let expected: Hash = hash_pair(left_hash, right_hash).into(); assert_eq!(tree.root(), expected); } @@ -454,7 +463,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([1;20]), + asset: EthAddress([1; 20]), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -462,10 +471,11 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); - let root = KeccakHash::from(tree.update_key(&key).expect("Test failed")); + let root = + KeccakHash::from(tree.update_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); tree.delete_key(&key).expect("Test failed"); assert_eq!(tree.root().0, [0; 32]); @@ -479,15 +489,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -498,7 +508,9 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[2].keccak256()).into(); + let expected: Hash = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) + .into(); assert_eq!(tree.root(), expected); } @@ -515,11 +527,14 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let expected = transfer.keccak256(); let key = Key::from(&transfer); - assert_eq!(BridgePoolTree::parse_key(&key).expect("Test failed"), expected); + assert_eq!( + BridgePoolTree::parse_key(&key).expect("Test failed"), + expected + ); } /// Test that parsing a key with multiple segments fails @@ -535,24 +550,31 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let hash = transfer.keccak256().to_string(); - let key = Key{segments: vec![DbKeySeg::AddressSeg(bertha_address()), DbKeySeg::StringSeg(hash)]}; + let key = Key { + segments: vec![ + DbKeySeg::AddressSeg(bertha_address()), + DbKeySeg::StringSeg(hash), + ], + }; assert!(BridgePoolTree::parse_key(&key).is_err()); } /// Test that parsing a key that is not a hash fails #[test] fn test_key_not_hash() { - let key = Key{segments: vec![DbKeySeg::StringSeg("bloop".into())]}; + let key = Key { + segments: vec![DbKeySeg::StringSeg("bloop".into())], + }; assert!(BridgePoolTree::parse_key(&key).is_err()); } /// Test that [`contains_key`] works correctly #[test] fn test_contains_key() { - let mut tree = BridgePoolTree::default(); + let mut tree = BridgePoolTree::default(); let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), @@ -563,10 +585,13 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; tree.update_key(&Key::from(&transfer)).expect("Test failed"); - assert!(tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + assert!( + tree.contains_key(&Key::from(&transfer)) + .expect("Test failed") + ); let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), @@ -577,9 +602,13 @@ mod test_bridge_pool_tree { gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; - assert!(!tree.contains_key(&Key::from(&transfer)).expect("Test failed")); + assert!( + !tree + .contains_key(&Key::from(&transfer)) + .expect("Test failed") + ); } /// Test that the empty proof works. @@ -588,7 +617,9 @@ mod test_bridge_pool_tree { let tree = BridgePoolTree::default(); let keys = vec![]; let values = vec![]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(Default::default())); } @@ -600,17 +631,19 @@ mod test_bridge_pool_tree { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), amount: 0.into(), - nonce: 0.into() + nonce: 0.into(), }, gas_fee: GasFee { amount: 0.into(), - payer: bertha_address() - } + payer: bertha_address(), + }, }; let mut tree = BridgePoolTree::default(); let key = Key::from(&transfer); let _ = tree.update_key(&key).expect("Test failed"); - let proof = tree.get_membership_proof(array::from_ref(&key), vec![transfer]).expect("Test failed"); + let proof = tree + .get_membership_proof(array::from_ref(&key), vec![transfer]) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -623,15 +656,15 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -639,11 +672,12 @@ mod test_bridge_pool_tree { let _ = tree.update_key(&key).expect("Test failed"); } let key = Key::from(&transfers[0]); - let proof = tree.get_membership_proof( - array::from_ref(&key), - vec![transfers.remove(0)] - ) - .expect("Test failed"); + let proof = tree + .get_membership_proof( + array::from_ref(&key), + vec![transfers.remove(0)], + ) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -655,15 +689,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); @@ -673,7 +707,9 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let keys = vec![Key::from(&transfers[0]), Key::from(&transfers[1])]; let values = vec![transfers[0].clone(), transfers[1].clone()]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -685,15 +721,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -701,7 +737,9 @@ mod test_bridge_pool_tree { } let keys = vec![]; let values = vec![]; - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())) } @@ -713,15 +751,15 @@ mod test_bridge_pool_tree { for i in 0..2 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -729,7 +767,9 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, transfers) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -741,15 +781,15 @@ mod test_bridge_pool_tree { for i in 0..3 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); @@ -757,7 +797,9 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree.get_membership_proof(&keys, transfers).expect("Test failed"); + let proof = tree + .get_membership_proof(&keys, transfers) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -769,64 +811,57 @@ mod test_bridge_pool_tree { for i in 0..5 { let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([i;20]), - recipient: EthAddress([i+1; 20]), + asset: EthAddress([i; 20]), + recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), }, gas_fee: GasFee { amount: 0.into(), payer: bertha_address(), - } + }, }; let key = Key::from(&transfer); transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys: Vec<_> = transfers - .iter() - .step_by(2) - .map(Key::from) - .collect(); - let values: Vec<_> = transfers - .iter() - .step_by(2) - .cloned() - .collect(); - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let keys: Vec<_> = transfers.iter().step_by(2).map(Key::from).collect(); + let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); + let proof = tree + .get_membership_proof(&keys, values) + .expect("Test failed"); assert!(proof.verify(tree.root().into())); } /// Create a random set of transfers. - fn random_transfers(number: usize) -> impl Strategy> { + fn random_transfers( + number: usize, + ) -> impl Strategy> { prop::collection::vec( - ( - prop::array::uniform20(0u8..), - prop::num::u64::ANY, - ), + (prop::array::uniform20(0u8..), prop::num::u64::ANY), 0..=number, ) - .prop_flat_map( | addrs | + .prop_flat_map(|addrs| { Just( - addrs.into_iter().map(| (addr, nonce)| - PendingTransfer { + addrs + .into_iter() + .map(|(addr, nonce)| PendingTransfer { transfer: TransferToEthereum { asset: EthAddress(addr), recipient: EthAddress(addr), amount: Default::default(), - nonce: nonce.into() + nonce: nonce.into(), }, gas_fee: GasFee { amount: Default::default(), - payer: bertha_address() - } - }, - ) - .dedup() - .collect::>() + payer: bertha_address(), + }, + }) + .dedup() + .collect::>(), ) - ) + }) } prop_compose! { @@ -843,7 +878,7 @@ mod test_bridge_pool_tree { } } - proptest!{ + proptest! { /// Given a random tree and a subset of leaves, /// verify that the constructed multi-proof correctly /// verifies. @@ -866,4 +901,4 @@ mod test_bridge_pool_tree { assert!(proof.verify(tree.root().into())); } } -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 21f16031ad..1d175e83d2 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -17,7 +17,9 @@ use thiserror::Error; use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; -use crate::ledger::eth_bridge::storage::bridge_pool::{BridgePoolTree, get_signed_root_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_signed_root_key, BridgePoolTree, +}; use crate::ledger::storage::ics23_specs::ibc_leaf_spec; use crate::ledger::storage::{ics23_specs, types}; use crate::types::address::{Address, InternalAddress}; @@ -717,7 +719,8 @@ mod test { std::array::from_ref(&ibc_key), vec![ibc_val.clone().into()], ) - .unwrap(){ + .unwrap() + { MembershipProof::ICS23(proof) => proof, _ => panic!("Test failed"), }; @@ -770,11 +773,12 @@ mod test { tree.update(&pos_key, pos_val.clone()).unwrap(); let specs = proof_specs::(); - let proof = match tree.get_sub_tree_existence_proof( - std::array::from_ref(&pos_key), - vec![pos_val.clone().into()], - ) - .unwrap() + let proof = match tree + .get_sub_tree_existence_proof( + std::array::from_ref(&pos_key), + vec![pos_val.clone().into()], + ) + .unwrap() { MembershipProof::ICS23(proof) => proof, _ => panic!("Test failed"), diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1dd03af3d9..61f9ced6be 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -523,7 +523,8 @@ where .block .tree .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) - .map_err(Error::MerkleTreeError)? { + .map_err(Error::MerkleTreeError)? + { self.block .tree .get_tendermint_proof(key, proof) @@ -540,11 +541,14 @@ where array::from_ref(key), vec![value], ) - .map_err(Error::MerkleTreeError)? { + .map_err(Error::MerkleTreeError)? + { tree.get_tendermint_proof(key, proof) .map_err(Error::MerkleTreeError) } else { - Err(Error::MerkleTreeError(MerkleTreeError::TendermintProof)) + Err(Error::MerkleTreeError( + MerkleTreeError::TendermintProof, + )) } } None => Err(Error::NoMerkleTree { height }), diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index e7c55df8d8..36a378b8db 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -68,7 +68,11 @@ impl keccak::encode::Encode for PendingTransfer { impl From<&PendingTransfer> for Key { fn from(transfer: &PendingTransfer) -> Self { - Key{segments: vec![DbKeySeg::StringSeg(transfer.keccak256().to_string())]} + Key { + segments: vec![DbKeySeg::StringSeg( + transfer.keccak256().to_string(), + )], + } } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 7be49ac538..e7c7daf7c1 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -411,7 +411,8 @@ mod tests { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); let vp_env = TestNativeVpEnv::new(tx_env); - let result: Result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + let result: Result = + vp_env.validate_tx(PosVP::new, |_tx_data| {}); // Put the tx_env back before checking the result tx_host_env::set(vp_env.tx_env); let result = From 2ef1f95b4f1230f0fe48a637ba1c70f77affa3ad Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 10:48:47 +0200 Subject: [PATCH 0858/1995] [fix]: Fixing broken doc link --- shared/src/ledger/eth_bridge/storage/bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 66cdc346ec..f1dfc181c9 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -127,7 +127,7 @@ impl BridgePoolTree { } } - /// Return the root as a [`Hash`] type. + /// Return the root as a [`struct@Hash`] type. pub fn root(&self) -> Hash { self.root.clone().into() } From 5b259eba91878e43484c45be6d5a3559a89fbf22 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 09:16:07 +0000 Subject: [PATCH 0859/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 057829c654..558b72d3c0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.aac575fac2ddaba8fca6f341b2ca6a77dd430767aae628e75aebf0b05470173f.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", - "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", - "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", - "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", - "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", - "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", - "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", - "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", - "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", - "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", - "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" + "tx_from_intent.wasm": "tx_from_intent.e767aa7d060b7fb2657d364f0cea06c20048b7c19e7799d932494a72a1de17b5.wasm", + "tx_ibc.wasm": "tx_ibc.3f8e471d8d99b238a7399b0aa1bbe41d4072554488eff5cc119b00c57b22345c.wasm", + "tx_init_account.wasm": "tx_init_account.4be0bb8d68dc00570b0a724ae46029eaee81f6dfc7202766a6657440769df69a.wasm", + "tx_init_nft.wasm": "tx_init_nft.d0d13453e56596f4d53c5971332c393b0136d58d2ced56074dc476f64b530282.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f5447040cd013feaca417d5bac93a440312b930f997d3a565e81986dbb3d178c.wasm", + "tx_init_validator.wasm": "tx_init_validator.85db1169a7b37823bdb0eb30f719f36cf6ecff28a5bc4a3d543f6925c7e3a3fa.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.13ef749fad6f98b7e98c74039a65836777d3fe177a08f16dcea9b001fa39350c.wasm", + "tx_transfer.wasm": "tx_transfer.b2eb45f6f4327453e5b19ed822be4dd680ac255cd4e0f54a353c6a4bdebe4d5b.wasm", + "tx_unbond.wasm": "tx_unbond.9af49a6daeccf15a6b3bdbd241a7882f5ffe7b6cd2bcbc728940daad266c83a2.wasm", + "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6402700d3e400774d4a93946514d9c9e866cef5d42e48c824a2fe4f52224cb57.wasm", + "tx_withdraw.wasm": "tx_withdraw.6f0daba8ff3314202598898498dad586679b46700ddf63474a47efbc46281bae.wasm", + "vp_nft.wasm": "vp_nft.e3ca11213957817f55a30c365287d3cd03d7dbb96341940e00bfc818a442cb06.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f4c080ef9463fe6eef3a066b13ee99fe79d1fb39825c8750d376d80873353e04.wasm", + "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", + "vp_user.wasm": "vp_user.bde8e5cc24d67a85d4d9c56e884fd39b76e04ca5d040212ccf397d4c6b50801d.wasm" } \ No newline at end of file From de2d99be93f68e2f57a916d23ec0d54e71fbb05e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 13:32:29 +0100 Subject: [PATCH 0860/1995] Fix ABCI++ to compile again --- apps/src/lib/node/ledger/protocol/mod.rs | 32 +++++++++++++++++++ .../node/ledger/protocol/transactions/mod.rs | 1 + .../shell/vote_extensions/eth_events.rs | 8 ++--- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 8e46225a13..48ab28f7d1 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -20,6 +20,7 @@ use namada::types::address::{Address, InternalAddress}; use namada::types::storage; use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use namada::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; +#[cfg(not(feature = "abcipp"))] use namada::types::vote_extensions::ethereum_events; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::{self, wasm, WasmCacheAccess}; @@ -206,6 +207,7 @@ where /// is updated natively rather than via the wasm environment, so gas does not /// need to be metered and validity predicates are bypassed. A [`TxResult`] /// containing changed keys and the like should be returned in the normal way. +#[cfg(not(feature = "abcipp"))] pub(crate) fn apply_protocol_tx( tx: ProtocolTxType, storage: &mut Storage, @@ -234,6 +236,36 @@ where } } +#[cfg(feature = "abcipp")] +pub(crate) fn apply_protocol_tx( + tx: ProtocolTxType, + _storage: &mut Storage, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + match tx { + ProtocolTxType::EthereumEvents(_) + | ProtocolTxType::ValidatorSetUpdate(_) => { + // TODO(namada#198): implement this + tracing::warn!( + "Attempt made to apply an unimplemented protocol transaction, \ + no actions will be taken" + ); + Ok(TxResult::default()) + } + _ => { + tracing::error!( + "Attempt made to apply an unsupported protocol transaction! - \ + {:#?}", + tx + ); + Err(Error::TxTypeError) + } + } +} + /// Execute a transaction code. Returns verifiers requested by the transaction. fn execute_tx( tx: &Tx, diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 381dda4fee..41884b2153 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -4,4 +4,5 @@ //! to update their blockchain state in a deterministic way. This can be done //! natively rather than via the wasm environment as happens with regular //! transactions. +#[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 59717f4efd..69c893f954 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -369,8 +369,8 @@ mod test_vote_extensions { /// Test that ethereum events are added to vote extensions. /// Check that vote extensions pass verification. #[cfg(feature = "abcipp")] - #[test] - fn test_eth_events_vote_extension() { + #[tokio::test] + async fn test_eth_events_vote_extension() { let (mut shell, _, oracle) = setup(); let address = shell .mode @@ -389,8 +389,8 @@ mod test_vote_extensions { name: "Test".to_string(), address: EthAddress([0; 20]), }; - oracle.send(event_1.clone()).expect("Test failed"); - oracle.send(event_2.clone()).expect("Test failed"); + oracle.send(event_1.clone()).await.expect("Test failed"); + oracle.send(event_2.clone()).await.expect("Test failed"); let vote_extension = ::try_from_slice( &shell.extend_vote(Default::default()).vote_extension[..], From d61ea911cd408fce5aff6b102508a3ce8d41c41e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 0861/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 29 ++++ apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 72 ++++++--- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 57 ++++++- 7 files changed, 295 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index ad3d2f5fcb..319b138e51 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -16,6 +16,9 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block Epoch, + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix @@ -24,6 +27,16 @@ pub enum Path { HasKey(storage::Key), } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -34,6 +47,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -52,6 +68,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } } } } @@ -64,6 +87,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 5d04cef45c..1f670944d5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,22 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Epoch, Key, PrefixValue}; @@ -18,6 +27,7 @@ use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -84,6 +94,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, }, Err(err) => response::Query { code: 1, @@ -307,6 +325,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index c0ccf75f68..cdea60eae0 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -63,8 +64,14 @@ pub struct BridgePoolTree { impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool - pub fn new(root: KeccakHash, store: BTreeSet) -> Self { - Self { root, store } + pub fn new(store: BTreeSet) -> Self { + let mut tree = Self { + root: KeccakHash::default(), + store, + }; + let root = tree.compute_root(); + tree.root = root; + tree } /// Parse the key to ensure it is of the correct type. @@ -83,7 +90,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.store.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -118,9 +125,9 @@ impl BridgePoolTree { } } - /// Return the root as a [`Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + /// Return the root as a [`struct@Hash`] type. + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store @@ -344,6 +351,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { use std::array; @@ -406,9 +438,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -443,7 +474,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -499,9 +530,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -635,7 +665,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(array::from_ref(&key), vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -669,7 +699,7 @@ mod test_bridge_pool_tree { vec![transfers.remove(0)], ) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -701,7 +731,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -731,7 +761,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -761,7 +791,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -791,7 +821,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -822,7 +852,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -889,7 +919,7 @@ mod test_bridge_pool_tree { values.push(transfer); } let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 1d175e83d2..bd27e83857 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 61f9ced6be..a8056a0dfc 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -21,7 +21,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 5491b78685..69ea31e37f 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 36a378b8db..b33fee72ff 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,11 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; -use crate::types::keccak; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -55,14 +56,16 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl keccak::encode::Encode for PendingTransfer { +impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::String(self.gas_fee.payer.to_string()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, nonce] + vec![version, namespace, from, to, amount, fee, nonce] } } @@ -96,3 +99,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From b608c78bdf6f67846baf4b5287ba3b436bf71a67 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Oct 2022 08:04:23 +0000 Subject: [PATCH 0862/1995] [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 de0f089b1f..c929fc60c2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3b89f8ae7c2d0e78d813fd249bcfb80871205fa0eac306004184155df972bda5.wasm", - "tx_ibc.wasm": "tx_ibc.948b68260e10def4a1b80d0a13c8c02d25f18920cbbb6ef0febacd800cd2e4aa.wasm", - "tx_init_account.wasm": "tx_init_account.33ba50356486f46204bb311259446bad8217b3fad3338d3729097c4c27cf6123.wasm", - "tx_init_nft.wasm": "tx_init_nft.37dc3000a2365db54f0fbf550501e6d9ac6572820ce7a70dfa894e07e67bb764.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5db61cb13d2739fdb72e4f46b9f0413d41102d5b91b5b0c01fed87555c68723.wasm", - "tx_init_validator.wasm": "tx_init_validator.b564420e6a58e6a397df44c929e1e8e3d3297a76f17a008c0035cd95f46974d0.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.26733715c5ef27cb27a6f88cbef2d468f092e357c39f2272195b94db3a2ca63e.wasm", - "tx_transfer.wasm": "tx_transfer.eb6d5fa86d9276f3628e3e093acaf11a77b1f44d8016ac971803e8f4613bdc2f.wasm", - "tx_unbond.wasm": "tx_unbond.553d6ca840850f1e76c3fda04efc8d3a929b4e0e69c0129685f3871a060b3ed0.wasm", - "tx_update_vp.wasm": "tx_update_vp.8e12c05146f0189242f53ba0aa077729fb25b1617b179180b290a92f4d0117b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.0f6c8bb64f0b5121efffe1f72a8dfcd584b523979b7fdd3e73e93e61a46bf31b.wasm", - "tx_withdraw.wasm": "tx_withdraw.fdbdf0db6de1d53c259f84b503d807a67560a3b45f477301e9a0b20ea8275034.wasm", - "vp_nft.wasm": "vp_nft.b1387806bd245f55eb7ef6ba70f4e645eab7dcc048ac8d314d8162fdd021cb9d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.356e4873a8d44eba2f5472c2d37485a30b24fd6785fe1dc017d031ec116dbe6f.wasm", - "vp_token.wasm": "vp_token.3f492f380226899d1c0da5d5b69a6de5e67b8a7ebfc5179a506bf38c23819c96.wasm", - "vp_user.wasm": "vp_user.c11d62664abc50e9b613acba04b040f93c2a8fe013a4a916e452c6323cb1bd29.wasm" + "tx_from_intent.wasm": "tx_from_intent.3e5734a16b9ed575111765dd318c6f0045598991035b959a24f99bf77bd14634.wasm", + "tx_ibc.wasm": "tx_ibc.bd919697f8fc9bed1d50abf5b3a8e1b3b737fe2e65bfd8c055e965f0831910a5.wasm", + "tx_init_account.wasm": "tx_init_account.078a50524745f9293b3b32c0694eceb3da75621d99e1e44ef79f45928c53a4a2.wasm", + "tx_init_nft.wasm": "tx_init_nft.fe9013d06b1de44091da6adac2458a3688a2ce54177f758b2386c8307c9ed9c2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.eb7551c29dab7fc2d317568b5d94a4b829ed8083eda7431f7c7507c0ee23a10f.wasm", + "tx_init_validator.wasm": "tx_init_validator.88506a9b248679d9f65f9adea6ab99f9fe4fa189b1b7928083f03eba1985c171.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.cc2b4de9f8999ef6a56762d021a1f2ed81cd5d9082de3574afbc4195fbb90528.wasm", + "tx_transfer.wasm": "tx_transfer.b54edcb95323d2a7c2dacf695cae32ff1e28ad78b07bb16450903f27ac940c59.wasm", + "tx_unbond.wasm": "tx_unbond.ff2cff17d1ab94e4fbd69ab30af4ee732e576498e52dc33b15e1bb43d2dfc074.wasm", + "tx_update_vp.wasm": "tx_update_vp.28bfdcddf988bf0e9f5b51fd6f592931be8fdd51f960073afa47e0f9f15e81c2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.66ee2ecebe5ebb2c1bc8749888890ef9d542affc23d27f0414c36a73a649f7e0.wasm", + "tx_withdraw.wasm": "tx_withdraw.4edb668ae6fcfe5dccdd3b58ffee3772f2e6d57e3794207b9353f87dff1940e3.wasm", + "vp_nft.wasm": "vp_nft.e34a534fe7621ec07036fab390098c90b21766f02f2a14800458cf3f3500480b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.5475409ca94d33ffc4ffc93872f230a62ebf06545b601dc8427bb2bfa23834a2.wasm", + "vp_token.wasm": "vp_token.9d7841550cb1f20f25b8701b1b4075f43638d9dea77733035f106f98566bc24e.wasm", + "vp_user.wasm": "vp_user.dcc6032b8435ed6aeca16fb2eb54e8ffe8759b9efc11f67ffe98a334fb74dab1.wasm" } \ No newline at end of file From a2b2145eb1350ca12d07ca921256c9835fe8c2ea Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 12:33:32 +0100 Subject: [PATCH 0863/1995] Update specs --- .../ethereum-bridge/ethereum_events_attestation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md index d5a3e0e4ea..1c8bdd3095 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/ethereum_events_attestation.md @@ -49,7 +49,7 @@ keys involved are: ``` # all values are Borsh-serialized /eth_msgs/\$msg_hash/body : EthereumEvent -/eth_msgs/\$msg_hash/seen_by : Vec
+/eth_msgs/\$msg_hash/seen_by : BTreeSet
/eth_msgs/\$msg_hash/voting_power: (u64, u64) # reduced fraction < 1 e.g. (2, 3) /eth_msgs/\$msg_hash/seen: bool ``` From 4fe23aa9d261f8acbb5270975d8d9c848fd3b4cd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 12:39:43 +0100 Subject: [PATCH 0864/1995] Switch eth_msgs seen_by to BTreeSet --- .../transactions/ethereum_events/eth_msgs.rs | 12 +++++------- .../protocol/transactions/ethereum_events/mod.rs | 3 ++- shared/src/ledger/eth_bridge/storage/eth_msgs.rs | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 71c01f1c29..6c57ea277b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -22,11 +22,9 @@ use namada::types::voting_power::FractionalVotingPower; pub struct EthMsgUpdate { /// The event being seen pub body: EthereumEvent, - /// Addresses of the validators who have just seen this event - /// we use [`BTreeSet`] even though ordering is not important here, so that - /// we can derive [`Hash`] for [`EthMsgUpdate`]. This also conveniently - /// orders addresses in the order in which they should be stored in - /// blockchain storage. + /// Addresses of the validators who have just seen this event. We use + /// [`BTreeSet`] even though ordering is not important here, so that we + /// can derive [`Hash`] for [`EthMsgUpdate`]. // NOTE(feature = "abcipp"): This can just become BTreeSet
because // BlockHeight will always be the previous block pub seen_by: BTreeSet<(Address, BlockHeight)>, @@ -52,8 +50,8 @@ pub struct EthMsg { pub body: EthereumEvent, /// The total voting power that's voted for this event across all epochs pub voting_power: FractionalVotingPower, - /// The addresses of validators that voted for this event, in sorted order. - pub seen_by: Vec
, + /// The addresses of validators that voted for this event + pub seen_by: BTreeSet
, /// Whether this event has been acted on or not pub seen: bool, } diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index a9b24deb39..7451ffff68 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -249,7 +249,8 @@ where ); let body: EthereumEvent = read::value(store, ð_msg_keys.body())?; let seen: bool = read::value(store, ð_msg_keys.seen())?; - let seen_by: Vec
= read::value(store, ð_msg_keys.seen_by())?; + let seen_by: BTreeSet
= + read::value(store, ð_msg_keys.seen_by())?; let voting_power: FractionalVotingPower = read::value(store, ð_msg_keys.voting_power())?; diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs index e133e22918..a290973ba4 100644 --- a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs +++ b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs @@ -40,7 +40,8 @@ impl Keys { .expect("should always be able to construct this key") } - /// Get the `seen_by` key - there should be a [`Vec
`] stored here. + /// Get the `seen_by` key - there should be a [`BTreeSet
`] stored + /// here. pub fn seen_by(&self) -> Key { self.prefix .push(&SEEN_BY_KEY_SEGMENT.to_owned()) From 1c78964ee050a49d847e55864c14428b78022932 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 12:56:36 +0100 Subject: [PATCH 0865/1995] Convert EthMsg/MultiSignedEthEvent seen_by to BTreeSet --- .../protocol/transactions/ethereum_events/eth_msgs.rs | 4 ++-- .../protocol/transactions/ethereum_events/mod.rs | 10 ++-------- .../protocol/transactions/ethereum_events/utils.rs | 4 ++-- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 ++++---- .../node/ledger/shell/vote_extensions/eth_events.rs | 4 ++-- shared/src/types/vote_extensions/ethereum_events.rs | 10 ++++------ 8 files changed, 19 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 6c57ea277b..dbd3917fac 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -58,7 +58,7 @@ pub struct EthMsg { #[cfg(test)] mod tests { - use std::collections::{BTreeSet, HashSet}; + use std::collections::BTreeSet; use namada::types::address; use namada::types::ethereum_events::testing::{ @@ -76,7 +76,7 @@ mod tests { let event = arbitrary_single_transfer(arbitrary_nonce(), receiver); let with_signers = MultiSignedEthEvent { event: event.clone(), - signers: HashSet::from_iter(vec![( + signers: BTreeSet::from([( sole_validator.clone(), BlockHeight(100), )]), diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 7451ffff68..c6a43d511d 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -449,10 +449,7 @@ mod tests { &mut storage, vec![MultiSignedEthEvent { event: event.clone(), - signers: HashSet::from_iter(vec![( - sole_validator, - BlockHeight(100), - )]), + signers: BTreeSet::from([(sole_validator, BlockHeight(100))]), }], ); @@ -510,10 +507,7 @@ mod tests { &mut storage, vec![MultiSignedEthEvent { event: event.clone(), - signers: HashSet::from_iter(vec![( - validator_a, - BlockHeight(100), - )]), + signers: BTreeSet::from([(validator_a, BlockHeight(100))]), }], ); let tx_result = match result { diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs index 990e3b5fde..877b0179f4 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs @@ -281,7 +281,7 @@ mod tests { 1.into(), address::testing::established_address_1(), ), - signers: HashSet::from_iter(vec![ + signers: BTreeSet::from([ ( address::testing::established_address_1(), BlockHeight(100), @@ -297,7 +297,7 @@ mod tests { 2.into(), address::testing::established_address_2(), ), - signers: HashSet::from_iter(vec![ + signers: BTreeSet::from([ ( address::testing::established_address_1(), BlockHeight(101), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6ff72a1ec0..2798ea5a9e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -906,7 +906,7 @@ mod test_finalize_block { #[cfg(feature = "abcipp")] signers: HashSet::from([address.clone()]), #[cfg(not(feature = "abcipp"))] - signers: HashSet::from([( + signers: BTreeSet::from([( address.clone(), shell.storage.last_height, )]), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6917635d06..a95ffcc5bd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -276,7 +276,7 @@ pub(super) mod record { // TODO: write tests for validator set update vote extensions in // prepare proposals mod test_prepare_proposal { - use std::collections::{HashMap, HashSet}; + use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos::namada_proof_of_stake::types::{ @@ -569,7 +569,7 @@ mod test_prepare_proposal { let events = vec![MultiSignedEthEvent { event: ext.data.ethereum_events[0].clone(), signers: { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] s.insert(ext.data.validator_addr.clone()); #[cfg(not(feature = "abcipp"))] diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 0e4e22afa8..75cab7aa8c 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -411,7 +411,7 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { - use std::collections::{HashMap, HashSet}; + use std::collections::HashMap; use assert_matches::assert_matches; use borsh::BorshDeserialize; @@ -580,7 +580,7 @@ mod test_process_proposal { events: vec![MultiSignedEthEvent { event, signers: { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] s.insert(addr); #[cfg(not(feature = "abcipp"))] @@ -634,7 +634,7 @@ mod test_process_proposal { events: vec![MultiSignedEthEvent { event, signers: { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] s.insert(addr); #[cfg(not(feature = "abcipp"))] @@ -705,7 +705,7 @@ mod test_process_proposal { events: vec![MultiSignedEthEvent { event, signers: { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] s.insert(addr); #[cfg(not(feature = "abcipp"))] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 69c893f954..0f55c21085 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -1,6 +1,6 @@ //! Extend Tendermint votes with Ethereum events seen by a quorum of validators. -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::{DBIter, StorageHasher, DB}; @@ -239,7 +239,7 @@ where // register all ethereum events seen by `validator_addr` for ev in vote_extension.data.ethereum_events { let signers = - event_observers.entry(ev).or_insert_with(HashSet::new); + event_observers.entry(ev).or_insert_with(BTreeSet::new); #[cfg(feature = "abcipp")] signers.insert(validator_addr.clone()); #[cfg(not(feature = "abcipp"))] diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 9eaf838480..da48c5146e 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -1,7 +1,7 @@ //! Contains types necessary for processing Ethereum events //! in vote extensions. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -79,11 +79,11 @@ pub struct MultiSignedEthEvent { pub event: EthereumEvent, /// List of addresses of validators who signed this event #[cfg(feature = "abcipp")] - pub signers: HashSet
, + pub signers: BTreeSet
, /// List of addresses of validators who signed this event /// and block height at which they signed it #[cfg(not(feature = "abcipp"))] - pub signers: HashSet<(Address, BlockHeight)>, + pub signers: BTreeSet<(Address, BlockHeight)>, } /// Compresses a set of signed [`Vext`] instances, to save @@ -162,8 +162,6 @@ impl VextDigest { #[cfg(test)] mod tests { - use std::collections::HashSet; - use super::*; use crate::proto::Signed; use crate::types::address::{self, Address}; @@ -276,7 +274,7 @@ mod tests { #[cfg(not(feature = "abcipp"))] let signers = { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); s.insert((validator_1.clone(), last_block_height)); s.insert(( validator_1.clone(), From a01f4254fd7a27c069c9d1f7049a52035e01e718 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 17:21:36 +0100 Subject: [PATCH 0866/1995] Remove comment --- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index c6a43d511d..e3af67d46b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -220,8 +220,6 @@ fn calculate_new_eth_msg( EthMsg { body: update.body, voting_power: seen_by_voting_power, - // the below `.collect()` is deterministic and will result in a - // sorted vector as `update.seen_by` is a [`BTreeSet`] seen_by: update .seen_by .into_iter() From 1e963e1e157b0831f0b3e7ffa02839ebeab2af3f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 12 Oct 2022 17:30:14 +0100 Subject: [PATCH 0867/1995] Fix cargo doc --- shared/src/ledger/eth_bridge/storage/eth_msgs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs index a290973ba4..88b168fdc3 100644 --- a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs +++ b/shared/src/ledger/eth_bridge/storage/eth_msgs.rs @@ -40,7 +40,7 @@ impl Keys { .expect("should always be able to construct this key") } - /// Get the `seen_by` key - there should be a [`BTreeSet
`] stored + /// Get the `seen_by` key - there should be a `BTreeSet
` stored /// here. pub fn seen_by(&self) -> Key { self.prefix From 9d2ace803f955fc63e5e91c03b79ec9064eaed86 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Oct 2022 08:56:29 +0000 Subject: [PATCH 0868/1995] [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 c929fc60c2..0ccb909760 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3e5734a16b9ed575111765dd318c6f0045598991035b959a24f99bf77bd14634.wasm", - "tx_ibc.wasm": "tx_ibc.bd919697f8fc9bed1d50abf5b3a8e1b3b737fe2e65bfd8c055e965f0831910a5.wasm", - "tx_init_account.wasm": "tx_init_account.078a50524745f9293b3b32c0694eceb3da75621d99e1e44ef79f45928c53a4a2.wasm", - "tx_init_nft.wasm": "tx_init_nft.fe9013d06b1de44091da6adac2458a3688a2ce54177f758b2386c8307c9ed9c2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.eb7551c29dab7fc2d317568b5d94a4b829ed8083eda7431f7c7507c0ee23a10f.wasm", - "tx_init_validator.wasm": "tx_init_validator.88506a9b248679d9f65f9adea6ab99f9fe4fa189b1b7928083f03eba1985c171.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.cc2b4de9f8999ef6a56762d021a1f2ed81cd5d9082de3574afbc4195fbb90528.wasm", - "tx_transfer.wasm": "tx_transfer.b54edcb95323d2a7c2dacf695cae32ff1e28ad78b07bb16450903f27ac940c59.wasm", - "tx_unbond.wasm": "tx_unbond.ff2cff17d1ab94e4fbd69ab30af4ee732e576498e52dc33b15e1bb43d2dfc074.wasm", - "tx_update_vp.wasm": "tx_update_vp.28bfdcddf988bf0e9f5b51fd6f592931be8fdd51f960073afa47e0f9f15e81c2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.66ee2ecebe5ebb2c1bc8749888890ef9d542affc23d27f0414c36a73a649f7e0.wasm", - "tx_withdraw.wasm": "tx_withdraw.4edb668ae6fcfe5dccdd3b58ffee3772f2e6d57e3794207b9353f87dff1940e3.wasm", - "vp_nft.wasm": "vp_nft.e34a534fe7621ec07036fab390098c90b21766f02f2a14800458cf3f3500480b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.5475409ca94d33ffc4ffc93872f230a62ebf06545b601dc8427bb2bfa23834a2.wasm", - "vp_token.wasm": "vp_token.9d7841550cb1f20f25b8701b1b4075f43638d9dea77733035f106f98566bc24e.wasm", - "vp_user.wasm": "vp_user.dcc6032b8435ed6aeca16fb2eb54e8ffe8759b9efc11f67ffe98a334fb74dab1.wasm" + "tx_from_intent.wasm": "tx_from_intent.fc96b58e4a49608e5a449b519f0470e387ed81995d615a617fdefad0d1985e9e.wasm", + "tx_ibc.wasm": "tx_ibc.1e14034884781c89e441ee545db1a1918914f84159dee4a9c631214aa1f99598.wasm", + "tx_init_account.wasm": "tx_init_account.1cc5bdc6a2f12497a8aad73472021d1e83d2f2a70889f43bcb732ea476cb76fc.wasm", + "tx_init_nft.wasm": "tx_init_nft.fde5d70cfd8921867a9f72be02fe865212a030779de0aba471713e53b820f0e3.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bdbd8a9fdfc68b566795c9302844b5ac6a428f06071bc482c34dffaf3f323c16.wasm", + "tx_init_validator.wasm": "tx_init_validator.3e86adf8a6292275591d998249a3d803f6dce37f4fea920e88e2e806ba2983a6.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.12705b20b935d36113c28ed49fbced9c01b81aca98e9dd5cf071cf85eae2a298.wasm", + "tx_transfer.wasm": "tx_transfer.7b969d9e5405cb4eeb23bc61b6df3b7cd47931686ec22e5325272699f2ad5542.wasm", + "tx_unbond.wasm": "tx_unbond.04721b263f0149b17788f50c1d9264b3625a06ac93fd779f3e58a9aa1afdf824.wasm", + "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c5943428368cd06b4ceaa0f69eec3316f2e33051054d451bab40ee87379cabd0.wasm", + "tx_withdraw.wasm": "tx_withdraw.26529ab26c7e4fbd331a243d318fc2803fe8be16e4f17cda8f24844a9278ea81.wasm", + "vp_nft.wasm": "vp_nft.040e15d71e03b105738962903f08a4bf7c08de21d210b87590c18c5275022491.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab8a3988cb4613b26a64dbeaea3ba8fbc3af55aeeecda12b945cf60a85de4bb3.wasm", + "vp_token.wasm": "vp_token.b517a4a2be9375eab53b885e23a212cfec0dd1f30381168e879f3b67fc6fd199.wasm", + "vp_user.wasm": "vp_user.bf7af5c1db9a1bd536ae7ff782ceabb148b8e860a4af55bb8ebaa8fcc128ccd9.wasm" } \ No newline at end of file From 44b4913ae9873e947f83718136557f77030e22f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Oct 2022 09:24:40 +0000 Subject: [PATCH 0869/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0ccb909760..c0f61a1ea4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.fc96b58e4a49608e5a449b519f0470e387ed81995d615a617fdefad0d1985e9e.wasm", - "tx_ibc.wasm": "tx_ibc.1e14034884781c89e441ee545db1a1918914f84159dee4a9c631214aa1f99598.wasm", - "tx_init_account.wasm": "tx_init_account.1cc5bdc6a2f12497a8aad73472021d1e83d2f2a70889f43bcb732ea476cb76fc.wasm", - "tx_init_nft.wasm": "tx_init_nft.fde5d70cfd8921867a9f72be02fe865212a030779de0aba471713e53b820f0e3.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bdbd8a9fdfc68b566795c9302844b5ac6a428f06071bc482c34dffaf3f323c16.wasm", - "tx_init_validator.wasm": "tx_init_validator.3e86adf8a6292275591d998249a3d803f6dce37f4fea920e88e2e806ba2983a6.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.12705b20b935d36113c28ed49fbced9c01b81aca98e9dd5cf071cf85eae2a298.wasm", - "tx_transfer.wasm": "tx_transfer.7b969d9e5405cb4eeb23bc61b6df3b7cd47931686ec22e5325272699f2ad5542.wasm", - "tx_unbond.wasm": "tx_unbond.04721b263f0149b17788f50c1d9264b3625a06ac93fd779f3e58a9aa1afdf824.wasm", + "tx_from_intent.wasm": "tx_from_intent.9782c72ce9a90592848ef693e82d1fac7b719e8581df2f354592468c3e0fba0f.wasm", + "tx_ibc.wasm": "tx_ibc.da0a29ab7bdb98f03c658bb6f26c80a07ee437ce85c53e418c00abf0ea32db34.wasm", + "tx_init_account.wasm": "tx_init_account.c473c703b40b5c8e7c4ea6d2809d1d2c3f37edc85efd9945e0e7fd10b6183a39.wasm", + "tx_init_nft.wasm": "tx_init_nft.d3155d8776ea32c12353fb72f312a52ed9e2845eded0d9c2ed1b8c7adf6c10b6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a61ad9ab854bccf074f2498fdcaf03e26e15c9dfce884c8415d49ddd394c55cc.wasm", + "tx_init_validator.wasm": "tx_init_validator.477a5da8ca1586078eae79f8188698c558f1278439610066595ef712c55d9ce7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.08cc5a08c593bd8a67186e39c5d09fff04d262c7234bc314f365bd86fc0e54ed.wasm", + "tx_transfer.wasm": "tx_transfer.6f5954270608aac965a90778f168626c7b4757b78fc15dd2be408ce0ece8e423.wasm", + "tx_unbond.wasm": "tx_unbond.2299b16187c424f6dec074c03d76568c8723b7c47c4621c031a624a926c24df3.wasm", "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c5943428368cd06b4ceaa0f69eec3316f2e33051054d451bab40ee87379cabd0.wasm", - "tx_withdraw.wasm": "tx_withdraw.26529ab26c7e4fbd331a243d318fc2803fe8be16e4f17cda8f24844a9278ea81.wasm", - "vp_nft.wasm": "vp_nft.040e15d71e03b105738962903f08a4bf7c08de21d210b87590c18c5275022491.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab8a3988cb4613b26a64dbeaea3ba8fbc3af55aeeecda12b945cf60a85de4bb3.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.401adac389df5c0143fe44ecd87eb3bf2e374d1ef8d1a13217ef3369c48a2a1a.wasm", + "tx_withdraw.wasm": "tx_withdraw.5913f833ad93fd363ba12fdc7f95431044aa5d19eb13d77f337464f95d8aaf62.wasm", + "vp_nft.wasm": "vp_nft.2f58ad62ceb603e25ee0f175b1c12c8499bc15e5bc04e5ea279d1fdfb8392159.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.aa9631f5c9dc4d3a1158956b7561a4f953ee5fc7bea4d3d0b0bd6f14cc04879a.wasm", "vp_token.wasm": "vp_token.b517a4a2be9375eab53b885e23a212cfec0dd1f30381168e879f3b67fc6fd199.wasm", - "vp_user.wasm": "vp_user.bf7af5c1db9a1bd536ae7ff782ceabb148b8e860a4af55bb8ebaa8fcc128ccd9.wasm" + "vp_user.wasm": "vp_user.e412597092c8ec5859b52a08861081e4e321b743efbbda5783eb432a3ff9a2a4.wasm" } \ No newline at end of file From 025263b0392d5a2815a5945c5f607bb9972bc1e4 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Fri, 7 Oct 2022 17:50:39 +0200 Subject: [PATCH 0870/1995] ci: added tendermint ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 --- .github/workflows/build-tendermint.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 5d7224abb7..91a5259dac 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -24,6 +24,9 @@ jobs: - name: tendermint-unreleased repository: heliaxdev/tendermint tendermint_version: 559fb33ff9b27503ce7ac1c7d8589fe1d8b3e900 + - name: tendermint-unreleased + repository: heliaxdev/tendermint + tendermint_version: ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 steps: - name: Build ${{ matrix.make.name }} From 656f228413e41777b625f629f6a991be5102b096 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 6 Oct 2022 13:18:17 +0200 Subject: [PATCH 0871/1995] ci: added gh action specific to eth-bridge-integration branch --- .github/workflows/build-and-test-bridge.yml | 335 ++++++++++++++++++++ .github/workflows/build-and-test.yml | 3 + 2 files changed, 338 insertions(+) create mode 100644 .github/workflows/build-and-test-bridge.yml diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml new file mode 100644 index 0000000000..cee43b941b --- /dev/null +++ b/.github/workflows/build-and-test-bridge.yml @@ -0,0 +1,335 @@ +name: Build and Test Ethereum Bridge + +on: + push: + branches: + - eth-bridge-integration + # Run in PRs with conflicts (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) + pull_request_target: + branches: + - eth-bridge-integration + types: [opened, synchronize, reopened] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + id-token: write + contents: read + packages: read + +env: + GIT_LFS_SKIP_SMUDGE: 1 + + +jobs: + build-wasm: + timeout-minutes: 30 + runs-on: ${{ matrix.os }} + container: + image: ghcr.io/anoma/namada:wasm-0.6.1 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + wasm_cache_version: ["v1"] + + 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' }} + # From https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target: + # "This event runs in the context of the base of the pull request, + # rather than in the context of the merge commit, as the pull_request + # event does." + # We set the ref to the head commit of the PR instead. + # For this, we have to make sure that we're not running CI on untrusted + # code (more info in the link above), so the repo MUST be configured + # 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 + run: | + make build-wasm-scripts + - name: Upload wasm artifacts + uses: actions/upload-artifact@v3 + with: + name: wasm-${{ github.sha }} + path: | + wasm/tx_*.wasm + wasm/vp_*.wasm + wasm/checksums.json + - name: Test Wasm + run: make test-wasm + - name: Check wasm up-to-date + run: cmp -- wasm/checksums.json wasm/original-checksums.json + - name: Print diff + if: failure() + run: diff -y -W 150 wasm/checksums.json wasm/original-checksums.json --suppress-common-lines + + update-wasm: + runs-on: ${{ matrix.os }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.build-wasm.result == 'success' }} + timeout-minutes: 30 + needs: [build-wasm] + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + + steps: + - 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: Download wasm artifacts + uses: actions/download-artifact@v3 + with: + name: wasm-${{ github.sha }} + path: ./wasm + - name: Update WASM + run: aws s3 sync wasm s3://$BUCKET_NAME --acl public-read --exclude "*" --include "*.wasm" --exclude "*/*" --region $AWS_REGION + env: + BUCKET_NAME: namada-wasm-master + AWS_REGION: eu-west-1 + + anoma-eth: + runs-on: ${{ matrix.os }} + timeout-minutes: 80 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + nightly_version: [nightly-2022-05-20] + make: + - name: ABCI + suffix: '' + cache_key: anoma + cache_version: v1 + wait_for: anoma-release-eth (ubuntu-latest, ABCI Release build, anoma-e2e-release, v1) + tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: full + 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: Cache cargo registry + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- + - name: Start sccache server + run: sccache --start-server + - name: Build + run: make build${{ matrix.make.suffix }} + - name: Build test + run: make build-test${{ matrix.make.suffix }} + - name: Download wasm artifacts + uses: actions/download-artifact@v3 + with: + name: wasm-${{ github.sha }} + path: ./wasm + - name: Run unit test + run: make test-unit${{ matrix.make.suffix }} + - name: Wait for release binaries + uses: lewagon/wait-on-check-action@master + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + check-name: ${{ matrix.make.wait_for }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 30 + allowed-conclusions: success + - name: Download tendermint binaries + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{secrets.GITHUB_TOKEN}} + workflow: build-tendermint.yml + workflow_conclusion: success + name: ${{ matrix.make.tendermint_artifact }} + path: /usr/local/bin + - name: Download anoma binaries + uses: actions/download-artifact@v3 + with: + name: binaries${{ matrix.make.suffix }}-${{ github.sha }} + path: ./target/release/ + - name: Change permissions + run: | + chmod +x target/release/namada + chmod +x target/release/namadaw + 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: + ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: 20 + ANOMA_E2E_USE_PREBUILT_BINARIES: "true" + 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() + uses: actions/upload-artifact@v3 + with: + name: logs-e2e${{ matrix.make.suffix }}-${{ github.sha }} + path: | + /tmp/.*/logs/ + /tmp/.*/e2e-test.*/setup/validator-*/.anoma/logs/*.log + retention-days: 5 + - name: Print sccache stats + if: always() + run: sccache --show-stats + - name: Stop sccache server + if: always() + run: sccache --stop-server || true + + anoma-release-eth: + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + make: + - name: ABCI Release build + suffix: '' + cache_key: anoma-e2e-release + cache_version: "v1" + + env: + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: full + 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: Cache cargo registry + uses: actions/cache@v3 + continue-on-error: false + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- + - name: Start sccache server + run: sccache --start-server + - name: Build + run: make build-release${{ matrix.make.suffix }} + - name: Upload target binaries + uses: actions/upload-artifact@v3 + with: + name: binaries${{ matrix.make.suffix }}-${{ github.sha }} + path: | + target/release/namada + target/release/namadac + target/release/namadaw + target/release/namadan + - name: Print sccache stats + if: always() + run: sccache --show-stats + - name: Stop sccache server + if: always() + run: sccache --stop-server || true diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e2e72820c8..516a93ce41 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -4,8 +4,11 @@ on: push: branches: - main + - "!eth-bridge-integration" # Run in PRs with conflicts (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) pull_request_target: + branches: + - "!eth-bridge-integration" types: [opened, synchronize, reopened] workflow_dispatch: From 6aaca13a32c20cd960c872d2b1ed965f66315c0b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 6 Oct 2022 19:56:41 +0200 Subject: [PATCH 0872/1995] ci: fix build-and-test.yml --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 516a93ce41..f8d07c94f7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -8,6 +8,7 @@ on: # Run in PRs with conflicts (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) pull_request_target: branches: + - main - "!eth-bridge-integration" types: [opened, synchronize, reopened] workflow_dispatch: From 0800fadd8d7048f89f261326eed68f5927fe7b72 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Oct 2022 18:00:53 +0000 Subject: [PATCH 0873/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index c0f61a1ea4..0ccb909760 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9782c72ce9a90592848ef693e82d1fac7b719e8581df2f354592468c3e0fba0f.wasm", - "tx_ibc.wasm": "tx_ibc.da0a29ab7bdb98f03c658bb6f26c80a07ee437ce85c53e418c00abf0ea32db34.wasm", - "tx_init_account.wasm": "tx_init_account.c473c703b40b5c8e7c4ea6d2809d1d2c3f37edc85efd9945e0e7fd10b6183a39.wasm", - "tx_init_nft.wasm": "tx_init_nft.d3155d8776ea32c12353fb72f312a52ed9e2845eded0d9c2ed1b8c7adf6c10b6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a61ad9ab854bccf074f2498fdcaf03e26e15c9dfce884c8415d49ddd394c55cc.wasm", - "tx_init_validator.wasm": "tx_init_validator.477a5da8ca1586078eae79f8188698c558f1278439610066595ef712c55d9ce7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.08cc5a08c593bd8a67186e39c5d09fff04d262c7234bc314f365bd86fc0e54ed.wasm", - "tx_transfer.wasm": "tx_transfer.6f5954270608aac965a90778f168626c7b4757b78fc15dd2be408ce0ece8e423.wasm", - "tx_unbond.wasm": "tx_unbond.2299b16187c424f6dec074c03d76568c8723b7c47c4621c031a624a926c24df3.wasm", + "tx_from_intent.wasm": "tx_from_intent.fc96b58e4a49608e5a449b519f0470e387ed81995d615a617fdefad0d1985e9e.wasm", + "tx_ibc.wasm": "tx_ibc.1e14034884781c89e441ee545db1a1918914f84159dee4a9c631214aa1f99598.wasm", + "tx_init_account.wasm": "tx_init_account.1cc5bdc6a2f12497a8aad73472021d1e83d2f2a70889f43bcb732ea476cb76fc.wasm", + "tx_init_nft.wasm": "tx_init_nft.fde5d70cfd8921867a9f72be02fe865212a030779de0aba471713e53b820f0e3.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bdbd8a9fdfc68b566795c9302844b5ac6a428f06071bc482c34dffaf3f323c16.wasm", + "tx_init_validator.wasm": "tx_init_validator.3e86adf8a6292275591d998249a3d803f6dce37f4fea920e88e2e806ba2983a6.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.12705b20b935d36113c28ed49fbced9c01b81aca98e9dd5cf071cf85eae2a298.wasm", + "tx_transfer.wasm": "tx_transfer.7b969d9e5405cb4eeb23bc61b6df3b7cd47931686ec22e5325272699f2ad5542.wasm", + "tx_unbond.wasm": "tx_unbond.04721b263f0149b17788f50c1d9264b3625a06ac93fd779f3e58a9aa1afdf824.wasm", "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.401adac389df5c0143fe44ecd87eb3bf2e374d1ef8d1a13217ef3369c48a2a1a.wasm", - "tx_withdraw.wasm": "tx_withdraw.5913f833ad93fd363ba12fdc7f95431044aa5d19eb13d77f337464f95d8aaf62.wasm", - "vp_nft.wasm": "vp_nft.2f58ad62ceb603e25ee0f175b1c12c8499bc15e5bc04e5ea279d1fdfb8392159.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.aa9631f5c9dc4d3a1158956b7561a4f953ee5fc7bea4d3d0b0bd6f14cc04879a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c5943428368cd06b4ceaa0f69eec3316f2e33051054d451bab40ee87379cabd0.wasm", + "tx_withdraw.wasm": "tx_withdraw.26529ab26c7e4fbd331a243d318fc2803fe8be16e4f17cda8f24844a9278ea81.wasm", + "vp_nft.wasm": "vp_nft.040e15d71e03b105738962903f08a4bf7c08de21d210b87590c18c5275022491.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab8a3988cb4613b26a64dbeaea3ba8fbc3af55aeeecda12b945cf60a85de4bb3.wasm", "vp_token.wasm": "vp_token.b517a4a2be9375eab53b885e23a212cfec0dd1f30381168e879f3b67fc6fd199.wasm", - "vp_user.wasm": "vp_user.e412597092c8ec5859b52a08861081e4e321b743efbbda5783eb432a3ff9a2a4.wasm" + "vp_user.wasm": "vp_user.bf7af5c1db9a1bd536ae7ff782ceabb148b8e860a4af55bb8ebaa8fcc128ccd9.wasm" } \ No newline at end of file From 8a5a4dd6608a084b49960915c30ff8c2ba961ad8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 27 Sep 2022 23:16:57 +0100 Subject: [PATCH 0874/1995] Reenable e2e tests which work locally --- tests/src/e2e/ledger_tests.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ca81f006e1..1723ce6b42 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -35,8 +35,6 @@ use crate::{run, run_as}; /// combinations from fresh state, the node starts-up successfully for both a /// validator and non-validator user. #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; @@ -135,8 +133,6 @@ fn test_node_connectivity() -> Result<()> { /// 3. Check that the node detects this /// 4. Check that the node shuts down #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; From da8075facda152e1b373c5ba9200319e9bcd0c53 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 28 Sep 2022 00:08:19 +0100 Subject: [PATCH 0875/1995] Don't run Ethereum bridge during e2e tests --- tests/src/e2e/ledger_tests.rs | 2 -- tests/src/e2e/setup.rs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 1723ce6b42..e30fcc206b 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -169,8 +169,6 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { /// 5. Reset the ledger's state /// 6. Run the ledger again, it should start from fresh state #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..1f6da2baeb 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -666,6 +666,7 @@ where run_cmd .env("ANOMA_LOG", "info") .env("TM_LOG_LEVEL", "info") + .env("ANOMA_LEDGER__ETHEREUM__MODE", "Off") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) .args(&[ From 97c91103416218ca7d8647506f7c6356d4bee97d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 28 Sep 2022 00:44:36 +0100 Subject: [PATCH 0876/1995] Log everything at debug level --- tests/src/e2e/setup.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1f6da2baeb..a780208a56 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -664,8 +664,9 @@ where ); run_cmd - .env("ANOMA_LOG", "info") - .env("TM_LOG_LEVEL", "info") + .env("ANOMA_LOG", "debug") + .env("ANOMA_TM_STDOUT", "true") + .env("TM_LOG_LEVEL", "debug") .env("ANOMA_LEDGER__ETHEREUM__MODE", "Off") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) From 7b9b48244240e7f5ce515c2c4067fefb795ab4b3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 28 Sep 2022 00:45:01 +0100 Subject: [PATCH 0877/1995] Add simple e2e transfer test (to help with debugging) --- tests/src/e2e/ledger_tests.rs | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e30fcc206b..e60ae70a45 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -58,6 +58,55 @@ fn run_ledger() -> Result<()> { Ok(()) } +#[test] +/// In this test we: +/// 1. Run a single genesis validator node +/// 2. Submit a valid token transfer tx +/// 3. Check that the transfer was processed +fn test_simple_transfer() -> Result<()> { + let test = + setup::network(|genesis| setup::add_validators(0, genesis), None)?; + + let args = ["ledger"]; + let mut validator = + run_as!(test, Who::Validator(0), Bin::Node, args, Some(40))?; + validator.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + + let bg_validator = validator.background(); + + // 2. Submit a valid token transfer tx + let tendermint_rpc_addr = 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", + &tendermint_rpc_addr, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // // 3. Check that the transfer was processed + let mut validator = bg_validator.foreground(); + let expected_result = "all VPs accepted transaction"; + validator.exp_string(expected_result)?; + + Ok(()) +} + /// In this test we: /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Submit a valid token transfer tx From 0f431f85128abed43f4fbf05c10fae991da7a0cf Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 13 Oct 2022 17:09:16 +0100 Subject: [PATCH 0878/1995] Remove test_simple_transfer for now --- tests/src/e2e/ledger_tests.rs | 49 ----------------------------------- 1 file changed, 49 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e60ae70a45..e30fcc206b 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -58,55 +58,6 @@ fn run_ledger() -> Result<()> { Ok(()) } -#[test] -/// In this test we: -/// 1. Run a single genesis validator node -/// 2. Submit a valid token transfer tx -/// 3. Check that the transfer was processed -fn test_simple_transfer() -> Result<()> { - let test = - setup::network(|genesis| setup::add_validators(0, genesis), None)?; - - let args = ["ledger"]; - let mut validator = - run_as!(test, Who::Validator(0), Bin::Node, args, Some(40))?; - validator.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - - let bg_validator = validator.background(); - - // 2. Submit a valid token transfer tx - let tendermint_rpc_addr = 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", - &tendermint_rpc_addr, - ]; - let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - - // // 3. Check that the transfer was processed - let mut validator = bg_validator.foreground(); - let expected_result = "all VPs accepted transaction"; - validator.exp_string(expected_result)?; - - Ok(()) -} - /// In this test we: /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Submit a valid token transfer tx From edcbeaf23aef61ba264b0a66fd1caa8637915455 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 13 Oct 2022 17:09:48 +0100 Subject: [PATCH 0879/1995] Use info logs for the moment --- tests/src/e2e/setup.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index a780208a56..3f85304eef 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -664,9 +664,9 @@ where ); run_cmd - .env("ANOMA_LOG", "debug") + .env("ANOMA_LOG", "info") .env("ANOMA_TM_STDOUT", "true") - .env("TM_LOG_LEVEL", "debug") + .env("TM_LOG_LEVEL", "info") .env("ANOMA_LEDGER__ETHEREUM__MODE", "Off") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) From 4510a67cfb36991bbc6f6d3a934586ebe6e33dd4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 13 Oct 2022 17:10:20 +0100 Subject: [PATCH 0880/1995] Only re-enable the run ledger test --- tests/src/e2e/ledger_tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e30fcc206b..90e018a738 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -133,6 +133,8 @@ fn test_node_connectivity() -> Result<()> { /// 3. Check that the node detects this /// 4. Check that the node shuts down #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; @@ -169,6 +171,8 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { /// 5. Reset the ledger's state /// 6. Run the ledger again, it should start from fresh state #[test] +#[ignore] +// TODO(namada#418): re-enable once working again fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; From 7e0a931cc0bca5d2ef47d8d2398c81d3a461d7f3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 13 Oct 2022 18:12:38 +0100 Subject: [PATCH 0881/1995] Revert "ci: added tendermint ad825dcadbd4b98c3f91ce5a711e4fb36a69c377" This reverts commit bb7de0ef55af606235e6a020450b9ef4585c44d9. --- .github/workflows/build-tendermint.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 91a5259dac..5d7224abb7 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -24,9 +24,6 @@ jobs: - name: tendermint-unreleased repository: heliaxdev/tendermint tendermint_version: 559fb33ff9b27503ce7ac1c7d8589fe1d8b3e900 - - name: tendermint-unreleased - repository: heliaxdev/tendermint - tendermint_version: ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 steps: - name: Build ${{ matrix.make.name }} From a144811357861df88a3633088db9db56b72e9de5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 10:26:05 +0100 Subject: [PATCH 0882/1995] Begin event log and add queries code --- apps/Cargo.toml | 1 + apps/src/lib/node/ledger/events.rs | 2 + apps/src/lib/node/ledger/events/log.rs | 6 + .../node/ledger/events/log/dumb_queries.rs | 155 ++++++++++++++++++ 4 files changed, 164 insertions(+) create mode 100644 apps/src/lib/node/ledger/events/log.rs create mode 100644 apps/src/lib/node/ledger/events/log/dumb_queries.rs diff --git a/apps/Cargo.toml b/apps/Cargo.toml index be78e08479..79fd0c4683 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,6 +92,7 @@ file-lock = "2.0.2" futures = "0.3" hex = "0.4.3" itertools = "0.10.1" +lazy_static = "1.4.0" libc = "0.2.97" libloading = "0.7.2" libp2p = "0.38.0" diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 3adefc59d7..ff356ba52e 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -1,3 +1,5 @@ +pub mod log; + use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{self, Display}; diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs new file mode 100644 index 0000000000..6c54d63757 --- /dev/null +++ b/apps/src/lib/node/ledger/events/log.rs @@ -0,0 +1,6 @@ +//! A log to store events emitted by `FinalizeBlock` calls in the ledger. +//! +//! The log is flushed every other `N` block heights, or every other `E` +//! `FinalizeBlock` events, where `N` and `E` are configurable parameters. + +pub mod dumb_queries; diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs new file mode 100644 index 0000000000..ef96193521 --- /dev/null +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -0,0 +1,155 @@ +//! Silly simple Tendermint query parser. +//! +//! This parser will only work with simple queries of the form: +//! +//! ```text +//! tm.event='NewBlock' AND .<$attr>='<$value>' +//! ``` + +use lazy_static::lazy_static; +use regex::Regex; + +use crate::node::ledger::events::{Event, EventType}; + +/// Regular expression used to parse Tendermint queries. +const QUERY_PARSING_REGEX_STR: &str = + r"^tm\.event='NewBlock' AND (accepted|applied)\.([\w_]+)='([^']+)'$"; + +lazy_static! { + /// Compiled regular expression used to parse Tendermint queries. + static ref QUERY_PARSING_REGEX: Regex = Regex::new(QUERY_PARSING_REGEX_STR).unwrap(); +} + +/// A [`QueryMatcher`] verifies if a Namada event matches a +/// given Tendermint query. +#[derive(Debug, Clone)] +pub struct QueryMatcher<'q> { + event_type: EventType, + attr: String, + value: &'q str, +} + +impl<'q> QueryMatcher<'q> { + /// Checks if this [`QueryMatcher`] validates the + /// given [`Event`]. + pub fn matches(&self, event: &Event) -> bool { + event.event_type == self.event_type + && event + .attributes + .get(&self.attr) + .map(|value| value == self.value) + .unwrap_or_default() + } + + /// Parses a Tendermint-like events query. + pub fn parse(query: &'q str) -> Option { + let captures = QUERY_PARSING_REGEX.captures(query)?; + + let event_type = match captures.get(1)?.as_str() { + "accepted" => EventType::Accepted, + "applied" => EventType::Applied, + // NOTE: the regex only matches `accepted` + // and `applied` + _ => unreachable!(), + }; + let attr = captures.get(2)?.as_str().to_string(); + let value = captures.get(3)?.as_str(); + + Some(Self { + event_type, + attr, + value, + }) + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use proptest::string::{string_regex, RegexGeneratorStrategy}; + + use super::*; + use crate::node::ledger::events::EventLevel; + + /// Returns a proptest strategy that yields Tendermint-like queries. + fn tm_query_strat() -> RegexGeneratorStrategy { + string_regex( + // slice out the string init and end specifiers + &QUERY_PARSING_REGEX_STR[1..QUERY_PARSING_REGEX_STR.len() - 1], + ) + .unwrap() + } + + proptest! { + /// Test if we can parse a Tendermint query, feeding [`QueryMatcher::parse`] + /// random input data. + #[test] + fn test_random_inputs(query in tm_query_strat()) { + QueryMatcher::parse(&query).unwrap(); + } + } + + /// Test if we parse a correct Tendermint query. + #[test] + fn test_parse_correct_tm_query() { + let q = QueryMatcher::parse( + "tm.event='NewBlock' AND applied.hash='123456'", + ) + .unwrap(); + + assert_eq!(q.event_type, EventType::Applied); + assert_eq!(&q.attr, "hash"); + assert_eq!(q.value, "123456"); + + let q = QueryMatcher::parse( + "tm.event='NewBlock' AND accepted.hash='DEADBEEF'", + ) + .unwrap(); + + assert_eq!(q.event_type, EventType::Accepted); + assert_eq!(&q.attr, "hash"); + assert_eq!(q.value, "DEADBEEF"); + } + + /// Test if query matching is working as expected. + #[test] + fn test_tm_query_matching() { + let matcher = QueryMatcher { + event_type: EventType::Accepted, + attr: "hash".to_string(), + value: "DEADBEEF", + }; + + let tests = { + let event_1 = Event { + event_type: EventType::Accepted, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), "DEADBEEF".to_string()); + attrs + }, + }; + let accepted_1 = true; + + let event_2 = Event { + event_type: EventType::Applied, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), "DEADBEEF".to_string()); + attrs + }, + }; + let accepted_2 = false; + + [(event_1, accepted_1), (event_2, accepted_2)] + }; + + for (ref ev, status) in tests { + if matcher.matches(ev) != status { + panic!("Test failed"); + } + } + } +} From 3063ad8b0dbd40a074ce38b7081154f85b90e4ef Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 0883/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 17 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 9 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 8 files changed, 237 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f848f69185..84eb71b43e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,6 +4330,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index dc338a38bc..84f61e9850 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,13 +5,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::ethereum_events::EthAddress; use namada::types::eth_bridge_pool::{ @@ -20,7 +20,8 @@ use namada::types::eth_bridge_pool::{ use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -336,13 +337,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -392,12 +405,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -410,22 +418,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -440,6 +440,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -841,10 +842,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1008,4 +1019,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..7c2cfbec2b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,6 +104,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index cdea60eae0..7e49c197b3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } @@ -64,14 +69,8 @@ pub struct BridgePoolTree { impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool - pub fn new(store: BTreeSet) -> Self { - let mut tree = Self { - root: KeccakHash::default(), - store, - }; - let root = tree.compute_root(); - tree.root = root; - tree + pub fn new(root: KeccakHash, store: BTreeSet) -> Self { + Self { root, store } } /// Parse the key to ensure it is of the correct type. diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index bd27e83857..92fbbcb306 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index a8056a0dfc..0c1357af93 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -430,15 +430,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl Into, ) -> Result<(u64, i64)> { + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); self.block.tree.update(key, value.clone())?; - let len = value.as_ref().len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index b33fee72ff..85f2bd99b1 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -43,6 +44,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -88,6 +90,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 08ee160662..3d20300a41 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -245,6 +245,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -267,6 +268,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From df578fc8c82ff933252754c3ebbe6d56ff8984d5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 13:15:40 +0100 Subject: [PATCH 0884/1995] Derive Eq and PartialEq on event types --- apps/src/lib/node/ledger/events.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index ff356ba52e..24c5a947f1 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -15,7 +15,7 @@ use crate::facade::tendermint_proto::abci::EventAttribute; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum EventLevel { Block, Tx, @@ -23,7 +23,7 @@ pub enum EventLevel { /// Custom events that can be queried from Tendermint /// using a websocket client -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Event { pub event_type: EventType, pub level: EventLevel, @@ -31,7 +31,7 @@ pub struct Event { } /// The two types of custom events we currently use -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum EventType { // The transaction was accepted to be included in a block Accepted, From 66bee3b226e49fc8836c75adad118462bb7df545 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 13:15:57 +0100 Subject: [PATCH 0885/1995] Update Cargo.lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index c7feeb1de8..4e9e4be514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4379,6 +4379,7 @@ dependencies = [ "git2", "hex", "itertools 0.10.3", + "lazy_static 1.4.0", "libc", "libloading", "libp2p", From be690523c185a830ab5863de975656b60c8b9443 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 14:54:18 +0100 Subject: [PATCH 0886/1995] Implement the event log --- Cargo.lock | 10 +++ apps/Cargo.toml | 1 + apps/src/lib/node/ledger/events/log.rs | 95 +++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e9e4be514..b47e2fce1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1157,6 +1157,15 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check 0.9.4", +] + [[package]] name = "clang-sys" version = "1.3.3" @@ -4362,6 +4371,7 @@ dependencies = [ "byteorder", "bytes 1.1.0", "cargo-watch", + "circular-queue", "clap 3.0.0-beta.2", "clarity", "color-eyre", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 79fd0c4683..1949fd9e30 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -75,6 +75,7 @@ blake2b-rs = "0.2.0" borsh = "0.9.0" byte-unit = "4.0.13" byteorder = "1.4.2" +circular-queue = "0.2.6" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} clarity = "0.5.1" diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 6c54d63757..29303861be 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -1,6 +1,97 @@ //! A log to store events emitted by `FinalizeBlock` calls in the ledger. //! -//! The log is flushed every other `N` block heights, or every other `E` -//! `FinalizeBlock` events, where `N` and `E` are configurable parameters. +//! The log can only hold `N` events at a time, where `N` is a configurable +//! parameter. If the log is holding `N` events, and a new event is logged, +//! old events are pruned. + +use std::default::Default; + +use circular_queue::CircularQueue; + +use crate::node::ledger::events::Event; pub mod dumb_queries; + +/// Errors specific to [`EventLog`] operations. +#[derive(Debug)] +pub enum Error { + /// We failed to parse a Tendermint query. + InvalidQuery, +} + +/// Parameters to configure the pruning of the event log. +pub struct Params { + /// Soft limit on the maximum number of events the event log can hold. + /// + /// If the number of events in the log exceeds this value, the log + /// will be pruned. + pub max_log_events: usize, +} + +impl Default for Params { + fn default() -> Self { + // TODO: tune the default params + Self { + max_log_events: 50000, + } + } +} + +/// Represents a log of [`Event`] instances emitted by +/// `FinalizeBlock` calls, in the ledger. +#[derive(Debug)] +pub struct EventLog { + queue: CircularQueue, +} + +impl EventLog { + /// Return a new event log. + pub fn new(params: Params) -> Self { + Self { + queue: CircularQueue::with_capacity(params.max_log_events), + } + } + + /// Log a new batch of events into the event log. + pub fn log_events<'e, E>(&mut self, events: E) + where + E: IntoIterator + 'e, + { + let mut num_entries = 0; + for event in events.into_iter().cloned() { + self.queue.push(event); + num_entries += 1; + } + tracing::debug!(num_entries, "Added new entries to the event log"); + } + + /// Returns a new iterator over this [`EventLog`], if the + /// given `query` is valid. + pub fn try_iter<'query, 'log>( + &'log self, + query: &'query str, + ) -> Result + 'query, Error> + where + // the log should outlive the query + 'log: 'query, + { + let matcher = + dumb_queries::QueryMatcher::parse(query).ok_or_else(|| { + tracing::debug!(query, "Invalid Tendermint query"); + Error::InvalidQuery + })?; + Ok(self.iter_with_matcher(matcher)) + } + + /// Just like [`EventLog::try_iter`], but uses a pre-compiled + /// query matcher. + #[inline] + pub fn iter_with_matcher<'query, 'log: 'query>( + &'log self, + matcher: dumb_queries::QueryMatcher<'query>, + ) -> impl Iterator + 'query { + self.queue + .iter() + .filter(move |event| matcher.matches(event)) + } +} From e66c55565b6798da0a867eb8fb042059a8a75b6d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 14:55:20 +0100 Subject: [PATCH 0887/1995] Add some param derives --- apps/src/lib/node/ledger/events/log.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 29303861be..9220a96778 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -20,6 +20,7 @@ pub enum Error { } /// Parameters to configure the pruning of the event log. +#[derive(Debug, Copy, Clone)] pub struct Params { /// Soft limit on the maximum number of events the event log can hold. /// From 05dd39f3b5da2645f6e023c90dbff56ab804222d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 15:09:06 +0100 Subject: [PATCH 0888/1995] Test writing events to the log --- apps/src/lib/node/ledger/events/log.rs | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 9220a96778..4c1c55e35c 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -96,3 +96,61 @@ impl EventLog { .filter(move |event| matcher.matches(event)) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::ledger::events::{EventLevel, EventType}; + + /// Return a vector of mock `FinalizeBlock` events. + fn mock_tx_events(hash: &str) -> Vec { + let event_1 = Event { + event_type: EventType::Accepted, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), hash.to_string()); + attrs + }, + }; + let event_2 = Event { + event_type: EventType::Applied, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), hash.to_string()); + attrs + }, + }; + vec![event_1, event_2] + } + + /// Test adding a couple of events to the event log, and + /// reading those events back. + #[test] + fn test_log_add() { + const NUM_HEIGHTS: usize = 4; + + let mut log = EventLog::new(Params::default()); + + // send events to the logger + let events = mock_tx_events("DEADBEEF"); + + for _ in 0..NUM_HEIGHTS { + log.log_events(&events); + } + + // inspect log + let events_in_log: Vec<_> = log + .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") + .unwrap() + .cloned() + .collect(); + + assert_eq!(events_in_log.len(), NUM_HEIGHTS as usize); + + for i in 0..NUM_HEIGHTS { + assert_eq!(events[0], events_in_log[i]); + } + } +} From 0a2d232eb892783a640bf157b5798549217ff14a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 15:34:21 +0100 Subject: [PATCH 0889/1995] Test log pruning --- apps/src/lib/node/ledger/events/log.rs | 61 +++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 4c1c55e35c..b6493ed64f 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -133,7 +133,7 @@ mod tests { let mut log = EventLog::new(Params::default()); - // send events to the logger + // add new events to the log let events = mock_tx_events("DEADBEEF"); for _ in 0..NUM_HEIGHTS { @@ -147,10 +147,67 @@ mod tests { .cloned() .collect(); - assert_eq!(events_in_log.len(), NUM_HEIGHTS as usize); + assert_eq!(events_in_log.len(), NUM_HEIGHTS); for i in 0..NUM_HEIGHTS { assert_eq!(events[0], events_in_log[i]); } } + + /// Test pruning old events from the log. + #[test] + fn test_log_prune() { + const LOG_CAP: usize = 4; + + // log cap has to be a multiple of two + // for this test + assert!(LOG_CAP & 1 == 0); + + const MATCHED_EVENTS: usize = LOG_CAP / 2; + + let mut log = EventLog::new(Params { + max_log_events: LOG_CAP, + }); + + // completely fill the log with events + // + // `mock_tx_events` returns 2 events, so + // we do `LOG_CAP / 2` iters to fill the log + let events = mock_tx_events("DEADBEEF"); + assert_eq!(events.len(), 2); + + for _ in 0..(LOG_CAP / 2) { + log.log_events(&events); + } + + // inspect log - it should be full + let events_in_log: Vec<_> = log + .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") + .unwrap() + .cloned() + .collect(); + + assert_eq!(events_in_log.len(), MATCHED_EVENTS); + + for i in 0..MATCHED_EVENTS { + assert_eq!(events[0], events_in_log[i]); + } + + // add a new APPLIED event to the log, + // pruning the first ACCEPTED event we added + log.log_events(Some(&events[1])); + + let events_in_log: Vec<_> = log + .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") + .unwrap() + .cloned() + .collect(); + + const ACCEPTED_EVENTS: usize = MATCHED_EVENTS - 1; + assert_eq!(events_in_log.len(), ACCEPTED_EVENTS); + + for i in 0..ACCEPTED_EVENTS { + assert_eq!(events[0], events_in_log[i]); + } + } } From 388a3a81036ff49a11941cc7444e521c9dca3f86 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 15:42:35 +0100 Subject: [PATCH 0890/1995] Bow down to clippy --- apps/src/lib/node/ledger/events/log.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index b6493ed64f..344e767fce 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -149,8 +149,8 @@ mod tests { assert_eq!(events_in_log.len(), NUM_HEIGHTS); - for i in 0..NUM_HEIGHTS { - assert_eq!(events[0], events_in_log[i]); + for event in events_in_log { + assert_eq!(events[0], event); } } @@ -161,7 +161,9 @@ mod tests { // log cap has to be a multiple of two // for this test - assert!(LOG_CAP & 1 == 0); + if LOG_CAP & 1 != 0 { + panic!(); + } const MATCHED_EVENTS: usize = LOG_CAP / 2; @@ -189,8 +191,8 @@ mod tests { assert_eq!(events_in_log.len(), MATCHED_EVENTS); - for i in 0..MATCHED_EVENTS { - assert_eq!(events[0], events_in_log[i]); + for event in events_in_log { + assert_eq!(events[0], event); } // add a new APPLIED event to the log, @@ -206,8 +208,8 @@ mod tests { const ACCEPTED_EVENTS: usize = MATCHED_EVENTS - 1; assert_eq!(events_in_log.len(), ACCEPTED_EVENTS); - for i in 0..ACCEPTED_EVENTS { - assert_eq!(events[0], events_in_log[i]); + for event in events_in_log { + assert_eq!(events[0], event); } } } From 583b21d53dbae0881a8ad755f217d5d31e038d9b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 15:47:56 +0100 Subject: [PATCH 0891/1995] Add another check to the test --- apps/src/lib/node/ledger/events/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 344e767fce..0f6f969dab 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -161,7 +161,7 @@ mod tests { // log cap has to be a multiple of two // for this test - if LOG_CAP & 1 != 0 { + if LOG_CAP < 2 || LOG_CAP & 1 != 0 { panic!(); } From 2dbad8130fb1d04a8a45b0024423cc47c7ae3d13 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 15:57:43 +0100 Subject: [PATCH 0892/1995] Don't remove new Tendermint --- .github/workflows/build-tendermint.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 5d7224abb7..07de2b2614 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -12,7 +12,6 @@ permissions: env: GIT_LFS_SKIP_SMUDGE: 1 - jobs: tendermint: runs-on: ${{ matrix.os }} @@ -24,6 +23,9 @@ jobs: - name: tendermint-unreleased repository: heliaxdev/tendermint tendermint_version: 559fb33ff9b27503ce7ac1c7d8589fe1d8b3e900 + - name: tendermint-unreleased + repository: heliaxdev/tendermint + tendermint_version: ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 steps: - name: Build ${{ matrix.make.name }} From c4294761482ccae19547e2534494629f5abdf170 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 15:59:02 +0100 Subject: [PATCH 0893/1995] Don't change whitespace --- .github/workflows/build-tendermint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 07de2b2614..91a5259dac 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -12,6 +12,7 @@ permissions: env: GIT_LFS_SKIP_SMUDGE: 1 + jobs: tendermint: runs-on: ${{ matrix.os }} From 543e483b78578aac2533d285ffc1d649a5f6a435 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 0894/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/mod.rs | 13 ++++- apps/src/lib/node/ledger/shell/queries.rs | 19 +++---- shared/Cargo.toml | 1 - shared/src/types/key/secp256k1.rs | 60 ++++++++++++++++------- 5 files changed, 61 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84eb71b43e..f848f69185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,7 +4330,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 399c6b14b6..62d3b63af0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -857,10 +857,19 @@ mod test_utils { sig_bytes[0] = sig_bytes[0].wrapping_add(1); common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + common::Signature::Secp256k1(secp256k1::Signature( + ref sig, + ref recovery_id, + )) => { let mut sig_bytes = sig.serialize(); + let recovery_id_bytes = recovery_id.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) + let bytes: [u8; 65] = + [sig_bytes.as_slice(), [recovery_id_bytes].as_slice()] + .concat() + .try_into() + .unwrap(); + common::Signature::Secp256k1((&bytes).try_into().unwrap()) } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 84f61e9850..a413f3739d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -13,10 +13,10 @@ use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; -use namada::types::ethereum_events::EthAddress; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -1044,7 +1044,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1081,7 +1081,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1089,7 +1089,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1097,7 +1097,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1141,7 +1141,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1155,14 +1155,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7c2cfbec2b..ccb4a3752d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,7 +104,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 96b892fdbf..7ab6172774 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -266,7 +267,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(pub libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature, pub RecoveryId); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; @@ -304,6 +305,7 @@ impl Serialize for Signature { for elem in &arr[..] { seq.serialize_element(elem)?; } + seq.serialize_element(&self.1.serialize())?; seq.end() } } @@ -316,7 +318,7 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( @@ -325,13 +327,13 @@ impl<'de> Deserialize<'de> for Signature { )) } - fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + fn visit_seq(self, mut seq: A) -> Result<[u8; 65], A::Error> where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -341,23 +343,34 @@ impl<'de> Deserialize<'de> for Signature { } let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE, + libsecp256k1::util::SIGNATURE_SIZE + 1, ByteArrayVisitor, )?; - let sig = libsecp256k1::Signature::parse_standard(&arr_res) + let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); + let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); - Ok(Signature(sig.unwrap())) + Ok(Signature( + sig.unwrap(), + RecoveryId::parse(arr_res[64]).map_err(Error::custom)?, + )) } } impl BorshDeserialize for Signature { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first + let (sig_bytes, recovery_id) = BorshDeserialize::deserialize(buf)?; + Ok(Signature( - libsecp256k1::Signature::parse_standard( - &(BorshDeserialize::deserialize(buf)?), - ) - .map_err(|e| { + libsecp256k1::Signature::parse_standard(&sig_bytes).map_err( + |e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + }, + )?, + RecoveryId::parse(recovery_id).map_err(|e| { std::io::Error::new( ErrorKind::InvalidInput, format!("Error decoding secp256k1 signature: {}", e), @@ -369,7 +382,10 @@ impl BorshDeserialize for Signature { impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - BorshSerialize::serialize(&self.0.serialize(), writer) + BorshSerialize::serialize( + &(self.0.serialize(), self.1.serialize()), + writer, + ) } } @@ -405,12 +421,19 @@ impl PartialOrd for Signature { } } -impl TryFrom<&[u8; 64]> for Signature { +impl TryFrom<&[u8; 65]> for Signature { type Error = ParseSignatureError; - fn try_from(sig: &[u8; 64]) -> Result { - libsecp256k1::Signature::parse_standard(sig) - .map(Self) + fn try_from(sig: &[u8; 65]) -> Result { + let sig_bytes = sig[..64].try_into().unwrap(); + let recovery_id = RecoveryId::parse(sig[64]).map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + })?; + libsecp256k1::Signature::parse_standard(&sig_bytes) + .map(|sig| Self(sig, recovery_id)) .map_err(|err| { ParseSignatureError::InvalidEncoding(std::io::Error::new( ErrorKind::Other, @@ -467,7 +490,8 @@ impl super::SigScheme for SigScheme { 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) + let (sig, recovery_id) = libsecp256k1::sign(&message, &keypair.0); + Signature(sig, recovery_id) } } From b7fedc3b6c317051c0733bcaba5f62a68c62375d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 16:14:34 +0100 Subject: [PATCH 0895/1995] Update tendermint-rs and ibc-rs dependencies --- Cargo.lock | 48 +++++++++++++-------------- Cargo.toml | 16 ++++----- wasm/checksums.json | 30 ++++++++--------- wasm/tx_template/Cargo.lock | 12 +++---- wasm/tx_template/Cargo.toml | 12 +++---- wasm/vp_template/Cargo.lock | 12 +++---- wasm/vp_template/Cargo.toml | 12 +++---- wasm/wasm_source/Cargo.lock | 12 +++---- wasm/wasm_source/Cargo.toml | 12 +++---- wasm_for_tests/wasm_source/Cargo.lock | 12 +++---- wasm_for_tests/wasm_source/Cargo.toml | 12 +++---- 11 files changed, 95 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f848f69185..5fcc012880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2893,12 +2893,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2909,10 +2909,10 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint 0.23.6", - "tendermint-light-client-verifier 0.23.6", - "tendermint-proto 0.23.6", - "tendermint-testgen 0.23.6", + "tendermint 0.23.5", + "tendermint-light-client-verifier 0.23.5", + "tendermint-proto 0.23.5", + "tendermint-testgen 0.23.5", "time 0.3.9", "tracing 0.1.35", ] @@ -2920,12 +2920,12 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2936,10 +2936,10 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint 0.23.5", - "tendermint-light-client-verifier 0.23.5", - "tendermint-proto 0.23.5", - "tendermint-testgen 0.23.5", + "tendermint 0.23.6", + "tendermint-light-client-verifier 0.23.6", + "tendermint-proto 0.23.6", + "tendermint-testgen 0.23.6", "time 0.3.9", "tracing 0.1.35", ] @@ -2947,27 +2947,27 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "base64 0.13.0", "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", - "tendermint-proto 0.23.6", + "tendermint-proto 0.23.5", ] [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64 0.13.0", "bytes 1.1.0", "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", - "tendermint-proto 0.23.5", + "tendermint-proto 0.23.6", ] [[package]] @@ -4311,10 +4311,10 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", - "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436)", + "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", + "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "itertools 0.10.3", "libsecp256k1 0.7.0", @@ -6939,7 +6939,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes 1.1.0", @@ -6980,7 +6980,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "flex-error", "serde 1.0.137", @@ -7006,7 +7006,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", @@ -7035,7 +7035,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes 1.1.0", "flex-error", @@ -7085,7 +7085,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "async-tungstenite", @@ -7133,7 +7133,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/Cargo.toml b/Cargo.toml index ccbb2c35de..5c499bf6d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,16 +33,16 @@ borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git # borsh-schema-derive-internal = {path = "../borsh-rs/borsh-schema-derive-internal"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} # patched to a commit on the `eth-bridge-integration` branch of our fork tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "fcc0014d0bda707109901abfa1b2f782d242f082"} diff --git a/wasm/checksums.json b/wasm/checksums.json index 0ccb909760..2634122c96 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.fc96b58e4a49608e5a449b519f0470e387ed81995d615a617fdefad0d1985e9e.wasm", - "tx_ibc.wasm": "tx_ibc.1e14034884781c89e441ee545db1a1918914f84159dee4a9c631214aa1f99598.wasm", - "tx_init_account.wasm": "tx_init_account.1cc5bdc6a2f12497a8aad73472021d1e83d2f2a70889f43bcb732ea476cb76fc.wasm", - "tx_init_nft.wasm": "tx_init_nft.fde5d70cfd8921867a9f72be02fe865212a030779de0aba471713e53b820f0e3.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bdbd8a9fdfc68b566795c9302844b5ac6a428f06071bc482c34dffaf3f323c16.wasm", - "tx_init_validator.wasm": "tx_init_validator.3e86adf8a6292275591d998249a3d803f6dce37f4fea920e88e2e806ba2983a6.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.12705b20b935d36113c28ed49fbced9c01b81aca98e9dd5cf071cf85eae2a298.wasm", - "tx_transfer.wasm": "tx_transfer.7b969d9e5405cb4eeb23bc61b6df3b7cd47931686ec22e5325272699f2ad5542.wasm", - "tx_unbond.wasm": "tx_unbond.04721b263f0149b17788f50c1d9264b3625a06ac93fd779f3e58a9aa1afdf824.wasm", + "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", + "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", + "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", + "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", + "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", + "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", + "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c5943428368cd06b4ceaa0f69eec3316f2e33051054d451bab40ee87379cabd0.wasm", - "tx_withdraw.wasm": "tx_withdraw.26529ab26c7e4fbd331a243d318fc2803fe8be16e4f17cda8f24844a9278ea81.wasm", - "vp_nft.wasm": "vp_nft.040e15d71e03b105738962903f08a4bf7c08de21d210b87590c18c5275022491.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ab8a3988cb4613b26a64dbeaea3ba8fbc3af55aeeecda12b945cf60a85de4bb3.wasm", - "vp_token.wasm": "vp_token.b517a4a2be9375eab53b885e23a212cfec0dd1f30381168e879f3b67fc6fd199.wasm", - "vp_user.wasm": "vp_user.bf7af5c1db9a1bd536ae7ff782ceabb148b8e860a4af55bb8ebaa8fcc128ccd9.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", + "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", + "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", + "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", + "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 78c2272f93..64bad284a2 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1060,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1087,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64", "bytes", @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", @@ -2468,7 +2468,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 49828ed0bd..0c4a29c8b1 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -26,14 +26,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index b9dcdc3d5d..3a3058df34 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1060,7 +1060,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1087,7 +1087,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64", "bytes", @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2456,7 +2456,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", @@ -2468,7 +2468,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index e2eadbd977..97ec78b64a 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -26,14 +26,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 3f0a675b4d..2b891bf4e1 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64", "bytes", @@ -2457,7 +2457,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2485,7 +2485,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", @@ -2497,7 +2497,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2514,7 +2514,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index ffd9730962..ddbc38276b 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -58,14 +58,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} [profile.release] # smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index e31959dbef..f0cabb9569 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1070,7 +1070,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.14.0" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "bytes", "derive_more", @@ -1097,7 +1097,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.17.1" -source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=451ff75591d9de48b7147a651858c3daeb5e2436#451ff75591d9de48b7147a651858c3daeb5e2436" +source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64", "bytes", @@ -2459,7 +2459,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", "bytes", @@ -2487,7 +2487,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "derive_more", "flex-error", @@ -2499,7 +2499,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "bytes", "flex-error", @@ -2516,7 +2516,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.6" -source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b#bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b" +source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 1033271a6d..0436d8b44b 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -40,14 +40,14 @@ borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} # patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "bc0b6ac47b3bfc1ee8b944341b654b867b3b5d0b"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} # patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "451ff75591d9de48b7147a651858c3daeb5e2436"} +ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} [dev-dependencies] namada_tests = {path = "../../tests"} From a307a4c2aee5efa41eebefc0a68614b4ec27f29c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 16:06:46 +0100 Subject: [PATCH 0896/1995] Run e2e tests that will pass in CI --- tests/src/e2e/eth_bridge_tests.rs | 2 -- tests/src/e2e/ledger_tests.rs | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index aa5f657659..b276dd409e 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -19,8 +19,6 @@ fn storage_key(path: &str) -> String { } #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn everything() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 90e018a738..f726c7c673 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -63,8 +63,6 @@ fn run_ledger() -> Result<()> { /// 2. Submit a valid token transfer tx /// 3. Check that all the nodes processed the tx with the same result #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes let test = @@ -133,8 +131,6 @@ fn test_node_connectivity() -> Result<()> { /// 3. Check that the node detects this /// 4. Check that the node shuts down #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; @@ -171,8 +167,6 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { /// 5. Reset the ledger's state /// 6. Run the ledger again, it should start from fresh state #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; @@ -240,8 +234,6 @@ fn run_ledger_load_state_and_reset() -> Result<()> { /// 6. Query token balance /// 7. Query the raw bytes of a storage key #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn ledger_txs_and_queries() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; @@ -414,8 +406,6 @@ fn ledger_txs_and_queries() -> Result<()> { /// 4. Restart the ledger /// 5. Submit and invalid transactions (malformed) #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn invalid_transactions() -> Result<()> { let test = setup::single_node_net()?; @@ -922,8 +912,6 @@ fn pos_init_validator() -> Result<()> { /// 1. Run the ledger node with 10s consensus timeout /// 2. Spawn threads each submitting token transfer tx #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn ledger_many_txs_in_a_block() -> Result<()> { let test = Arc::new(setup::network( |genesis| genesis, From 698ebac0b922e463c8eb9635792777d4a38392f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 12:29:03 +0100 Subject: [PATCH 0897/1995] Update apps/src/lib/node/ledger/events/log.rs Co-authored-by: James --- apps/src/lib/node/ledger/events/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 0f6f969dab..494ff83829 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -93,7 +93,7 @@ impl EventLog { ) -> impl Iterator + 'query { self.queue .iter() - .filter(move |event| matcher.matches(event)) + .filter(move |&event| matcher.matches(event)) } } From abd19406ac43dc9482612504a1c28524888ca404 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 12:49:42 +0100 Subject: [PATCH 0898/1995] Make event cloning explicit --- apps/src/lib/node/ledger/events/log.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 494ff83829..f31b7734c5 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -54,12 +54,12 @@ impl EventLog { } /// Log a new batch of events into the event log. - pub fn log_events<'e, E>(&mut self, events: E) + pub fn log_events(&mut self, events: E) where - E: IntoIterator + 'e, + E: IntoIterator, { let mut num_entries = 0; - for event in events.into_iter().cloned() { + for event in events.into_iter() { self.queue.push(event); num_entries += 1; } @@ -137,7 +137,7 @@ mod tests { let events = mock_tx_events("DEADBEEF"); for _ in 0..NUM_HEIGHTS { - log.log_events(&events); + log.log_events(events.clone()); } // inspect log @@ -179,7 +179,7 @@ mod tests { assert_eq!(events.len(), 2); for _ in 0..(LOG_CAP / 2) { - log.log_events(&events); + log.log_events(events.clone()); } // inspect log - it should be full @@ -197,7 +197,7 @@ mod tests { // add a new APPLIED event to the log, // pruning the first ACCEPTED event we added - log.log_events(Some(&events[1])); + log.log_events(Some(events[1].clone())); let events_in_log: Vec<_> = log .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") From 83c887cbdcf21e3723e0a4699a6e3987f20a631b Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 15:20:59 +0200 Subject: [PATCH 0899/1995] [feat]: Fixed secp256k key signatures --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 13 +++++++--- shared/src/types/key/dkg_session_keys.rs | 7 +++-- shared/src/types/key/ed25519.rs | 13 +++++++--- shared/src/types/key/mod.rs | 27 ++++++++++++++++--- shared/src/types/key/secp256k1.rs | 27 +++++++++++++------ wasm/checksums.json | 33 ++++++++++++------------ wasm/tx_template/Cargo.lock | 7 +++++ wasm/vp_template/Cargo.lock | 7 +++++ wasm/wasm_source/Cargo.lock | 7 +++++ wasm_for_tests/wasm_source/Cargo.lock | 7 +++++ 12 files changed, 113 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f848f69185..7460c0c2aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4303,6 +4303,7 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 59196e1ff0..633367053c 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -70,7 +71,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -78,7 +79,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -196,7 +199,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -204,7 +207,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index dbcf9fe04c..052461de9a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -106,7 +107,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -114,7 +115,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -203,7 +206,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -211,7 +214,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1c5ac85558..5a16838455 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -7,6 +7,7 @@ use std::hash::Hash; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -85,7 +86,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -96,7 +97,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -107,7 +108,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] @@ -356,6 +357,26 @@ 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 + } + common::PublicKey::Secp256k1(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + +/// 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 { + HEXUPPER.encode(raw_hash.as_ref()) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 7ab6172774..9c8825dd98 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -115,7 +116,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -123,7 +124,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -245,7 +248,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -253,7 +256,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } @@ -396,10 +401,16 @@ impl BorshSchema for Signature { 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 }; + // Encoded as `([u8; SIGNATURE_SIZE], u8)` + let signature = + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::declaration(); + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::add_definitions_recursively( + definitions, + ); + let recovery = "u8".into(); + let definition = borsh::schema::Definition::Tuple { + elements: vec![signature, recovery], + }; definitions.insert(Self::declaration(), definition); } diff --git a/wasm/checksums.json b/wasm/checksums.json index 4754de3ff7..1e108d527b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { + "tx_bond.wasm": "tx_bond.be941c5af5bb2de3615c684b3b80d6231a7f44773dfcb7188a60e27ec0dcdaf7.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", - "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", - "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", - "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", - "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", - "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", - "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", - "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", - "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", - "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", - "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" + "tx_from_intent.wasm": "tx_from_intent.554092b457f5cfac35be30f49c0ea32018dc6812d4d37ef7bfc93d573b831577.wasm", + "tx_ibc.wasm": "tx_ibc.b3eefca76e4972686505861b731a30a19516a960490c898c910907527a32af4f.wasm", + "tx_init_account.wasm": "tx_init_account.c09ee3f72062a0e013f434d4d2a050f38daffd61a5c092ded5f3b0933f6f479c.wasm", + "tx_init_nft.wasm": "tx_init_nft.f05a2d71781c2f26353e5ec316521ccd7cf9ec9c9acff9c20b563dfed5c4ca92.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6f39045b82b32167132cf4317d366476541ad5a29f6e20c69a89602ef0f711d5.wasm", + "tx_init_validator.wasm": "tx_init_validator.f1dac763153151f2194eb699945b862be2f2c0a4c5e6b0d0029e7183e3f6a20b.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.4f9410360d536605e3335829126911353283eedc227f35e4da289ce7a93c27df.wasm", + "tx_transfer.wasm": "tx_transfer.c72acd0c6236468226e6d1a9805470ce641dc2944f5b6572918bf291001f8b24.wasm", + "tx_unbond.wasm": "tx_unbond.bdca8ae42250cb4e2d004b1b6fc4e7aae5f3294182e9201b6f356dc6a3d48f7b.wasm", + "tx_update_vp.wasm": "tx_update_vp.06ce2ffd4a276f4feeb9f437d223aa13b086b518c29b5fea48f8cefc2ea755e0.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d8a7823943b5c8e103aa09395fd270d390c9bd4faa913da97f4b3dd58d28d95d.wasm", + "tx_withdraw.wasm": "tx_withdraw.e85b69525f1f373ca2d55c4e89538c8df4fcfc0ac597f5587ae676018f29b2a9.wasm", + "vp_nft.wasm": "vp_nft.9a2ebfb62971a6d8ea34a8cd694a5ce18af08853988e424ff836a65cc8b57812.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7dce264e34990368dc5d8f26ef98cbd628d6b01cd937eaf121e5fdbfdddb07e9.wasm", + "vp_token.wasm": "vp_token.6e5218a15ed6fbb27ee5c1da122bc691780667c32273043612b638086e9741e4.wasm", + "vp_user.wasm": "vp_user.44992e8c971ca59e0a7ee9c3a81c838a3417a0064872f5def11780de82cc383c.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 78c2272f93..46ed1f0a4b 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index b9dcdc3d5d..9182e830f7 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 3f0a675b4d..226f98be03 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1402,6 +1408,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index e31959dbef..b76a18ffad 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -620,6 +620,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1403,6 +1409,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", From 5c099addf584bf164814f0721e8c1757b42ff2ce Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 15:29:19 +0200 Subject: [PATCH 0900/1995] [feat]: Updtae secp256 keys and as well as merging in key changes from main --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/mod.rs | 13 +++- shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 13 ++-- shared/src/types/key/dkg_session_keys.rs | 7 +- shared/src/types/key/ed25519.rs | 13 ++-- shared/src/types/key/mod.rs | 27 +++++++- shared/src/types/key/secp256k1.rs | 87 +++++++++++++++++------- wasm/tx_template/Cargo.lock | 7 ++ wasm/vp_template/Cargo.lock | 7 ++ wasm/wasm_source/Cargo.lock | 7 ++ wasm_for_tests/wasm_source/Cargo.lock | 7 ++ 12 files changed, 149 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fcc012880..7a356d5887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4303,6 +4303,7 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9449e5e70f..9b0766b118 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -857,10 +857,19 @@ mod test_utils { sig_bytes[0] = sig_bytes[0].wrapping_add(1); common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + common::Signature::Secp256k1(secp256k1::Signature( + ref sig, + ref recovery_id, + )) => { let mut sig_bytes = sig.serialize(); + let recovery_id_bytes = recovery_id.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) + let bytes: [u8; 65] = + [sig_bytes.as_slice(), [recovery_id_bytes].as_slice()] + .concat() + .try_into() + .unwrap(); + common::Signature::Secp256k1((&bytes).try_into().unwrap()) } } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 59196e1ff0..633367053c 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -70,7 +71,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -78,7 +79,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -196,7 +199,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -204,7 +207,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index dbcf9fe04c..052461de9a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -106,7 +107,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -114,7 +115,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -203,7 +206,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -211,7 +214,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1c5ac85558..5a16838455 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -7,6 +7,7 @@ use std::hash::Hash; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -85,7 +86,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -96,7 +97,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -107,7 +108,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] @@ -356,6 +357,26 @@ 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 + } + common::PublicKey::Secp256k1(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + +/// 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 { + HEXUPPER.encode(raw_hash.as_ref()) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 96b892fdbf..9c8825dd98 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,8 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; +use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -114,7 +116,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -122,7 +124,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -244,7 +248,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -252,7 +256,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } @@ -266,7 +272,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(pub libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature, pub RecoveryId); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; @@ -304,6 +310,7 @@ impl Serialize for Signature { for elem in &arr[..] { seq.serialize_element(elem)?; } + seq.serialize_element(&self.1.serialize())?; seq.end() } } @@ -316,7 +323,7 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( @@ -325,13 +332,13 @@ impl<'de> Deserialize<'de> for Signature { )) } - fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + fn visit_seq(self, mut seq: A) -> Result<[u8; 65], A::Error> where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -341,23 +348,34 @@ impl<'de> Deserialize<'de> for Signature { } let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE, + libsecp256k1::util::SIGNATURE_SIZE + 1, ByteArrayVisitor, )?; - let sig = libsecp256k1::Signature::parse_standard(&arr_res) + let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); + let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); - Ok(Signature(sig.unwrap())) + Ok(Signature( + sig.unwrap(), + RecoveryId::parse(arr_res[64]).map_err(Error::custom)?, + )) } } impl BorshDeserialize for Signature { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first + let (sig_bytes, recovery_id) = BorshDeserialize::deserialize(buf)?; + Ok(Signature( - libsecp256k1::Signature::parse_standard( - &(BorshDeserialize::deserialize(buf)?), - ) - .map_err(|e| { + libsecp256k1::Signature::parse_standard(&sig_bytes).map_err( + |e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + }, + )?, + RecoveryId::parse(recovery_id).map_err(|e| { std::io::Error::new( ErrorKind::InvalidInput, format!("Error decoding secp256k1 signature: {}", e), @@ -369,7 +387,10 @@ impl BorshDeserialize for Signature { impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - BorshSerialize::serialize(&self.0.serialize(), writer) + BorshSerialize::serialize( + &(self.0.serialize(), self.1.serialize()), + writer, + ) } } @@ -380,10 +401,16 @@ impl BorshSchema for Signature { 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 }; + // Encoded as `([u8; SIGNATURE_SIZE], u8)` + let signature = + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::declaration(); + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::add_definitions_recursively( + definitions, + ); + let recovery = "u8".into(); + let definition = borsh::schema::Definition::Tuple { + elements: vec![signature, recovery], + }; definitions.insert(Self::declaration(), definition); } @@ -405,12 +432,19 @@ impl PartialOrd for Signature { } } -impl TryFrom<&[u8; 64]> for Signature { +impl TryFrom<&[u8; 65]> for Signature { type Error = ParseSignatureError; - fn try_from(sig: &[u8; 64]) -> Result { - libsecp256k1::Signature::parse_standard(sig) - .map(Self) + fn try_from(sig: &[u8; 65]) -> Result { + let sig_bytes = sig[..64].try_into().unwrap(); + let recovery_id = RecoveryId::parse(sig[64]).map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + })?; + libsecp256k1::Signature::parse_standard(&sig_bytes) + .map(|sig| Self(sig, recovery_id)) .map_err(|err| { ParseSignatureError::InvalidEncoding(std::io::Error::new( ErrorKind::Other, @@ -467,7 +501,8 @@ impl super::SigScheme for SigScheme { 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) + let (sig, recovery_id) = libsecp256k1::sign(&message, &keypair.0); + Signature(sig, recovery_id) } } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 64bad284a2..b59950f447 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3a3058df34..392c010281 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 2b891bf4e1..4905239cb0 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1402,6 +1408,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f0cabb9569..56e8a94bfb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -620,6 +620,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1403,6 +1409,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", From 7355d064bdc9fa3dea820a40f2c19c66d3662195 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 17 Oct 2022 15:59:05 +0200 Subject: [PATCH 0901/1995] Update shared/src/types/key/mod.rs Co-authored-by: Tiago Carvalho --- shared/src/types/key/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 5a16838455..f2908f155b 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -372,7 +372,7 @@ pub fn tm_consensus_key_raw_hash(pk: &common::PublicKey) -> String { } } -/// Convert Tendermint validator's raw hash bytes to Anoma raw hash string +/// Convert Tendermint validator's raw hash bytes to Namada raw hash string pub fn tm_raw_hash_to_string(raw_hash: impl AsRef<[u8]>) -> String { HEXUPPER.encode(raw_hash.as_ref()) } From cb132108f355a85a53d1526883f7cef068231c67 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 0902/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++- .../ledger/eth_bridge/storage/bridge_pool.rs | 5 ++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a413f3739d..d08daaf4cd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -372,7 +372,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1185,4 +1185,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7e49c197b3..81027b23d3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -161,6 +161,11 @@ impl BridgePoolTree { } leaves.insert(hash); } + if !leaves.is_subset(&self.store) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; From bce9e91de236ea9b2ac351f059231c7aea71eed0 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:02:17 +0200 Subject: [PATCH 0903/1995] [chore]: Added docstring --- shared/src/types/key/secp256k1.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9c8825dd98..8c969261e5 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -323,6 +323,9 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { + // [`libsecp265k1::util::SIGNATURE_SIZE`] is for a traditional + // signature on this curve. For Ethereum, an extra byte is included + // that prevents malleability attacks. type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { From d9197cea640db04272d1384f447b5c0c4aa57ed6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 14:13:59 +0000 Subject: [PATCH 0904/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index cbfb7ac1d2..c5a946c707 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.3e5734a16b9ed575111765dd318c6f0045598991035b959a24f99bf77bd14634.wasm", - "tx_ibc.wasm": "tx_ibc.bd919697f8fc9bed1d50abf5b3a8e1b3b737fe2e65bfd8c055e965f0831910a5.wasm", - "tx_init_account.wasm": "tx_init_account.078a50524745f9293b3b32c0694eceb3da75621d99e1e44ef79f45928c53a4a2.wasm", - "tx_init_nft.wasm": "tx_init_nft.fe9013d06b1de44091da6adac2458a3688a2ce54177f758b2386c8307c9ed9c2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.eb7551c29dab7fc2d317568b5d94a4b829ed8083eda7431f7c7507c0ee23a10f.wasm", - "tx_init_validator.wasm": "tx_init_validator.88506a9b248679d9f65f9adea6ab99f9fe4fa189b1b7928083f03eba1985c171.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.cc2b4de9f8999ef6a56762d021a1f2ed81cd5d9082de3574afbc4195fbb90528.wasm", - "tx_transfer.wasm": "tx_transfer.b54edcb95323d2a7c2dacf695cae32ff1e28ad78b07bb16450903f27ac940c59.wasm", - "tx_unbond.wasm": "tx_unbond.ff2cff17d1ab94e4fbd69ab30af4ee732e576498e52dc33b15e1bb43d2dfc074.wasm", - "tx_update_vp.wasm": "tx_update_vp.28bfdcddf988bf0e9f5b51fd6f592931be8fdd51f960073afa47e0f9f15e81c2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.66ee2ecebe5ebb2c1bc8749888890ef9d542affc23d27f0414c36a73a649f7e0.wasm", - "tx_withdraw.wasm": "tx_withdraw.4edb668ae6fcfe5dccdd3b58ffee3772f2e6d57e3794207b9353f87dff1940e3.wasm", - "vp_nft.wasm": "vp_nft.e34a534fe7621ec07036fab390098c90b21766f02f2a14800458cf3f3500480b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.5475409ca94d33ffc4ffc93872f230a62ebf06545b601dc8427bb2bfa23834a2.wasm", - "vp_token.wasm": "vp_token.9d7841550cb1f20f25b8701b1b4075f43638d9dea77733035f106f98566bc24e.wasm", - "vp_user.wasm": "vp_user.dcc6032b8435ed6aeca16fb2eb54e8ffe8759b9efc11f67ffe98a334fb74dab1.wasm" -} + "tx_from_intent.wasm": "tx_from_intent.46321f0cb4ab41ff1059cb32e8307c1a909db837c0ed4df4926b5c2d709e430b.wasm", + "tx_ibc.wasm": "tx_ibc.9652b3179073b74d6ef8fc093654df16465ca38da71b10a89506be842d6c132d.wasm", + "tx_init_account.wasm": "tx_init_account.ad2b61f2f59055180b9411167b67a3dd26f8df535be74370a023c2448541202d.wasm", + "tx_init_nft.wasm": "tx_init_nft.f4eda1b78feaee4122f184a3351e438b34cf684b9d54580e6018f67fbbe684ae.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c864adc5a98b80f63583066bff461b3397b0a1c6446cfc9d5879500c9e666963.wasm", + "tx_init_validator.wasm": "tx_init_validator.52045789fe99a6472541a78f424b53a265b296005d6781a1d2c953f9ecd83a13.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.c316a90cef3c1d86aa7236979de90aa75d4bb0b50eb89e5aeb3b8f08c17cb93f.wasm", + "tx_transfer.wasm": "tx_transfer.bd995ad8643d42c7ab1cf8b0cfcf36cd480507c56ca00bf6d38c74efa0450696.wasm", + "tx_unbond.wasm": "tx_unbond.230d4b9a022a85c16097a5623bafe66edfc5af534a60742cbe669960bed3d36b.wasm", + "tx_update_vp.wasm": "tx_update_vp.c0d112ef51c6ba05aa17a7e83e103f368ff44011a8d2a1d50bf206040c4b5c84.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1e4d947578ae9250b4a25101823e4dd4004fb085e77038b775a9539cfa52e666.wasm", + "tx_withdraw.wasm": "tx_withdraw.17c472e0cdd858d6421dbc03509da5278a16e9f4f2f82f42a589ec8871f169e3.wasm", + "vp_nft.wasm": "vp_nft.b6a1e83e46a83f02c510fc6f6da5801924ad86a8944b9443fb20bcdf8cc3da97.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7201fa3ae532f64eb0172d0fb04b055e72a49143a71b2718aedc8ef5a399c761.wasm", + "vp_token.wasm": "vp_token.76781e41aa38873a06066cc81ede234767b96490c4151da5682d9d5d5ad022ec.wasm", + "vp_user.wasm": "vp_user.09c35edb0ae1b25c4ff6fe8b73088ac9a51407b9dada2aa6d2d353fa533b18b9.wasm" +} \ No newline at end of file From 1f4ca8dfce571a18941f5c828172d7b233b4fb3f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 14 Oct 2022 16:53:40 +0100 Subject: [PATCH 0905/1995] Add new query paths --- apps/src/lib/node/ledger/rpc.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index ad3d2f5fcb..a14d138da3 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -9,19 +9,27 @@ use thiserror::Error; use crate::facade::tendermint::abci::Path as AbciPath; -/// RPC query path +/// RPC query path. #[derive(Debug, Clone)] pub enum Path { - /// Dry run a transaction + /// Dry run a transaction. DryRunTx, - /// Epoch of the last committed block + /// Epoch of the last committed block. Epoch, - /// Read a storage value with exact storage key + /// Read a storage value with exact storage key. Value(storage::Key), - /// Read a range of storage values with a matching key prefix + /// Read a range of storage values with a matching key prefix. Prefix(storage::Key), - /// Check if the given storage key exists + /// Check if the given storage key exists. HasKey(storage::Key), + /// Check if a transaction was accepted. + // TODO: use a fixed length type here, + // for the accepted hash + Accepted(String), + /// Check if a transaction was applied. + // TODO: use a fixed length type here, + // for the applied hash + Applied(String), } #[derive(Debug, Clone)] From 002a1dfde861b7ddfbeda858f23cb87abe03a252 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:13:31 +0100 Subject: [PATCH 0906/1995] Add a hex encoded hash type --- shared/src/types/hash.rs | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e8ef5577cb..53957d0170 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -23,6 +23,8 @@ pub enum Error { Temporary { error: String }, #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), + #[error("The string is not valid hex encoded data.")] + NotHexEncoded, } /// Result for functions that may fail @@ -145,3 +147,56 @@ impl Value for Hash { Hash([0u8; 32]) } } + +/// The hex encoded version of a [`Hash`]. +#[derive( + Clone, + Debug, + Default, + Hash, + PartialEq, + Eq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct HashString { + inner: [u8; HASH_LENGTH * 2], +} + +impl Deref for HashString { + type Target = str; + + fn deref(&self) -> &str { + // SAFETY: We can only construct a `HashString` + // from valid hex encoded data. + unsafe { std::str::from_utf8_unchecked(&self.inner) } + } +} + +impl TryFrom<&str> for HashString { + type Error = self::Error; + + fn try_from(hash: &str) -> HashResult { + const HEX_LEN: usize = HASH_LENGTH * 2; + + let mut hash_len = 0; + let mut buf = [0; HEX_LEN]; + + for (slot, ch) in buf.iter_mut().zip(hash.chars().take(HEX_LEN)) { + match ch { + 'a'..='f' | 'A'..='F' | '0'..='9' => *slot = ch, + _ => return Err(self::Error::NotHexEncoded), + } + hash_len += 1; + } + + if hash_len == HEX_LEN { + Ok(HashString { inner: buf }) + } else { + Err(self::Error::NotHexEncoded) + } + } +} From 8066d85616b9a69c72dfc5c4a94f4eea9ae41b43 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:23:10 +0100 Subject: [PATCH 0907/1995] Remove serialization methods from hash str --- shared/src/types/hash.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 53957d0170..ef14de55c5 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -149,19 +149,7 @@ impl Value for Hash { } /// The hex encoded version of a [`Hash`]. -#[derive( - Clone, - Debug, - Default, - Hash, - PartialEq, - Eq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] +#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] pub struct HashString { inner: [u8; HASH_LENGTH * 2], } From 7a609686e9c0d13d8ec4bfe08cfb5f38a20ebaf4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:24:58 +0100 Subject: [PATCH 0908/1995] Hash str fixes --- shared/src/types/hash.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index ef14de55c5..203ebe1f66 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -149,11 +149,19 @@ impl Value for Hash { } /// The hex encoded version of a [`Hash`]. -#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)] +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct HashString { inner: [u8; HASH_LENGTH * 2], } +impl Default for HashString { + fn default() -> Self { + Self { + inner: [0; HASH_LENGTH * 2], + } + } +} + impl Deref for HashString { type Target = str; @@ -175,7 +183,7 @@ impl TryFrom<&str> for HashString { for (slot, ch) in buf.iter_mut().zip(hash.chars().take(HEX_LEN)) { match ch { - 'a'..='f' | 'A'..='F' | '0'..='9' => *slot = ch, + 'a'..='f' | 'A'..='F' | '0'..='9' => *slot = ch as u8, _ => return Err(self::Error::NotHexEncoded), } hash_len += 1; From 2e0df5d34e47dda85b07b6ae93a4cba2514b49b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:31:18 +0100 Subject: [PATCH 0909/1995] Tx accepted and applied paths --- apps/src/lib/node/ledger/rpc.rs | 31 ++++++++++++++++++----- apps/src/lib/node/ledger/shell/queries.rs | 2 ++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index a14d138da3..e9c96fc41f 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use namada::types::address::Address; +use namada::types::hash::{self, HashString}; use namada::types::storage; use thiserror::Error; @@ -23,13 +24,9 @@ pub enum Path { /// Check if the given storage key exists. HasKey(storage::Key), /// Check if a transaction was accepted. - // TODO: use a fixed length type here, - // for the accepted hash - Accepted(String), + Accepted(HashString), /// Check if a transaction was applied. - // TODO: use a fixed length type here, - // for the applied hash - Applied(String), + Applied(HashString), } #[derive(Debug, Clone)] @@ -45,6 +42,8 @@ const EPOCH_PATH: &str = "epoch"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; +const ACCEPTED_PREFIX: &str = "accepted"; +const APPLIED_PREFIX: &str = "applied"; impl Display for Path { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -60,6 +59,14 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::Accepted(hash) => { + let hash: &str = hash; + write!(f, "{ACCEPTED_PREFIX}/{hash}") + } + Path::Applied(hash) => { + let hash: &str = hash; + write!(f, "{APPLIED_PREFIX}/{hash}") + } } } } @@ -87,6 +94,16 @@ impl FromStr for Path { .map_err(PathParseError::InvalidStorageKey)?; Ok(Self::HasKey(key)) } + Some((ACCEPTED_PREFIX, hash)) => { + let hash = + hash.try_into().map_err(PathParseError::InvalidHash)?; + Ok(Self::Accepted(hash)) + } + Some((APPLIED_PREFIX, hash)) => { + let hash = + hash.try_into().map_err(PathParseError::InvalidHash)?; + Ok(Self::Applied(hash)) + } _ => Err(PathParseError::InvalidPath(s.to_string())), }, } @@ -109,4 +126,6 @@ pub enum PathParseError { InvalidPath(String), #[error("Invalid storage key: {0}")] InvalidStorageKey(storage::Error), + #[error("Invalid hash: {0}")] + InvalidHash(hash::Error), } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cd6393e2b0..ff858f73a4 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -86,6 +86,8 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::Accepted(_) => todo!(), + Path::Applied(_) => todo!(), }, Err(err) => response::Query { code: 1, From cca6639044e4f5d7bd5c66a78f474076164b951b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:36:46 +0100 Subject: [PATCH 0910/1995] Add accepted and applied query matcher constructors --- .../node/ledger/events/log/dumb_queries.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index ef96193521..b5cf3a8786 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -7,6 +7,7 @@ //! ``` use lazy_static::lazy_static; +use namada::types::hash::HashString; use regex::Regex; use crate::node::ledger::events::{Event, EventType}; @@ -61,6 +62,24 @@ impl<'q> QueryMatcher<'q> { value, }) } + + /// Returns a query matching the given accepted hash. + pub fn accepted(hash: &'q HashString) -> Self { + Self { + event_type: EventType::Accepted, + attr: "hash".to_string(), + value: hash, + } + } + + /// Returns a query matching the given applied hash. + pub fn applied(hash: &'q HashString) -> Self { + Self { + event_type: EventType::Applied, + attr: "hash".to_string(), + value: hash, + } + } } #[cfg(test)] From 3da652468d9204a40f07b862c9d2f8352db71cfd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:45:17 +0100 Subject: [PATCH 0911/1995] Add event log to the shell --- apps/src/lib/node/ledger/shell/mod.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9449e5e70f..23b55c84fa 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -61,6 +61,7 @@ use crate::facade::tendermint_proto::types::ConsensusParams; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tower_abci::{request, response}; +use crate::node::ledger::events::log::EventLog; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -338,6 +339,8 @@ where pub(super) tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, + /// Log of events emitted by `FinalizeBlock`. + event_log: EventLog, } impl Shell @@ -453,9 +456,25 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), + // TODO: config event log params + event_log: EventLog::new(Default::default()), } } + /// Return a reference to the [`EventLog`]. + #[inline] + #[allow(dead_code)] + pub fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + #[allow(dead_code)] + pub fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } + /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { From ea7ffd2d729a3cb8ec10f0fd1f212798bc826c2d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 09:53:39 +0100 Subject: [PATCH 0912/1995] Log events in finalize block --- apps/src/lib/node/ledger/shell/finalize_block.rs | 3 +++ apps/src/lib/node/ledger/shell/mod.rs | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6ff72a1ec0..1a2bf7a75d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -421,6 +421,9 @@ where .gas_meter .finalize_transaction() .map_err(|_| Error::GasOverflow)?; + + self.event_log_mut().log_events(&response.events); + Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 23b55c84fa..0dc55fcb9b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -470,7 +470,6 @@ where /// Return a mutable reference to the [`EventLog`]. #[inline] - #[allow(dead_code)] pub fn event_log_mut(&mut self) -> &mut EventLog { &mut self.event_log } From 214dba4aa488c36651927f3d4c82f0e16a626d64 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 10:23:50 +0100 Subject: [PATCH 0913/1995] Add borsh serialization to events --- apps/src/lib/node/ledger/events.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 24c5a947f1..0a8a50ad60 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::governance::utils::ProposalEvent; use namada::types::ibc::IbcEvent; use namada::types::transaction::{hash_tx, TxType}; @@ -15,7 +15,7 @@ use crate::facade::tendermint_proto::abci::EventAttribute; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventLevel { Block, Tx, @@ -23,7 +23,7 @@ pub enum EventLevel { /// Custom events that can be queried from Tendermint /// using a websocket client -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub struct Event { pub event_type: EventType, pub level: EventLevel, @@ -31,7 +31,7 @@ pub struct Event { } /// The two types of custom events we currently use -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventType { // The transaction was accepted to be included in a block Accepted, From 87e3ab828cd92f9dc3e418feb6ab1dbc0ae8faa5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 10:24:38 +0100 Subject: [PATCH 0914/1995] Implement accepted and applied tx query endpoints --- apps/src/lib/node/ledger/shell/mod.rs | 1 - apps/src/lib/node/ledger/shell/queries.rs | 30 +++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 0dc55fcb9b..a3c178d753 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -463,7 +463,6 @@ where /// Return a reference to the [`EventLog`]. #[inline] - #[allow(dead_code)] pub fn event_log(&self) -> &EventLog { &self.event_log } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index ff858f73a4..d78c197c43 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -19,6 +19,7 @@ use super::*; use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; +use crate::node::ledger::events::log::dumb_queries; use crate::node::ledger::response; #[derive(Error, Debug)] @@ -86,8 +87,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), - Path::Accepted(_) => todo!(), - Path::Applied(_) => todo!(), + Path::Accepted(ref hash) => { + let matcher = dumb_queries::QueryMatcher::accepted(hash); + self.query_event_log(matcher) + } + Path::Applied(ref hash) => { + let matcher = dumb_queries::QueryMatcher::applied(hash); + self.query_event_log(matcher) + } }, Err(err) => response::Query { code: 1, @@ -97,6 +104,25 @@ where } } + /// Query events in the event log matching the given query. + fn query_event_log( + &self, + matcher: dumb_queries::QueryMatcher<'_>, + ) -> response::Query { + let value = self + .event_log() + .iter_with_matcher(matcher) + .cloned() + .collect::>() + .try_to_vec() + .unwrap(); + + response::Query { + value, + ..Default::default() + } + } + /// Query to check if a storage key exists. fn has_storage_key(&self, key: &Key) -> response::Query { match self.storage.has_key(key) { From 4bd37c3254f2a3f64efa3ffb986dfa5a729bb61f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 10:35:56 +0100 Subject: [PATCH 0915/1995] Add hash str proptest --- shared/src/types/hash.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 203ebe1f66..4d5b170beb 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -172,6 +172,15 @@ impl Deref for HashString { } } +impl TryFrom for HashString { + type Error = self::Error; + + #[inline] + fn try_from(hash: String) -> HashResult { + hash.as_str().try_into() + } +} + impl TryFrom<&str> for HashString { type Error = self::Error; @@ -196,3 +205,23 @@ impl TryFrom<&str> for HashString { } } } + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use proptest::string::{string_regex, RegexGeneratorStrategy}; + + use super::*; + + /// Returns a proptest strategy that yields hex encoded hashes. + fn hex_encoded_hash_strat() -> RegexGeneratorStrategy { + string_regex(r"[a-fA-F0-9]{64}").unwrap() + } + + proptest! { + #[test] + fn test_hash_string(hex_hash in hex_encoded_hash_strat()) { + let _: HashString = hex_hash.try_into().unwrap(); + } + } +} From 64c9e11a6680ec1b72c325bf92cca7c9bbfca27b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 12:51:46 +0100 Subject: [PATCH 0916/1995] Explicitly clone events in FinalizeBlock --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1a2bf7a75d..bfeb842855 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -422,7 +422,7 @@ where .finalize_transaction() .map_err(|_| Error::GasOverflow)?; - self.event_log_mut().log_events(&response.events); + self.event_log_mut().log_events(response.events.clone()); Ok(response) } From 5eb6f066456568aa9ac632dc213a3d2b4dc7dc0b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 12:41:25 +0100 Subject: [PATCH 0917/1995] Remove query parser --- apps/Cargo.toml | 1 - apps/src/lib/node/ledger/events/log.rs | 32 ++------ .../node/ledger/events/log/dumb_queries.rs | 77 ------------------- 3 files changed, 5 insertions(+), 105 deletions(-) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1949fd9e30..fc22eb7040 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -93,7 +93,6 @@ file-lock = "2.0.2" futures = "0.3" hex = "0.4.3" itertools = "0.10.1" -lazy_static = "1.4.0" libc = "0.2.97" libloading = "0.7.2" libp2p = "0.38.0" diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index f31b7734c5..d9f6e12147 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -12,13 +12,6 @@ use crate::node::ledger::events::Event; pub mod dumb_queries; -/// Errors specific to [`EventLog`] operations. -#[derive(Debug)] -pub enum Error { - /// We failed to parse a Tendermint query. - InvalidQuery, -} - /// Parameters to configure the pruning of the event log. #[derive(Debug, Copy, Clone)] pub struct Params { @@ -66,31 +59,16 @@ impl EventLog { tracing::debug!(num_entries, "Added new entries to the event log"); } - /// Returns a new iterator over this [`EventLog`], if the - /// given `query` is valid. - pub fn try_iter<'query, 'log>( + /// Returns a new iterator over this [`EventLog`]. + #[inline] + pub fn iter_with_matcher<'query, 'log>( &'log self, - query: &'query str, - ) -> Result + 'query, Error> + matcher: dumb_queries::QueryMatcher<'query>, + ) -> impl Iterator + 'query where // the log should outlive the query 'log: 'query, { - let matcher = - dumb_queries::QueryMatcher::parse(query).ok_or_else(|| { - tracing::debug!(query, "Invalid Tendermint query"); - Error::InvalidQuery - })?; - Ok(self.iter_with_matcher(matcher)) - } - - /// Just like [`EventLog::try_iter`], but uses a pre-compiled - /// query matcher. - #[inline] - pub fn iter_with_matcher<'query, 'log: 'query>( - &'log self, - matcher: dumb_queries::QueryMatcher<'query>, - ) -> impl Iterator + 'query { self.queue .iter() .filter(move |&event| matcher.matches(event)) diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index b5cf3a8786..f28c2e7182 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -6,21 +6,8 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` -use lazy_static::lazy_static; -use namada::types::hash::HashString; -use regex::Regex; - use crate::node::ledger::events::{Event, EventType}; -/// Regular expression used to parse Tendermint queries. -const QUERY_PARSING_REGEX_STR: &str = - r"^tm\.event='NewBlock' AND (accepted|applied)\.([\w_]+)='([^']+)'$"; - -lazy_static! { - /// Compiled regular expression used to parse Tendermint queries. - static ref QUERY_PARSING_REGEX: Regex = Regex::new(QUERY_PARSING_REGEX_STR).unwrap(); -} - /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. #[derive(Debug, Clone)] @@ -42,27 +29,6 @@ impl<'q> QueryMatcher<'q> { .unwrap_or_default() } - /// Parses a Tendermint-like events query. - pub fn parse(query: &'q str) -> Option { - let captures = QUERY_PARSING_REGEX.captures(query)?; - - let event_type = match captures.get(1)?.as_str() { - "accepted" => EventType::Accepted, - "applied" => EventType::Applied, - // NOTE: the regex only matches `accepted` - // and `applied` - _ => unreachable!(), - }; - let attr = captures.get(2)?.as_str().to_string(); - let value = captures.get(3)?.as_str(); - - Some(Self { - event_type, - attr, - value, - }) - } - /// Returns a query matching the given accepted hash. pub fn accepted(hash: &'q HashString) -> Self { Self { @@ -84,52 +50,9 @@ impl<'q> QueryMatcher<'q> { #[cfg(test)] mod tests { - use proptest::prelude::*; - use proptest::string::{string_regex, RegexGeneratorStrategy}; - use super::*; use crate::node::ledger::events::EventLevel; - /// Returns a proptest strategy that yields Tendermint-like queries. - fn tm_query_strat() -> RegexGeneratorStrategy { - string_regex( - // slice out the string init and end specifiers - &QUERY_PARSING_REGEX_STR[1..QUERY_PARSING_REGEX_STR.len() - 1], - ) - .unwrap() - } - - proptest! { - /// Test if we can parse a Tendermint query, feeding [`QueryMatcher::parse`] - /// random input data. - #[test] - fn test_random_inputs(query in tm_query_strat()) { - QueryMatcher::parse(&query).unwrap(); - } - } - - /// Test if we parse a correct Tendermint query. - #[test] - fn test_parse_correct_tm_query() { - let q = QueryMatcher::parse( - "tm.event='NewBlock' AND applied.hash='123456'", - ) - .unwrap(); - - assert_eq!(q.event_type, EventType::Applied); - assert_eq!(&q.attr, "hash"); - assert_eq!(q.value, "123456"); - - let q = QueryMatcher::parse( - "tm.event='NewBlock' AND accepted.hash='DEADBEEF'", - ) - .unwrap(); - - assert_eq!(q.event_type, EventType::Accepted); - assert_eq!(&q.attr, "hash"); - assert_eq!(q.value, "DEADBEEF"); - } - /// Test if query matching is working as expected. #[test] fn test_tm_query_matching() { From 02b2c75c67b9fe45490b91ade919190dfb75d09d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:12:34 +0100 Subject: [PATCH 0918/1995] Fix docstr --- shared/src/types/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 4d5b170beb..1a08dcd259 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -148,7 +148,7 @@ impl Value for Hash { } } -/// The hex encoded version of a [`Hash`]. +/// A hex encoded hash. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct HashString { inner: [u8; HASH_LENGTH * 2], From 3b71328ae79101bbbf7c2d29f64e2cdb7a916af5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:12:51 +0100 Subject: [PATCH 0919/1995] Add missing import --- apps/src/lib/node/ledger/events/log/dumb_queries.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index f28c2e7182..4d74b97544 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -6,6 +6,8 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` +use namada::types::hash::HashString; + use crate::node::ledger::events::{Event, EventType}; /// A [`QueryMatcher`] verifies if a Namada event matches a From eb0ded72a1e235e89d0cdd4b94a1db1b6a974051 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:13:12 +0100 Subject: [PATCH 0920/1995] Fix unit tests --- apps/src/lib/node/ledger/events/log.rs | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index d9f6e12147..7238d1df4e 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -77,9 +77,23 @@ impl EventLog { #[cfg(test)] mod tests { + use namada::types::hash::HashString; + use super::*; use crate::node::ledger::events::{EventLevel, EventType}; + const HASH: &str = + "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; + + /// An accepted tx hash query. + macro_rules! accepted { + ($hash:expr) => { + dumb_queries::QueryMatcher::accepted( + &HashString::try_from($hash).unwrap(), + ) + }; + } + /// Return a vector of mock `FinalizeBlock` events. fn mock_tx_events(hash: &str) -> Vec { let event_1 = Event { @@ -112,18 +126,15 @@ mod tests { let mut log = EventLog::new(Params::default()); // add new events to the log - let events = mock_tx_events("DEADBEEF"); + let events = mock_tx_events(HASH); for _ in 0..NUM_HEIGHTS { log.log_events(events.clone()); } // inspect log - let events_in_log: Vec<_> = log - .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") - .unwrap() - .cloned() - .collect(); + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); assert_eq!(events_in_log.len(), NUM_HEIGHTS); @@ -153,7 +164,7 @@ mod tests { // // `mock_tx_events` returns 2 events, so // we do `LOG_CAP / 2` iters to fill the log - let events = mock_tx_events("DEADBEEF"); + let events = mock_tx_events(HASH); assert_eq!(events.len(), 2); for _ in 0..(LOG_CAP / 2) { @@ -161,11 +172,8 @@ mod tests { } // inspect log - it should be full - let events_in_log: Vec<_> = log - .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") - .unwrap() - .cloned() - .collect(); + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); assert_eq!(events_in_log.len(), MATCHED_EVENTS); @@ -177,11 +185,8 @@ mod tests { // pruning the first ACCEPTED event we added log.log_events(Some(events[1].clone())); - let events_in_log: Vec<_> = log - .try_iter("tm.event='NewBlock' AND accepted.hash='DEADBEEF'") - .unwrap() - .cloned() - .collect(); + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); const ACCEPTED_EVENTS: usize = MATCHED_EVENTS - 1; assert_eq!(events_in_log.len(), ACCEPTED_EVENTS); From 3f2e7ccb90fef8be7394c89ba394460962cc4aef Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:13:23 +0100 Subject: [PATCH 0921/1995] Update Cargo.lock --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b47e2fce1f..2277ac7033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4389,7 +4389,6 @@ dependencies = [ "git2", "hex", "itertools 0.10.3", - "lazy_static 1.4.0", "libc", "libloading", "libp2p", From f4f86dc9cba22cbeafd0d7a14ac785cd07b8c9fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 14:48:57 +0000 Subject: [PATCH 0922/1995] [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 2634122c96..e8f7a27c1a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.dcad4fe9013afa57646007eefd749cf43453c085b603c5809efbb4a3202ae2c9.wasm", + "tx_ibc.wasm": "tx_ibc.6b199991a150467fadef2b7d3c6654ed6a4254eaf9570855910e5f443df598b8.wasm", + "tx_init_account.wasm": "tx_init_account.256ddd73be711aabd30ebecf9c8126ba5a6899b50341ba83be008683e7324842.wasm", + "tx_init_nft.wasm": "tx_init_nft.cc11aa351e7ae33e8773b486d7d5e8585e6acdce7c38d28f1bf7c99748e40ebc.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.9f513cae3440e6f9f07193bb222bbc5177d1b5a6d68f3f8b5ba6e225d29b6cc9.wasm", + "tx_init_validator.wasm": "tx_init_validator.00b2cc7d730ae9184dae7cbf18a8d730a8a2ebc0e2aa5a2fdfd6533298bef789.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.335e84b552ba3d45d4f521c9a4674d54a1a55aba6022b7c23899c2a781603129.wasm", + "tx_transfer.wasm": "tx_transfer.ad34b92ef8b3f4a937e15955baca6598d2d5e3e98faacb4f7310a6e6b5e4e641.wasm", + "tx_unbond.wasm": "tx_unbond.826bba3caa132fc9ab117285e41efddb99be9cf6bfceff4e7091f50dba9d5924.wasm", + "tx_update_vp.wasm": "tx_update_vp.a18118a46a716fdb0ae2c8163cfc145bcb0750c516322fc2b6af597e9d878237.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.63656c1b70615e84e1eeb7035e8cac5222225719d7213e70d65b22c8977f87c8.wasm", + "tx_withdraw.wasm": "tx_withdraw.ef61844f98aacb3bca716bbe433d937a84eb6d17523d699d523cd60c95860333.wasm", + "vp_nft.wasm": "vp_nft.01091570bddc36307ebc6af61cfff80e72fd097110f9de0b3a836a3a61eeddad.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.398a6b43c58fd3f2f01a82255ed80b219e2d5e6f0d699f119d1285dfe334016b.wasm", + "vp_token.wasm": "vp_token.3017e3f72780d21d6c566511a95e872c24ee212735dd798bcbecc786772712f3.wasm", + "vp_user.wasm": "vp_user.2815df8b0009818104aac2c0f0fb80c86cc191eefb1fbcf5dd4e12e9b35582da.wasm" } \ No newline at end of file From 1e72d9cb9ac2dfde9585eca269a54208a15a8473 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 14:49:56 +0000 Subject: [PATCH 0923/1995] [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 2634122c96..c366abf3f3 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.9915822bea588b0a18b4ec51bc249e88fdc3041bc3eddaaa2cc880dcd23c5eb0.wasm", + "tx_ibc.wasm": "tx_ibc.913192b268db668ebba6b415eea106c19e742b9b6be6ebb795a661dca82482af.wasm", + "tx_init_account.wasm": "tx_init_account.bd544ce16dae46177f9d9bd5281a53b4f4fe741833013a1f412b1f104c4bf6a6.wasm", + "tx_init_nft.wasm": "tx_init_nft.1f1b18628e97758d837d0f25c313979cc529abc56797be240ce4c74032d603c5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ad5ba0ef45cef6b59dab228dfaeb0dc643235639851c1adcff5d52152d2c01f5.wasm", + "tx_init_validator.wasm": "tx_init_validator.9b1ddb7e6dca6beadbf42c4da91771c92228562f4239494e4e56d57fd3c3b538.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.bd061f19c4d2e5801f671adb60e7e4d787a6f493a28c29f20a57e4a256a8dcbc.wasm", + "tx_transfer.wasm": "tx_transfer.1a8d43355f663c040a29d6e10e6cdfe6c73634aa5cd6863e1f3f5d58fe9ec560.wasm", + "tx_unbond.wasm": "tx_unbond.84a2a809d5b617740b991ddc5b88ad213ffa17aa178afe1f657b90cecebbaa7a.wasm", + "tx_update_vp.wasm": "tx_update_vp.84b896bce441e449cdbfa2401f2da8ea6f0c10725c1a8d9e1a03763b49056817.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8b171e9bdc02f6321f0b7ae6a881d00d26d7c5ecb00bb7aa0d77e411767517c9.wasm", + "tx_withdraw.wasm": "tx_withdraw.aede879ea5c81b4e479d89b2e9c5d9e5d80bf7e0a9e8a0a8c7bf7f2bb2b049f5.wasm", + "vp_nft.wasm": "vp_nft.87b7fc9e596891dd676294e18a467fd8ef16c372a3e22f7876b125fb9c31d606.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3ed495ea630de4a8d5459e09882c1862e768b826654aa1b700ed13e1e5cf8827.wasm", + "vp_token.wasm": "vp_token.b95cfb6bff76ffec7034f29453482937297cf713fd7cfd3f4f7045a2cb044bca.wasm", + "vp_user.wasm": "vp_user.fac7690818a1bc043cdb13d65b7f9d687343845c2e31e3eed841dc074803bff9.wasm" } \ No newline at end of file From 89c6f4023f7030faf4a57a89d36860d0b2a1fd5b Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 0924/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 11 +++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d08daaf4cd..3ce6f1d386 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -430,6 +430,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1181,6 +1183,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1223,7 +1227,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 81027b23d3..cbb7aebbe3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -164,7 +164,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.store) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; @@ -355,8 +356,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -371,12 +372,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 85f2bd99b1..44ebc7cb7e 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -58,8 +58,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::String(self.gas_fee.payer.to_string()); @@ -67,7 +67,7 @@ impl Encode for PendingTransfer { let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, nonce] + [version, namespace, from, to, amount, fee, nonce] } } @@ -116,17 +116,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -138,13 +136,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9c8825dd98..89016530b0 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -419,6 +421,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From dcfbe0526fa8ad8aaa030e79bec574f07ff55b97 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:52:38 +0200 Subject: [PATCH 0925/1995] [fix]: Fixed broken doc link --- shared/src/ledger/eth_bridge/storage/bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index c0ccf75f68..89e220335f 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -118,7 +118,7 @@ impl BridgePoolTree { } } - /// Return the root as a [`Hash`] type. + /// Return the root as a [struct@`Hash`] type. pub fn root(&self) -> Hash { self.root.clone().into() } From 93000b0d28c9ed54b3a597b9cfddc7a99adda6bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 14:54:57 +0000 Subject: [PATCH 0926/1995] [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 2634122c96..1250ca7e3e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.536ed89ef1c74d3c6b3e005d60cc53bbab7c36935b46bb69f7287de0a54f91e1.wasm", + "tx_ibc.wasm": "tx_ibc.790e710a87d31a215c19dd29d3c3723f6bb0e22482b3a368c21a367df99aefe8.wasm", + "tx_init_account.wasm": "tx_init_account.a50110eb561a84549708b98b5841358ed87de5a9b08f8a947711c5908eb13558.wasm", + "tx_init_nft.wasm": "tx_init_nft.bee3fbefc9e6319eea9244b9abc7fa5e80a62b43a003260803989856aef8d438.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.26ed1981f8f0ee5b9852dd96d7ad171707194056db5731e9915dbb1747892303.wasm", + "tx_init_validator.wasm": "tx_init_validator.8b37971addbd3ace29174db5f618f06b62115cd7b44ff1b7d756c9d4d81cbc56.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.659dbac8df06b8e6775e680ebb772c3d19733b9e14ea513c665a78ac70788610.wasm", + "tx_transfer.wasm": "tx_transfer.03a7bff45511f240ebdf92f5e4612e244cc937fda3a22ba0a77936e36d834f37.wasm", + "tx_unbond.wasm": "tx_unbond.74388ad956a8ed618974140836e97ae254ae9e8a3e24065908e3a318d55fd37b.wasm", + "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f3103ee1148954898e2cd92eb0134b34792e7a373a505f37544f389b3cd3cc88.wasm", + "tx_withdraw.wasm": "tx_withdraw.3e1619ad5db6dcb2146684bf54f2e383747fa81f1baf9d00005c4431a3d0a566.wasm", + "vp_nft.wasm": "vp_nft.31a3045be40eeabb48140d2f68db26006991dffb208f6bd394a0b6740cd5b4d0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.d4b27eadcb98ed3664028bee05fe33f8acb7a59182ca22833a77df696d1cf369.wasm", + "vp_token.wasm": "vp_token.aea6ee5649995c6352d982687c30cfe44fcf05a0cd9cb9b0b48d80768633d098.wasm", + "vp_user.wasm": "vp_user.589011534fd04c4b44fb4412b2f58ea548628dc1ba4db5d45a9aaa1f9e6f0552.wasm" } \ No newline at end of file From 3aa8bfb02d1093efb815338ea52e10002a8e4f30 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 0927/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3ce6f1d386..cc3a1b2991 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -428,6 +428,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1181,6 +1183,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 44ebc7cb7e..3fb898604e 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -132,6 +133,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -140,11 +143,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..24e0145ac5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -301,6 +301,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 009c33cbefd0fde5a95d4b2ccede6b49d95f87ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 15:33:09 +0000 Subject: [PATCH 0928/1995] [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 2634122c96..850b41f1b8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.7e9893f8e2affcfbb4e04fe936bb18571645353da08d8e08d880971f2cfca053.wasm", + "tx_ibc.wasm": "tx_ibc.29d0881d94f45a2fd67bb0f8cbc432a4c2c18ce96c1e17712b721a3ae04788d2.wasm", + "tx_init_account.wasm": "tx_init_account.fff73dad57c9f1f670593939b4a0a895ed538324a9a39958e3724a51b1fc1524.wasm", + "tx_init_nft.wasm": "tx_init_nft.378007b60d8324df95f0db09b568b8924b411d6402a76e0280ce5bb95835dff2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.59c9755b31a9727c7b5908c2c404f193b9de7ffac919833c342e06e4bca31953.wasm", + "tx_init_validator.wasm": "tx_init_validator.34b7afb1af8f7b23c00812e969f89058754c8447704a9cd1acd595281c58d16a.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.7962f00c4735a88cae4363ca172e6bbe1cd0b75c7271b5203b141e29a2d039ff.wasm", + "tx_transfer.wasm": "tx_transfer.e818885f13cbe1349c8702fa848991263b4032cae6c2f68df75cf9e4a7d4b4db.wasm", + "tx_unbond.wasm": "tx_unbond.1e3a5e7e5e85cf6222af2255e8bea9658693758473aba1022304c4b5af4d7f78.wasm", + "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d61eb971b9e8eab8d8d2ddf5ff238f2de769bc227cbcb14beb6e36462102ff36.wasm", + "tx_withdraw.wasm": "tx_withdraw.76c8c25e235a9e351796caef61a783e15e289f0a8b8ebdfba6ffefb027ec4f68.wasm", + "vp_nft.wasm": "vp_nft.acd5da5193e1d02caf3a278ea88eaa22943cd280e9c84a8d381217399c8bdabf.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.cc2633219e7fe4a807fb6e25ae8c7248b35dcea239dd6b108293bfee26d3a512.wasm", + "vp_token.wasm": "vp_token.cee6c283ec93ecf78d473cf382b2ec78a891d705fa48ca5c7393c61b74bb2aa7.wasm", + "vp_user.wasm": "vp_user.62b8491c45cb9fb1b2b4f4d4590a119ba34fcca8d63539ed2717c3bef365aff1.wasm" } \ No newline at end of file From acdcc2bb2cb3a3cf238fb2be7cf3e8cd5458f168 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:06:26 +0100 Subject: [PATCH 0929/1995] Add Default impl for EventLog --- apps/src/lib/node/ledger/events/log.rs | 6 ++++++ apps/src/lib/node/ledger/shell/mod.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 7238d1df4e..57a94f5abf 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -38,6 +38,12 @@ pub struct EventLog { queue: CircularQueue, } +impl Default for EventLog { + fn default() -> Self { + Self::new(Default::default()) + } +} + impl EventLog { /// Return a new event log. pub fn new(params: Params) -> Self { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a3c178d753..6ef9e1d468 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -457,7 +457,7 @@ where ), proposal_data: HashSet::new(), // TODO: config event log params - event_log: EventLog::new(Default::default()), + event_log: EventLog::default(), } } From 44039453bf7bfd74f672cbf876a777ac397dfeac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:14:15 +0100 Subject: [PATCH 0930/1995] Rename HashString to HexEncodedHash --- apps/src/lib/node/ledger/events/log.rs | 4 ++-- .../lib/node/ledger/events/log/dumb_queries.rs | 6 +++--- apps/src/lib/node/ledger/rpc.rs | 6 +++--- shared/src/types/hash.rs | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 57a94f5abf..456f03125e 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -83,7 +83,7 @@ impl EventLog { #[cfg(test)] mod tests { - use namada::types::hash::HashString; + use namada::types::hash::HexEncodedHash; use super::*; use crate::node::ledger::events::{EventLevel, EventType}; @@ -95,7 +95,7 @@ mod tests { macro_rules! accepted { ($hash:expr) => { dumb_queries::QueryMatcher::accepted( - &HashString::try_from($hash).unwrap(), + &HexEncodedHash::try_from($hash).unwrap(), ) }; } diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index 4d74b97544..d37694f66d 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -6,7 +6,7 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` -use namada::types::hash::HashString; +use namada::types::hash::HexEncodedHash; use crate::node::ledger::events::{Event, EventType}; @@ -32,7 +32,7 @@ impl<'q> QueryMatcher<'q> { } /// Returns a query matching the given accepted hash. - pub fn accepted(hash: &'q HashString) -> Self { + pub fn accepted(hash: &'q HexEncodedHash) -> Self { Self { event_type: EventType::Accepted, attr: "hash".to_string(), @@ -41,7 +41,7 @@ impl<'q> QueryMatcher<'q> { } /// Returns a query matching the given applied hash. - pub fn applied(hash: &'q HashString) -> Self { + pub fn applied(hash: &'q HexEncodedHash) -> Self { Self { event_type: EventType::Applied, attr: "hash".to_string(), diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index e9c96fc41f..cca9d26eb2 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -4,7 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use namada::types::address::Address; -use namada::types::hash::{self, HashString}; +use namada::types::hash::{self, HexEncodedHash}; use namada::types::storage; use thiserror::Error; @@ -24,9 +24,9 @@ pub enum Path { /// Check if the given storage key exists. HasKey(storage::Key), /// Check if a transaction was accepted. - Accepted(HashString), + Accepted(HexEncodedHash), /// Check if a transaction was applied. - Applied(HashString), + Applied(HexEncodedHash), } #[derive(Debug, Clone)] diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 1a08dcd259..f80a4a72df 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -150,11 +150,11 @@ impl Value for Hash { /// A hex encoded hash. #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct HashString { +pub struct HexEncodedHash { inner: [u8; HASH_LENGTH * 2], } -impl Default for HashString { +impl Default for HexEncodedHash { fn default() -> Self { Self { inner: [0; HASH_LENGTH * 2], @@ -162,17 +162,17 @@ impl Default for HashString { } } -impl Deref for HashString { +impl Deref for HexEncodedHash { type Target = str; fn deref(&self) -> &str { - // SAFETY: We can only construct a `HashString` + // SAFETY: We can only construct a `HexEncodedHash` // from valid hex encoded data. unsafe { std::str::from_utf8_unchecked(&self.inner) } } } -impl TryFrom for HashString { +impl TryFrom for HexEncodedHash { type Error = self::Error; #[inline] @@ -181,7 +181,7 @@ impl TryFrom for HashString { } } -impl TryFrom<&str> for HashString { +impl TryFrom<&str> for HexEncodedHash { type Error = self::Error; fn try_from(hash: &str) -> HashResult { @@ -199,7 +199,7 @@ impl TryFrom<&str> for HashString { } if hash_len == HEX_LEN { - Ok(HashString { inner: buf }) + Ok(HexEncodedHash { inner: buf }) } else { Err(self::Error::NotHexEncoded) } @@ -221,7 +221,7 @@ mod tests { proptest! { #[test] fn test_hash_string(hex_hash in hex_encoded_hash_strat()) { - let _: HashString = hex_hash.try_into().unwrap(); + let _: HexEncodedHash = hex_hash.try_into().unwrap(); } } } From 7d27b63b5e27e1c252282352d382bfa5e8f6158d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:19:46 +0100 Subject: [PATCH 0931/1995] Return length err --- shared/src/types/hash.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index f80a4a72df..79e9ac9831 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -25,6 +25,8 @@ pub enum Error { ConversionFailed(std::array::TryFromSliceError), #[error("The string is not valid hex encoded data.")] NotHexEncoded, + #[error("Got a hex encoded hash length of {got}, expected 64.")] + InvalidHexHashLength { got: usize }, } /// Result for functions that may fail @@ -201,7 +203,7 @@ impl TryFrom<&str> for HexEncodedHash { if hash_len == HEX_LEN { Ok(HexEncodedHash { inner: buf }) } else { - Err(self::Error::NotHexEncoded) + Err(self::Error::InvalidHexHashLength { got: hash_len }) } } } From bc2468de93c17c5df1faa457225dea3bbb2da4a1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:23:18 +0100 Subject: [PATCH 0932/1995] Add HEX_HASH_LENGTH top level const --- shared/src/types/hash.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 79e9ac9831..d9e207b643 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -13,9 +13,12 @@ use thiserror::Error; use crate::tendermint::abci::transaction; use crate::tendermint::Hash as TmHash; -/// The length of the transaction hash string +/// The length of the raw transaction hash. pub const HASH_LENGTH: usize = 32; +/// The length of the hex encoded transaction hash. +pub const HEX_HASH_LENGTH: usize = HASH_LENGTH * 2; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { @@ -25,7 +28,9 @@ pub enum Error { ConversionFailed(std::array::TryFromSliceError), #[error("The string is not valid hex encoded data.")] NotHexEncoded, - #[error("Got a hex encoded hash length of {got}, expected 64.")] + #[error( + "Got a hex encoded hash length of {got}, expected {HEX_HASH_LENGTH}." + )] InvalidHexHashLength { got: usize }, } @@ -153,13 +158,13 @@ impl Value for Hash { /// A hex encoded hash. #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct HexEncodedHash { - inner: [u8; HASH_LENGTH * 2], + inner: [u8; HEX_HASH_LENGTH], } impl Default for HexEncodedHash { fn default() -> Self { Self { - inner: [0; HASH_LENGTH * 2], + inner: [0; HEX_HASH_LENGTH], } } } @@ -187,12 +192,11 @@ impl TryFrom<&str> for HexEncodedHash { type Error = self::Error; fn try_from(hash: &str) -> HashResult { - const HEX_LEN: usize = HASH_LENGTH * 2; - let mut hash_len = 0; - let mut buf = [0; HEX_LEN]; + let mut buf = [0; HEX_HASH_LENGTH]; - for (slot, ch) in buf.iter_mut().zip(hash.chars().take(HEX_LEN)) { + for (slot, ch) in buf.iter_mut().zip(hash.chars().take(HEX_HASH_LENGTH)) + { match ch { 'a'..='f' | 'A'..='F' | '0'..='9' => *slot = ch as u8, _ => return Err(self::Error::NotHexEncoded), @@ -200,7 +204,7 @@ impl TryFrom<&str> for HexEncodedHash { hash_len += 1; } - if hash_len == HEX_LEN { + if hash_len == HEX_HASH_LENGTH { Ok(HexEncodedHash { inner: buf }) } else { Err(self::Error::InvalidHexHashLength { got: hash_len }) From 73acacb96fb0ebb192faee59b9c7f1931ac950a5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:25:22 +0100 Subject: [PATCH 0933/1995] Use tx_hash in fn params --- apps/src/lib/node/ledger/events/log/dumb_queries.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index d37694f66d..7208419584 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -31,21 +31,21 @@ impl<'q> QueryMatcher<'q> { .unwrap_or_default() } - /// Returns a query matching the given accepted hash. - pub fn accepted(hash: &'q HexEncodedHash) -> Self { + /// Returns a query matching the given accepted transaction hash. + pub fn accepted(tx_hash: &'q HexEncodedHash) -> Self { Self { event_type: EventType::Accepted, attr: "hash".to_string(), - value: hash, + value: tx_hash, } } - /// Returns a query matching the given applied hash. - pub fn applied(hash: &'q HexEncodedHash) -> Self { + /// Returns a query matching the given applied transaction hash. + pub fn applied(tx_hash: &'q HexEncodedHash) -> Self { Self { event_type: EventType::Applied, attr: "hash".to_string(), - value: hash, + value: tx_hash, } } } From 1d37c4399b147b22ac562acbe1e594fea85673d7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 09:31:38 +0100 Subject: [PATCH 0934/1995] Make tx hashes more explicit --- apps/src/lib/node/ledger/rpc.rs | 38 ++++++++++++----------- apps/src/lib/node/ledger/shell/queries.rs | 8 ++--- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index cca9d26eb2..50d83a402f 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -24,9 +24,9 @@ pub enum Path { /// Check if the given storage key exists. HasKey(storage::Key), /// Check if a transaction was accepted. - Accepted(HexEncodedHash), + Accepted { tx_hash: HexEncodedHash }, /// Check if a transaction was applied. - Applied(HexEncodedHash), + Applied { tx_hash: HexEncodedHash }, } #[derive(Debug, Clone)] @@ -59,13 +59,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } - Path::Accepted(hash) => { - let hash: &str = hash; - write!(f, "{ACCEPTED_PREFIX}/{hash}") + Path::Accepted { tx_hash } => { + let tx_hash: &str = tx_hash; + write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } - Path::Applied(hash) => { - let hash: &str = hash; - write!(f, "{APPLIED_PREFIX}/{hash}") + Path::Applied { tx_hash } => { + let tx_hash: &str = tx_hash; + write!(f, "{APPLIED_PREFIX}/{tx_hash}") } } } @@ -94,15 +94,17 @@ impl FromStr for Path { .map_err(PathParseError::InvalidStorageKey)?; Ok(Self::HasKey(key)) } - Some((ACCEPTED_PREFIX, hash)) => { - let hash = - hash.try_into().map_err(PathParseError::InvalidHash)?; - Ok(Self::Accepted(hash)) + Some((ACCEPTED_PREFIX, tx_hash)) => { + let tx_hash = tx_hash + .try_into() + .map_err(PathParseError::InvalidTxHash)?; + Ok(Self::Accepted { tx_hash }) } - Some((APPLIED_PREFIX, hash)) => { - let hash = - hash.try_into().map_err(PathParseError::InvalidHash)?; - Ok(Self::Applied(hash)) + Some((APPLIED_PREFIX, tx_hash)) => { + let tx_hash = tx_hash + .try_into() + .map_err(PathParseError::InvalidTxHash)?; + Ok(Self::Applied { tx_hash }) } _ => Err(PathParseError::InvalidPath(s.to_string())), }, @@ -126,6 +128,6 @@ pub enum PathParseError { InvalidPath(String), #[error("Invalid storage key: {0}")] InvalidStorageKey(storage::Error), - #[error("Invalid hash: {0}")] - InvalidHash(hash::Error), + #[error("Invalid transaction hash: {0}")] + InvalidTxHash(hash::Error), } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d78c197c43..0fb379cf3d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -87,12 +87,12 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), - Path::Accepted(ref hash) => { - let matcher = dumb_queries::QueryMatcher::accepted(hash); + Path::Accepted { ref tx_hash } => { + let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) } - Path::Applied(ref hash) => { - let matcher = dumb_queries::QueryMatcher::applied(hash); + Path::Applied { ref tx_hash } => { + let matcher = dumb_queries::QueryMatcher::applied(tx_hash); self.query_event_log(matcher) } }, From d56ee5fc9ac1d6298d895d04f5875bbbb1ee91de Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 11:58:25 +0200 Subject: [PATCH 0935/1995] [fix]: Fixed bug in serializing secp sigs. Covered with tests --- shared/src/types/key/secp256k1.rs | 65 +++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 8c969261e5..67d3352ab3 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -21,6 +21,11 @@ use super::{ }; use crate::types::ethereum_events::EthAddress; +/// [`libsecp265k1::util::SIGNATURE_SIZE`] is for a traditional +/// signature on this curve. For Ethereum, an extra byte is included +/// that prevents malleability attacks. +pub const SIGNATURE_LENGTH: usize = libsecp256k1::util::SIGNATURE_SIZE + 1; + /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PublicKey(pub libsecp256k1::PublicKey); @@ -306,7 +311,7 @@ impl Serialize for Signature { // TODO: implement the line below, currently cannot support [u8; 64] // serde::Serialize::serialize(&arr, serializer) - let mut seq = serializer.serialize_tuple(arr.len())?; + let mut seq = serializer.serialize_tuple(arr.len() + 1)?; for elem in &arr[..] { seq.serialize_element(elem)?; } @@ -323,15 +328,12 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - // [`libsecp265k1::util::SIGNATURE_SIZE`] is for a traditional - // signature on this curve. For Ethereum, an extra byte is included - // that prevents malleability attacks. - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; + type Value = [u8; SIGNATURE_LENGTH]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( "an array of length {}", - libsecp256k1::util::SIGNATURE_SIZE + SIGNATURE_LENGTH, )) } @@ -339,9 +341,9 @@ impl<'de> Deserialize<'de> for Signature { where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; + let mut arr = [0u8; SIGNATURE_LENGTH]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { + for i in 0..SIGNATURE_LENGTH { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -350,10 +352,8 @@ impl<'de> Deserialize<'de> for Signature { } } - let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE + 1, - ByteArrayVisitor, - )?; + let arr_res = deserializer + .deserialize_tuple(SIGNATURE_LENGTH, ByteArrayVisitor)?; let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); @@ -577,15 +577,18 @@ impl super::SigScheme for SigScheme { mod test { use super::*; + /// test vector from https://bitcoin.stackexchange.com/a/89848 + const SECRET_KEY_HEX: &str = + "c2c72dfbff11dfb4e9d5b0a20c620c58b15bb7552753601f043db91331b0db15"; + + /// Test that we can recover an Ethereum address from + /// a public secp key. #[test] fn test_eth_address_from_secp() { - // test vector from https://bitcoin.stackexchange.com/a/89848 - let sk_hex = - "c2c72dfbff11dfb4e9d5b0a20c620c58b15bb7552753601f043db91331b0db15"; let expected_pk_hex = "a225bf565ff4ea039bccba3e26456e910cd74e4616d67ee0a166e26da6e5e55a08d0fa1659b4b547ba7139ca531f62907b9c2e72b80712f1c81ece43c33f4b8b"; let expected_eth_addr_hex = "6ea27154616a29708dce7650b475dd6b82eba6a3"; - let sk_bytes = hex::decode(sk_hex).unwrap(); + let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); let pk: PublicKey = sk.ref_to(); // We're removing the first byte with @@ -597,4 +600,34 @@ mod test { let eth_addr_hex = hex::encode(eth_addr.0); assert_eq!(expected_eth_addr_hex, eth_addr_hex); } + + /// Test serializing and then de-serializing a signature + /// with Serde is idempotent. + #[test] + fn test_roundtrip_serde() { + let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); + let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); + let to_sign = "test".as_bytes(); + let mut signature = SigScheme::sign(&sk, to_sign); + signature.1 = RecoveryId::parse(3).expect("Test failed"); + let sig_json = serde_json::to_string(&signature).expect("Test failed"); + let sig: Signature = + serde_json::from_str(&sig_json).expect("Test failed"); + assert_eq!(sig, signature) + } + + /// Test serializing and then de-serializing a signature + /// with Borsh is idempotent. + #[test] + fn test_roundtrip_borsh() { + let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); + let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); + let to_sign = "test".as_bytes(); + let mut signature = SigScheme::sign(&sk, to_sign); + signature.1 = RecoveryId::parse(3).expect("Test failed"); + let sig_bytes = signature.try_to_vec().expect("Test failed"); + let sig = Signature::try_from_slice(sig_bytes.as_slice()) + .expect("Test failed"); + assert_eq!(sig, signature); + } } From 2eeed5ab219332f90e655f4642545b6f8f26ad61 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 11:00:21 +0100 Subject: [PATCH 0936/1995] Fix default hex encoded hash impl --- shared/src/types/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index d9e207b643..cf6f882445 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -164,7 +164,7 @@ pub struct HexEncodedHash { impl Default for HexEncodedHash { fn default() -> Self { Self { - inner: [0; HEX_HASH_LENGTH], + inner: [b'0'; HEX_HASH_LENGTH], } } } From 1ccb304447671d6c9495bee3782fd9ab94d2b167 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 14:56:18 +0200 Subject: [PATCH 0937/1995] [fix]: Simplified some conversions, avoided unnecessary heap allocations --- shared/src/types/hash.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 4e63af1ad9..56654969fa 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -92,9 +92,7 @@ impl TryFrom for Hash { type Error = self::Error; fn try_from(string: String) -> HashResult { - let bytes: Vec = - Vec::from_hex(string).map_err(Error::FromStringError)?; - Self::try_from(bytes.as_slice()) + string.as_str().try_into() } } @@ -102,9 +100,10 @@ impl TryFrom<&str> for Hash { type Error = self::Error; fn try_from(string: &str) -> HashResult { - let bytes: Vec = - Vec::from_hex(string).map_err(Error::FromStringError)?; - Self::try_from(bytes.as_slice()) + Ok(Self( + <[u8; HASH_LENGTH]>::from_hex(string) + .map_err(Error::FromStringError)?, + )) } } From 45084dc6bdec5c479a6d7dc225d0507b127eef7b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 0938/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b7b8cf1cf2..5d6f4d7177 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -228,8 +211,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -240,9 +223,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -252,9 +233,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f1dfc181c9..70dc9929b4 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool #[derive( Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index db7cada8ec..08ee160662 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 76b231dea50bedc91d0bca9f10bcd27152963e53 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 0939/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 +++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - 2 files changed, 124 insertions(+), 88 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 5d6f4d7177..d347755e76 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -268,19 +267,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -336,10 +337,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -356,10 +354,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -368,13 +368,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -385,13 +387,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -402,13 +406,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -419,13 +425,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -436,58 +444,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -498,13 +531,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -535,11 +573,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 70dc9929b4..c9d3be5366 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; From b630ed94fcd69eb3ac1cc2f9d532128c6ad768fc Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 0940/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From 61a7fb837cdcc8b20f9b1310fa9d03023b8461fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:32:58 +0000 Subject: [PATCH 0941/1995] [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 1250ca7e3e..73038833d5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.536ed89ef1c74d3c6b3e005d60cc53bbab7c36935b46bb69f7287de0a54f91e1.wasm", - "tx_ibc.wasm": "tx_ibc.790e710a87d31a215c19dd29d3c3723f6bb0e22482b3a368c21a367df99aefe8.wasm", - "tx_init_account.wasm": "tx_init_account.a50110eb561a84549708b98b5841358ed87de5a9b08f8a947711c5908eb13558.wasm", - "tx_init_nft.wasm": "tx_init_nft.bee3fbefc9e6319eea9244b9abc7fa5e80a62b43a003260803989856aef8d438.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26ed1981f8f0ee5b9852dd96d7ad171707194056db5731e9915dbb1747892303.wasm", - "tx_init_validator.wasm": "tx_init_validator.8b37971addbd3ace29174db5f618f06b62115cd7b44ff1b7d756c9d4d81cbc56.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.659dbac8df06b8e6775e680ebb772c3d19733b9e14ea513c665a78ac70788610.wasm", - "tx_transfer.wasm": "tx_transfer.03a7bff45511f240ebdf92f5e4612e244cc937fda3a22ba0a77936e36d834f37.wasm", - "tx_unbond.wasm": "tx_unbond.74388ad956a8ed618974140836e97ae254ae9e8a3e24065908e3a318d55fd37b.wasm", - "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f3103ee1148954898e2cd92eb0134b34792e7a373a505f37544f389b3cd3cc88.wasm", - "tx_withdraw.wasm": "tx_withdraw.3e1619ad5db6dcb2146684bf54f2e383747fa81f1baf9d00005c4431a3d0a566.wasm", - "vp_nft.wasm": "vp_nft.31a3045be40eeabb48140d2f68db26006991dffb208f6bd394a0b6740cd5b4d0.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d4b27eadcb98ed3664028bee05fe33f8acb7a59182ca22833a77df696d1cf369.wasm", - "vp_token.wasm": "vp_token.aea6ee5649995c6352d982687c30cfe44fcf05a0cd9cb9b0b48d80768633d098.wasm", - "vp_user.wasm": "vp_user.589011534fd04c4b44fb4412b2f58ea548628dc1ba4db5d45a9aaa1f9e6f0552.wasm" + "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", + "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", + "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", + "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", + "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", + "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", + "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", + "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", + "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", + "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" } \ No newline at end of file From 9aae01bcfe100cdd2fea2d810b0884e6ffeac63d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:33:01 +0000 Subject: [PATCH 0942/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 73038833d5..4754de3ff7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", - "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", - "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", - "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", - "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", - "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", - "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", + "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", + "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", + "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", + "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", + "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", + "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", - "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", - "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", + "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", + "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" + "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" } \ No newline at end of file From 289839e387279fcffadbbf1d5d787b086c0025cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 15:33:09 +0000 Subject: [PATCH 0943/1995] [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 4754de3ff7..850b41f1b8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", - "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", - "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", - "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", - "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", - "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", - "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", - "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", - "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", - "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", - "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" + "tx_from_intent.wasm": "tx_from_intent.7e9893f8e2affcfbb4e04fe936bb18571645353da08d8e08d880971f2cfca053.wasm", + "tx_ibc.wasm": "tx_ibc.29d0881d94f45a2fd67bb0f8cbc432a4c2c18ce96c1e17712b721a3ae04788d2.wasm", + "tx_init_account.wasm": "tx_init_account.fff73dad57c9f1f670593939b4a0a895ed538324a9a39958e3724a51b1fc1524.wasm", + "tx_init_nft.wasm": "tx_init_nft.378007b60d8324df95f0db09b568b8924b411d6402a76e0280ce5bb95835dff2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.59c9755b31a9727c7b5908c2c404f193b9de7ffac919833c342e06e4bca31953.wasm", + "tx_init_validator.wasm": "tx_init_validator.34b7afb1af8f7b23c00812e969f89058754c8447704a9cd1acd595281c58d16a.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.7962f00c4735a88cae4363ca172e6bbe1cd0b75c7271b5203b141e29a2d039ff.wasm", + "tx_transfer.wasm": "tx_transfer.e818885f13cbe1349c8702fa848991263b4032cae6c2f68df75cf9e4a7d4b4db.wasm", + "tx_unbond.wasm": "tx_unbond.1e3a5e7e5e85cf6222af2255e8bea9658693758473aba1022304c4b5af4d7f78.wasm", + "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d61eb971b9e8eab8d8d2ddf5ff238f2de769bc227cbcb14beb6e36462102ff36.wasm", + "tx_withdraw.wasm": "tx_withdraw.76c8c25e235a9e351796caef61a783e15e289f0a8b8ebdfba6ffefb027ec4f68.wasm", + "vp_nft.wasm": "vp_nft.acd5da5193e1d02caf3a278ea88eaa22943cd280e9c84a8d381217399c8bdabf.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.cc2633219e7fe4a807fb6e25ae8c7248b35dcea239dd6b108293bfee26d3a512.wasm", + "vp_token.wasm": "vp_token.cee6c283ec93ecf78d473cf382b2ec78a891d705fa48ca5c7393c61b74bb2aa7.wasm", + "vp_user.wasm": "vp_user.62b8491c45cb9fb1b2b4f4d4590a119ba34fcca8d63539ed2717c3bef365aff1.wasm" } \ No newline at end of file From f5ff5c863c2e02aacf480906dc587df51a705a1c Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 0944/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 29 ++++ apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 62 +++++--- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 57 ++++++- 7 files changed, 287 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index ad3d2f5fcb..319b138e51 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -16,6 +16,9 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block Epoch, + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix @@ -24,6 +27,16 @@ pub enum Path { HasKey(storage::Key), } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -34,6 +47,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -52,6 +68,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } } } } @@ -64,6 +87,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0ffbe3fe6e..18c8e5c0d6 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,22 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -20,6 +29,7 @@ use crate::facade::tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -86,6 +96,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, }, Err(err) => response::Query { code: 1, @@ -309,6 +327,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 89e220335f..21908a454d 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -83,7 +84,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.store.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -118,9 +119,9 @@ impl BridgePoolTree { } } - /// Return the root as a [struct@`Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + /// Return the root as a [`struct@Hash`] type. + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store @@ -344,6 +345,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { use std::array; @@ -406,9 +432,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.update_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -443,7 +468,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -499,9 +524,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -635,7 +659,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(array::from_ref(&key), vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -669,7 +693,7 @@ mod test_bridge_pool_tree { vec![transfers.remove(0)], ) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -701,7 +725,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -731,7 +755,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -761,7 +785,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -791,7 +815,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -822,7 +846,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -889,7 +913,7 @@ mod test_bridge_pool_tree { values.push(transfer); } let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 1d175e83d2..bd27e83857 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 61f9ced6be..a8056a0dfc 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -21,7 +21,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 5491b78685..69ea31e37f 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 36a378b8db..b33fee72ff 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,11 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; -use crate::types::keccak; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -55,14 +56,16 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl keccak::encode::Encode for PendingTransfer { +impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::String(self.gas_fee.payer.to_string()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, nonce] + vec![version, namespace, from, to, amount, fee, nonce] } } @@ -96,3 +99,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From b0a5de16bdd1eebad07e6ddcd3a161247fdca600 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 0945/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 7 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 9 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 8 files changed, 235 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fcc012880..bbd7b14246 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,6 +4330,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 18c8e5c0d6..ec62affde7 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,13 +5,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -20,7 +20,8 @@ use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -336,13 +337,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -392,12 +405,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -410,22 +418,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -440,6 +440,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -841,10 +842,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1008,4 +1019,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..7c2cfbec2b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,6 +104,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 21908a454d..7e49c197b3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index bd27e83857..92fbbcb306 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index a8056a0dfc..0c1357af93 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -430,15 +430,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl Into, ) -> Result<(u64, i64)> { + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); self.block.tree.update(key, value.clone())?; - let len = value.as_ref().len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index b33fee72ff..85f2bd99b1 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -43,6 +44,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -88,6 +90,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 08ee160662..3d20300a41 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -245,6 +245,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -267,6 +268,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From b8fcea5d969709954bde53e7d4d1d888e3f25e90 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 0946/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/mod.rs | 13 ++++- apps/src/lib/node/ledger/shell/queries.rs | 18 +++---- shared/Cargo.toml | 1 - shared/src/types/key/secp256k1.rs | 60 ++++++++++++++++------- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbd7b14246..5fcc012880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4330,7 +4330,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 399c6b14b6..62d3b63af0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -857,10 +857,19 @@ mod test_utils { sig_bytes[0] = sig_bytes[0].wrapping_add(1); common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + common::Signature::Secp256k1(secp256k1::Signature( + ref sig, + ref recovery_id, + )) => { let mut sig_bytes = sig.serialize(); + let recovery_id_bytes = recovery_id.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) + let bytes: [u8; 65] = + [sig_bytes.as_slice(), [recovery_id_bytes].as_slice()] + .concat() + .try_into() + .unwrap(); + common::Signature::Secp256k1((&bytes).try_into().unwrap()) } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index ec62affde7..d3fe8e2e18 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,6 +16,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; @@ -1044,7 +1045,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1081,7 +1082,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1089,7 +1090,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1097,7 +1098,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1141,7 +1142,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1155,14 +1156,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7c2cfbec2b..ccb4a3752d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,7 +104,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 96b892fdbf..7ab6172774 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -266,7 +267,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(pub libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature, pub RecoveryId); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; @@ -304,6 +305,7 @@ impl Serialize for Signature { for elem in &arr[..] { seq.serialize_element(elem)?; } + seq.serialize_element(&self.1.serialize())?; seq.end() } } @@ -316,7 +318,7 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( @@ -325,13 +327,13 @@ impl<'de> Deserialize<'de> for Signature { )) } - fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + fn visit_seq(self, mut seq: A) -> Result<[u8; 65], A::Error> where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -341,23 +343,34 @@ impl<'de> Deserialize<'de> for Signature { } let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE, + libsecp256k1::util::SIGNATURE_SIZE + 1, ByteArrayVisitor, )?; - let sig = libsecp256k1::Signature::parse_standard(&arr_res) + let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); + let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); - Ok(Signature(sig.unwrap())) + Ok(Signature( + sig.unwrap(), + RecoveryId::parse(arr_res[64]).map_err(Error::custom)?, + )) } } impl BorshDeserialize for Signature { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first + let (sig_bytes, recovery_id) = BorshDeserialize::deserialize(buf)?; + Ok(Signature( - libsecp256k1::Signature::parse_standard( - &(BorshDeserialize::deserialize(buf)?), - ) - .map_err(|e| { + libsecp256k1::Signature::parse_standard(&sig_bytes).map_err( + |e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + }, + )?, + RecoveryId::parse(recovery_id).map_err(|e| { std::io::Error::new( ErrorKind::InvalidInput, format!("Error decoding secp256k1 signature: {}", e), @@ -369,7 +382,10 @@ impl BorshDeserialize for Signature { impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - BorshSerialize::serialize(&self.0.serialize(), writer) + BorshSerialize::serialize( + &(self.0.serialize(), self.1.serialize()), + writer, + ) } } @@ -405,12 +421,19 @@ impl PartialOrd for Signature { } } -impl TryFrom<&[u8; 64]> for Signature { +impl TryFrom<&[u8; 65]> for Signature { type Error = ParseSignatureError; - fn try_from(sig: &[u8; 64]) -> Result { - libsecp256k1::Signature::parse_standard(sig) - .map(Self) + fn try_from(sig: &[u8; 65]) -> Result { + let sig_bytes = sig[..64].try_into().unwrap(); + let recovery_id = RecoveryId::parse(sig[64]).map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + })?; + libsecp256k1::Signature::parse_standard(&sig_bytes) + .map(|sig| Self(sig, recovery_id)) .map_err(|err| { ParseSignatureError::InvalidEncoding(std::io::Error::new( ErrorKind::Other, @@ -467,7 +490,8 @@ impl super::SigScheme for SigScheme { 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) + let (sig, recovery_id) = libsecp256k1::sign(&message, &keypair.0); + Signature(sig, recovery_id) } } From 668740995a4aad19c84d07c7f1be6335e6029924 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 15:20:59 +0200 Subject: [PATCH 0947/1995] [feat]: Fixed secp256k key signatures --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 13 ++++++++---- shared/src/types/key/dkg_session_keys.rs | 7 ++++-- shared/src/types/key/ed25519.rs | 13 ++++++++---- shared/src/types/key/mod.rs | 27 +++++++++++++++++++++--- shared/src/types/key/secp256k1.rs | 27 +++++++++++++++++------- wasm/tx_template/Cargo.lock | 7 ++++++ wasm/vp_template/Cargo.lock | 7 ++++++ wasm/wasm_source/Cargo.lock | 7 ++++++ wasm_for_tests/wasm_source/Cargo.lock | 7 ++++++ 11 files changed, 96 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fcc012880..7a356d5887 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4303,6 +4303,7 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 59196e1ff0..633367053c 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -70,7 +71,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -78,7 +79,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -196,7 +199,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -204,7 +207,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index dbcf9fe04c..052461de9a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -106,7 +107,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -114,7 +115,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -203,7 +206,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -211,7 +214,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1c5ac85558..5a16838455 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -7,6 +7,7 @@ use std::hash::Hash; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -85,7 +86,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -96,7 +97,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -107,7 +108,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] @@ -356,6 +357,26 @@ 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 + } + common::PublicKey::Secp256k1(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + +/// 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 { + HEXUPPER.encode(raw_hash.as_ref()) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 7ab6172774..9c8825dd98 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -115,7 +116,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -123,7 +124,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -245,7 +248,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -253,7 +256,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } @@ -396,10 +401,16 @@ impl BorshSchema for Signature { 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 }; + // Encoded as `([u8; SIGNATURE_SIZE], u8)` + let signature = + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::declaration(); + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::add_definitions_recursively( + definitions, + ); + let recovery = "u8".into(); + let definition = borsh::schema::Definition::Tuple { + elements: vec![signature, recovery], + }; definitions.insert(Self::declaration(), definition); } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 64bad284a2..b59950f447 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3a3058df34..392c010281 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 2b891bf4e1..4905239cb0 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1402,6 +1408,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f0cabb9569..56e8a94bfb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -620,6 +620,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1403,6 +1409,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", From 50bf4abedfc3c5f60586052257e07b1dd5c2cabe Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 0948/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++- .../ledger/eth_bridge/storage/bridge_pool.rs | 5 ++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d3fe8e2e18..1da0b363f2 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -373,7 +373,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1186,4 +1186,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7e49c197b3..81027b23d3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -161,6 +161,11 @@ impl BridgePoolTree { } leaves.insert(hash); } + if !leaves.is_subset(&self.store) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; From d2aa33e5cf765fac6455fba87a60ffe758b2906f Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 0949/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 11 +++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1da0b363f2..3175fe14fd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -431,6 +431,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1182,6 +1184,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1224,7 +1228,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 81027b23d3..cbb7aebbe3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -164,7 +164,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.store) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; @@ -355,8 +356,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -371,12 +372,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 85f2bd99b1..44ebc7cb7e 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -58,8 +58,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::String(self.gas_fee.payer.to_string()); @@ -67,7 +67,7 @@ impl Encode for PendingTransfer { let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, nonce] + [version, namespace, from, to, amount, fee, nonce] } } @@ -116,17 +116,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -138,13 +136,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9c8825dd98..89016530b0 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -419,6 +421,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From 9c8b07617837136db2a447e53b702e7f4280ce60 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 0950/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3175fe14fd..f376c675aa 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -429,6 +429,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1182,6 +1184,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 44ebc7cb7e..3fb898604e 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -132,6 +133,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -140,11 +143,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..24e0145ac5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -301,6 +301,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 5d2d88aacfa0b1ef314c901c3ef7af90eeb9691f Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 0951/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index f376c675aa..cc3a1b2991 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -18,7 +18,6 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 0dbfbaec2c894c8b6bf8617585180a868f147120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 14 Oct 2022 11:14:11 +0200 Subject: [PATCH 0952/1995] shared/storage: fix the height recorded for a new epoch --- shared/src/ledger/storage/mod.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1df873edd6..7c7ee893e0 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -611,7 +611,7 @@ where let evidence_max_age_num_blocks: u64 = 100000; self.block .pred_epochs - .new_epoch(height + 1, evidence_max_age_num_blocks); + .new_epoch(height, evidence_max_age_num_blocks); tracing::info!("Began a new epoch {}", self.block.epoch); } self.update_epoch_in_merkle_tree()?; @@ -830,12 +830,20 @@ 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)); - assert_eq!(storage.block.pred_epochs.get_epoch(block_height + 1), Some(epoch_before.next())); + assert_eq!( + storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), + Some(epoch_before)); + 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.block.pred_epochs.get_epoch(block_height), Some(epoch_before)); - assert_eq!(storage.block.pred_epochs.get_epoch(block_height + 1), Some(epoch_before)); + assert_eq!( + storage.block.pred_epochs.get_epoch(BlockHeight(block_height.0 - 1)), + Some(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); From 2234ecb4403955fde56a4faf6fd8be792afe4b7d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 14:59:04 +0100 Subject: [PATCH 0953/1995] Ignore failing unit test for now --- apps/src/lib/node/ledger/shell/queries.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cd6393e2b0..d15191baab 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -709,6 +709,17 @@ mod test_queries { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as /// expected. #[test] + #[ignore] + // TODO: we should fix this test to cope with epoch changes only + // happening at the first block of a new epoch. an erroneous change + // was introduced to the ledger, that updated the epoch correctly + // at the first block of the new epoch, but recorded `height + 1` + // instead of the actual height of the epoch change. since this + // test depended on that erroneous logic to pass, it's busted. + // + // linked issues: + // - + // - fn test_can_send_validator_set_update() { let (mut shell, _recv, _) = test_utils::setup_at_height(0u64); From 038360dac981920ef804c1bca635bb5b011bf0de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 18 Oct 2022 13:59:17 +0000 Subject: [PATCH 0954/1995] [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 1250ca7e3e..417c29a388 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.536ed89ef1c74d3c6b3e005d60cc53bbab7c36935b46bb69f7287de0a54f91e1.wasm", - "tx_ibc.wasm": "tx_ibc.790e710a87d31a215c19dd29d3c3723f6bb0e22482b3a368c21a367df99aefe8.wasm", - "tx_init_account.wasm": "tx_init_account.a50110eb561a84549708b98b5841358ed87de5a9b08f8a947711c5908eb13558.wasm", - "tx_init_nft.wasm": "tx_init_nft.bee3fbefc9e6319eea9244b9abc7fa5e80a62b43a003260803989856aef8d438.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.26ed1981f8f0ee5b9852dd96d7ad171707194056db5731e9915dbb1747892303.wasm", - "tx_init_validator.wasm": "tx_init_validator.8b37971addbd3ace29174db5f618f06b62115cd7b44ff1b7d756c9d4d81cbc56.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.659dbac8df06b8e6775e680ebb772c3d19733b9e14ea513c665a78ac70788610.wasm", - "tx_transfer.wasm": "tx_transfer.03a7bff45511f240ebdf92f5e4612e244cc937fda3a22ba0a77936e36d834f37.wasm", - "tx_unbond.wasm": "tx_unbond.74388ad956a8ed618974140836e97ae254ae9e8a3e24065908e3a318d55fd37b.wasm", - "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f3103ee1148954898e2cd92eb0134b34792e7a373a505f37544f389b3cd3cc88.wasm", - "tx_withdraw.wasm": "tx_withdraw.3e1619ad5db6dcb2146684bf54f2e383747fa81f1baf9d00005c4431a3d0a566.wasm", - "vp_nft.wasm": "vp_nft.31a3045be40eeabb48140d2f68db26006991dffb208f6bd394a0b6740cd5b4d0.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d4b27eadcb98ed3664028bee05fe33f8acb7a59182ca22833a77df696d1cf369.wasm", - "vp_token.wasm": "vp_token.aea6ee5649995c6352d982687c30cfe44fcf05a0cd9cb9b0b48d80768633d098.wasm", - "vp_user.wasm": "vp_user.589011534fd04c4b44fb4412b2f58ea548628dc1ba4db5d45a9aaa1f9e6f0552.wasm" + "tx_from_intent.wasm": "tx_from_intent.37fcd749502d6874f00784c9c084cb0466908d6cd3b715f04caa5862b47385d8.wasm", + "tx_ibc.wasm": "tx_ibc.ca3b8328228baa5f815461bf9a9dadd142535fdbd5a164d8894a1003bf57d392.wasm", + "tx_init_account.wasm": "tx_init_account.6b2c033d3c973cdcb8c070cbbb0f864e67b42711d93291e36e7b76dc73af169e.wasm", + "tx_init_nft.wasm": "tx_init_nft.9296016fac6b3d834d74a239f83edb275987962d0cba98aa8268e1e0babee0df.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2a312e8127a49a7a6bc7bb9242f5073ab51a0c6dda480d586cbce2fc6d8506c2.wasm", + "tx_init_validator.wasm": "tx_init_validator.6a476319971808794557e5ea111f1f86a91ee09bd83582056ac743f285d42eb0.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.7cdda2f5a2b951fd4d3b35a8ed6d252f54b0508839b0ffb2d82766faa09c5710.wasm", + "tx_transfer.wasm": "tx_transfer.cff70d53fc7a8b54f9b41ae518498119345e985d1f4cca2757760560d0a73d69.wasm", + "tx_unbond.wasm": "tx_unbond.50a1fdc2a18faf9873a391162e11454afebeffc8cdc946f0459ff53f55e09a90.wasm", + "tx_update_vp.wasm": "tx_update_vp.b0f682a2d5e621a3c5f76e4c2bf272d54e612fe63847e2d746a6d452cefc7823.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.48e6664b189ec7a2d18a4eec173911add5d9f1374ffb8904919f39660a411f96.wasm", + "tx_withdraw.wasm": "tx_withdraw.6d3e5bb0ef93e39d3781a33b6192a3804be614206127263fde048a5c85a4555c.wasm", + "vp_nft.wasm": "vp_nft.4f7f30282a66c2772a86b8a8f8d5e19944a66a0ee169db1ae662abda550a7f7e.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.279b012ef4c9a215c771141adf23126a0238da6dd033b971228640112b6bb3be.wasm", + "vp_token.wasm": "vp_token.447c5b3f55b2e7c66ee0094d8d58de8b01cda30ac177e29f14e69db44d909604.wasm", + "vp_user.wasm": "vp_user.7cfb15d4047480702f696b9576050ae22f1ca7cf1df454d457e5a8d61f766d8a.wasm" } \ No newline at end of file From e88eeac2f7eae59466fce12b22f02d50415fd4f6 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 16:06:12 +0200 Subject: [PATCH 0955/1995] [fix]: Added some more logging --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index d347755e76..b9dc885e04 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -120,7 +120,10 @@ where if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + incorrect key in the pending transaction pool: {}.\n \ + Expected key: {}", + key, + pending_key ); return Ok(false); } @@ -131,7 +134,12 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool: {:?}.\n \ + Expected: {:?}", + transfer, + pending + ); return Ok(false); } From 63f651817dfd6d6a79dd2a77b92d9151b700968c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 18 Oct 2022 14:13:54 +0000 Subject: [PATCH 0956/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7f6b64fea3..840bc7d56f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.be941c5af5bb2de3615c684b3b80d6231a7f44773dfcb7188a60e27ec0dcdaf7.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.a6df614cfbf38fcd84e4368b88456cf407ffa3934f91b25d68e2c8f6108ea589.wasm", + "tx_ibc.wasm": "tx_ibc.4d18e45f8b72bbf66a48f500472ce21a8d02bd7e0fee787c74b604a8ce48ffde.wasm", + "tx_init_account.wasm": "tx_init_account.bf9835eab5a5cf872267a17708ce5803336de596f26c603b2eaad4814d03e7e1.wasm", + "tx_init_nft.wasm": "tx_init_nft.c02075c35862b50a35a3ee5121a12c1cea3dcf2b8a79e942dc3106c1727422ee.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.38da4be308ff0407c763a169b967ff654c9c99339bc3c118e0e3da9da84b8add.wasm", + "tx_init_validator.wasm": "tx_init_validator.dc065d86be11001cee332abb6f72ac6a76775e2ff277f593d8435a6e2fbf319e.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1fe84c7be195e17c60acd51cfbe29594ee352d2b35a0c5b2d36ea89692f5581f.wasm", + "tx_transfer.wasm": "tx_transfer.8fb9fc436b802d0bd36b77821a9fb8327f584b48f2bf75ad8dc8c70b6a2c1309.wasm", + "tx_unbond.wasm": "tx_unbond.d5ec85674354fe95037f93f53f87415bd463a1f3c75832ea937783f6e495deae.wasm", + "tx_update_vp.wasm": "tx_update_vp.1a295a35cc0054c0ca2d4a2d99edaf50294912c8bbcfe0e3e5b01dbb8f4845b3.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.dbb668fb34d71f4ab983174835a46c7de70499f897e62c297686ba978d50d41d.wasm", + "tx_withdraw.wasm": "tx_withdraw.8cd2c344f74295676c9ac06c038822c7aacd4429008f988a99bad3497d18db89.wasm", + "vp_nft.wasm": "vp_nft.5d6daff6327e7b18bbffc761ecbc504cea221f07bbfa4cc536653cc83ef2d1b7.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3be4e95e71075c6a83faa9760cd63203743fa1cc996745a0d306d46e9d63375c.wasm", + "vp_token.wasm": "vp_token.04084aa211dd7ee505d4d8a005f5d5a13979b6b63950978fa0f5c9bc9bef318e.wasm", + "vp_user.wasm": "vp_user.9d23848f42fd9e2abbafa8aa240041e5faaba3617a856a385de5d3e6d2652daf.wasm" } \ No newline at end of file From 9e7858732f8e02a77b8ac7924ff0f67add7ff542 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 09:33:44 +0100 Subject: [PATCH 0957/1995] Removed the stupid hex encoded hash type Closes #636 Co-authored-by: satan --- apps/src/lib/node/ledger/events/log.rs | 18 ++-- .../node/ledger/events/log/dumb_queries.rs | 22 +++-- apps/src/lib/node/ledger/rpc.rs | 8 +- apps/src/lib/node/ledger/shell/queries.rs | 6 +- shared/src/types/hash.rs | 87 +++++-------------- 5 files changed, 49 insertions(+), 92 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs index 456f03125e..f3e655469a 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/apps/src/lib/node/ledger/events/log.rs @@ -67,14 +67,10 @@ impl EventLog { /// Returns a new iterator over this [`EventLog`]. #[inline] - pub fn iter_with_matcher<'query, 'log>( - &'log self, - matcher: dumb_queries::QueryMatcher<'query>, - ) -> impl Iterator + 'query - where - // the log should outlive the query - 'log: 'query, - { + pub fn iter_with_matcher( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> impl Iterator { self.queue .iter() .filter(move |&event| matcher.matches(event)) @@ -83,7 +79,7 @@ impl EventLog { #[cfg(test)] mod tests { - use namada::types::hash::HexEncodedHash; + use namada::types::hash::Hash; use super::*; use crate::node::ledger::events::{EventLevel, EventType}; @@ -94,9 +90,7 @@ mod tests { /// An accepted tx hash query. macro_rules! accepted { ($hash:expr) => { - dumb_queries::QueryMatcher::accepted( - &HexEncodedHash::try_from($hash).unwrap(), - ) + dumb_queries::QueryMatcher::accepted(Hash::try_from($hash).unwrap()) }; } diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index 7208419584..d4f569b8c9 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -6,20 +6,20 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` -use namada::types::hash::HexEncodedHash; +use namada::types::hash::Hash; use crate::node::ledger::events::{Event, EventType}; /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. #[derive(Debug, Clone)] -pub struct QueryMatcher<'q> { +pub struct QueryMatcher { event_type: EventType, attr: String, - value: &'q str, + value: Hash, } -impl<'q> QueryMatcher<'q> { +impl QueryMatcher { /// Checks if this [`QueryMatcher`] validates the /// given [`Event`]. pub fn matches(&self, event: &Event) -> bool { @@ -27,12 +27,18 @@ impl<'q> QueryMatcher<'q> { && event .attributes .get(&self.attr) - .map(|value| value == self.value) + .and_then(|value| { + value + .as_str() + .try_into() + .map(|v: Hash| v == self.value) + .ok() + }) .unwrap_or_default() } /// Returns a query matching the given accepted transaction hash. - pub fn accepted(tx_hash: &'q HexEncodedHash) -> Self { + pub fn accepted(tx_hash: Hash) -> Self { Self { event_type: EventType::Accepted, attr: "hash".to_string(), @@ -41,7 +47,7 @@ impl<'q> QueryMatcher<'q> { } /// Returns a query matching the given applied transaction hash. - pub fn applied(tx_hash: &'q HexEncodedHash) -> Self { + pub fn applied(tx_hash: Hash) -> Self { Self { event_type: EventType::Applied, attr: "hash".to_string(), @@ -61,7 +67,7 @@ mod tests { let matcher = QueryMatcher { event_type: EventType::Accepted, attr: "hash".to_string(), - value: "DEADBEEF", + value: "DEADBEEF".try_into().unwrap(), }; let tests = { diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 50d83a402f..4cbedca8bc 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -4,7 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use namada::types::address::Address; -use namada::types::hash::{self, HexEncodedHash}; +use namada::types::hash::{self, Hash}; use namada::types::storage; use thiserror::Error; @@ -24,9 +24,9 @@ pub enum Path { /// Check if the given storage key exists. HasKey(storage::Key), /// Check if a transaction was accepted. - Accepted { tx_hash: HexEncodedHash }, + Accepted { tx_hash: Hash }, /// Check if a transaction was applied. - Applied { tx_hash: HexEncodedHash }, + Applied { tx_hash: Hash }, } #[derive(Debug, Clone)] @@ -60,11 +60,9 @@ impl Display for Path { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } Path::Accepted { tx_hash } => { - let tx_hash: &str = tx_hash; write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } Path::Applied { tx_hash } => { - let tx_hash: &str = tx_hash; write!(f, "{APPLIED_PREFIX}/{tx_hash}") } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0fb379cf3d..3d435bd5da 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -87,11 +87,11 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), - Path::Accepted { ref tx_hash } => { + Path::Accepted { tx_hash } => { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) } - Path::Applied { ref tx_hash } => { + Path::Applied { tx_hash } => { let matcher = dumb_queries::QueryMatcher::applied(tx_hash); self.query_event_log(matcher) } @@ -107,7 +107,7 @@ where /// Query events in the event log matching the given query. fn query_event_log( &self, - matcher: dumb_queries::QueryMatcher<'_>, + matcher: dumb_queries::QueryMatcher, ) -> response::Query { let value = self .event_log() diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index cf6f882445..4a80b47fcb 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use arse_merkle_tree::traits::Value; use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex::FromHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -26,12 +27,8 @@ pub enum Error { Temporary { error: String }, #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), - #[error("The string is not valid hex encoded data.")] - NotHexEncoded, - #[error( - "Got a hex encoded hash length of {got}, expected {HEX_HASH_LENGTH}." - )] - InvalidHexHashLength { got: usize }, + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError), } /// Result for functions that may fail @@ -95,6 +92,25 @@ impl TryFrom<&[u8]> for Hash { } } +impl TryFrom for Hash { + type Error = self::Error; + + fn try_from(string: String) -> HashResult { + string.as_str().try_into() + } +} + +impl TryFrom<&str> for Hash { + type Error = self::Error; + + fn try_from(string: &str) -> HashResult { + Ok(Self( + <[u8; HASH_LENGTH]>::from_hex(string) + .map_err(Error::FromStringError)?, + )) + } +} + impl From for transaction::Hash { fn from(hash: Hash) -> Self { Self::new(hash.0) @@ -155,63 +171,6 @@ impl Value for Hash { } } -/// A hex encoded hash. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct HexEncodedHash { - inner: [u8; HEX_HASH_LENGTH], -} - -impl Default for HexEncodedHash { - fn default() -> Self { - Self { - inner: [b'0'; HEX_HASH_LENGTH], - } - } -} - -impl Deref for HexEncodedHash { - type Target = str; - - fn deref(&self) -> &str { - // SAFETY: We can only construct a `HexEncodedHash` - // from valid hex encoded data. - unsafe { std::str::from_utf8_unchecked(&self.inner) } - } -} - -impl TryFrom for HexEncodedHash { - type Error = self::Error; - - #[inline] - fn try_from(hash: String) -> HashResult { - hash.as_str().try_into() - } -} - -impl TryFrom<&str> for HexEncodedHash { - type Error = self::Error; - - fn try_from(hash: &str) -> HashResult { - let mut hash_len = 0; - let mut buf = [0; HEX_HASH_LENGTH]; - - for (slot, ch) in buf.iter_mut().zip(hash.chars().take(HEX_HASH_LENGTH)) - { - match ch { - 'a'..='f' | 'A'..='F' | '0'..='9' => *slot = ch as u8, - _ => return Err(self::Error::NotHexEncoded), - } - hash_len += 1; - } - - if hash_len == HEX_HASH_LENGTH { - Ok(HexEncodedHash { inner: buf }) - } else { - Err(self::Error::InvalidHexHashLength { got: hash_len }) - } - } -} - #[cfg(test)] mod tests { use proptest::prelude::*; @@ -227,7 +186,7 @@ mod tests { proptest! { #[test] fn test_hash_string(hex_hash in hex_encoded_hash_strat()) { - let _: HexEncodedHash = hex_hash.try_into().unwrap(); + let _: Hash = hex_hash.try_into().unwrap(); } } } From ce1a0f1d2bc729d4cc4aa08662aa09965005ab23 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 09:45:09 +0100 Subject: [PATCH 0958/1995] Fix unit test --- apps/src/lib/node/ledger/events/log/dumb_queries.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs index d4f569b8c9..d7e2c51630 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -64,10 +64,13 @@ mod tests { /// Test if query matching is working as expected. #[test] fn test_tm_query_matching() { + const HASH: &str = + "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; + let matcher = QueryMatcher { event_type: EventType::Accepted, attr: "hash".to_string(), - value: "DEADBEEF".try_into().unwrap(), + value: HASH.try_into().unwrap(), }; let tests = { @@ -76,7 +79,7 @@ mod tests { level: EventLevel::Block, attributes: { let mut attrs = std::collections::HashMap::new(); - attrs.insert("hash".to_string(), "DEADBEEF".to_string()); + attrs.insert("hash".to_string(), HASH.to_string()); attrs }, }; @@ -87,7 +90,7 @@ mod tests { level: EventLevel::Block, attributes: { let mut attrs = std::collections::HashMap::new(); - attrs.insert("hash".to_string(), "DEADBEEF".to_string()); + attrs.insert("hash".to_string(), HASH.to_string()); attrs }, }; From 0c6d3ccd75c347bab2d7d8773af5dd5bb1e49d58 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 12:33:08 +0100 Subject: [PATCH 0959/1995] WIP: Query tx status --- apps/src/lib/client/rpc.rs | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3f60f5c414..2c05acf36e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,6 +30,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; +use tokio::time::Instant; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -40,8 +41,57 @@ use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, HttpClient, Order, SubscriptionClient, WebSocketClient, }; +use crate::node::ledger::events::Event; use crate::node::ledger::rpc::Path; +/// Query the status of a given transaction. +/// +/// If a response is not delivered until `deadline`, we exit the cli with an +/// error. +pub async fn query_tx_status( + status: TxEventQuery, + args: args::Query, + deadline: Instant, +) -> Vec { + tokio::time::timeout_at(deadline, async move { + let client = HttpClient::new(args.ledger_address).unwrap(); + loop { + let data = vec![]; + let response = client + .abci_query(Some(status.clone().into()), data, None, false) + .await + .unwrap(); + let events = match response.code { + Code::Ok => { + match Vec::::try_from_slice(&response.value[..]) { + Ok(events) => events, + Err(err) => { + eprintln!("Error decoding the event value: {err}"); + return Err(()); + } + } + } + Code::Err(err) => { + eprintln!( + "Error in the query {} (error code {})", + response.info, err + ); + return Err(()); + } + }; + if events.len() > 0 { + break events; + } + } + }) + .await + .map_err(|_| { + eprintln!("Transaction status query deadline of {deadline} exceeded"); + }) + .and_then(|result| result) + .unwrap_or_else(|| cli::safe_exit(1)) +} + /// Query the epoch of the last committed block pub async fn query_epoch(args: args::Query) -> Epoch { let client = HttpClient::new(args.ledger_address).unwrap(); @@ -1440,6 +1490,17 @@ impl TxEventQuery { TxEventQuery::Applied(tx_hash) => tx_hash, } } + + /// The path to the ABCI query this [`TxEventQuery`] can perform. + fn query_path(&self) -> String {} +} + +impl From for crate::facade::tendermint::abci::Path { + fn from(tx_query: TxEventQuery) -> Self { + format!("{}/{}", tx_query.event_type(), tx_query.tx_hash()) + .parse() + .expect("This operation is infallible") + } } /// Transaction event queries are semantically a subset of general queries From 643169e38751b5af5483a744c458f0f320b65817 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:38:20 +0100 Subject: [PATCH 0960/1995] Query tx status fixes --- apps/src/lib/client/rpc.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 2c05acf36e..0fbed82f3b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -67,7 +67,7 @@ pub async fn query_tx_status( Ok(events) => events, Err(err) => { eprintln!("Error decoding the event value: {err}"); - return Err(()); + break Err(()); } } } @@ -76,20 +76,22 @@ pub async fn query_tx_status( "Error in the query {} (error code {})", response.info, err ); - return Err(()); + break Err(()); } }; - if events.len() > 0 { - break events; + if !events.is_empty() { + break Ok(events); } } }) .await .map_err(|_| { - eprintln!("Transaction status query deadline of {deadline} exceeded"); + eprintln!( + "Transaction status query deadline of {deadline:#?} exceeded" + ); }) .and_then(|result| result) - .unwrap_or_else(|| cli::safe_exit(1)) + .unwrap_or_else(|_| cli::safe_exit(1)) } /// Query the epoch of the last committed block @@ -1490,9 +1492,6 @@ impl TxEventQuery { TxEventQuery::Applied(tx_hash) => tx_hash, } } - - /// The path to the ABCI query this [`TxEventQuery`] can perform. - fn query_path(&self) -> String {} } impl From for crate::facade::tendermint::abci::Path { From e301b4fa2fd1508050727bd7a26e89422b248cb2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:45:15 +0100 Subject: [PATCH 0961/1995] Query tx status backoff --- apps/src/lib/client/rpc.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0fbed82f3b..2a4b88f9b6 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,7 +30,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use tokio::time::Instant; +use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -55,6 +55,10 @@ pub async fn query_tx_status( ) -> Vec { tokio::time::timeout_at(deadline, async move { let client = HttpClient::new(args.ledger_address).unwrap(); + + const ONE_SECOND: Duration = Duration::from_secs(1); + let mut backoff = ONE_SECOND; + loop { let data = vec![]; let response = client @@ -82,6 +86,10 @@ pub async fn query_tx_status( if !events.is_empty() { break Ok(events); } + // simple linear backoff - if an event is not available, + // increase the backoff duration by one second + tokio::time::sleep(ONE_SECOND).await; + backoff += ONE_SECOND; } }) .await From 8fd6e8b71cd22e507c8788cd3a2fa6e52e20df85 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:49:01 +0100 Subject: [PATCH 0962/1995] Return a single event from query tx status --- apps/src/lib/client/rpc.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 2a4b88f9b6..5cef0815e1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -52,7 +52,7 @@ pub async fn query_tx_status( status: TxEventQuery, args: args::Query, deadline: Instant, -) -> Vec { +) -> Event { tokio::time::timeout_at(deadline, async move { let client = HttpClient::new(args.ledger_address).unwrap(); @@ -65,7 +65,7 @@ pub async fn query_tx_status( .abci_query(Some(status.clone().into()), data, None, false) .await .unwrap(); - let events = match response.code { + let mut events = match response.code { Code::Ok => { match Vec::::try_from_slice(&response.value[..]) { Ok(events) => events, @@ -83,8 +83,9 @@ pub async fn query_tx_status( break Err(()); } }; - if !events.is_empty() { - break Ok(events); + if let Some(e) = events.pop() { + // we should only have one event matching the query + break Ok(e); } // simple linear backoff - if an event is not available, // increase the backoff duration by one second From 8003e38d28479fbc0497eb874ec755603ebd2588 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 13:50:25 +0100 Subject: [PATCH 0963/1995] Use backoff time in sleep --- apps/src/lib/client/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5cef0815e1..960522f9da 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -89,7 +89,7 @@ pub async fn query_tx_status( } // simple linear backoff - if an event is not available, // increase the backoff duration by one second - tokio::time::sleep(ONE_SECOND).await; + tokio::time::sleep(backoff).await; backoff += ONE_SECOND; } }) From 114c2fef523a6155a4c7f50ec0ccf0119e2889c5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 14:03:02 +0100 Subject: [PATCH 0964/1995] Remove owned str in TxEventQuery --- apps/src/lib/client/rpc.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 960522f9da..72ef496350 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -49,7 +49,7 @@ use crate::node::ledger::rpc::Path; /// If a response is not delivered until `deadline`, we exit the cli with an /// error. pub async fn query_tx_status( - status: TxEventQuery, + status: TxEventQuery<'_>, args: args::Query, deadline: Instant, ) -> Event { @@ -62,7 +62,7 @@ pub async fn query_tx_status( loop { let data = vec![]; let response = client - .abci_query(Some(status.clone().into()), data, None, false) + .abci_query(Some(status.into()), data, None, false) .await .unwrap(); let mut events = match response.code { @@ -1479,23 +1479,23 @@ pub async fn query_has_storage_key( } /// Represents a query for an event pertaining to the specified transaction -#[derive(Debug, Clone)] -pub enum TxEventQuery { - Accepted(String), - Applied(String), +#[derive(Debug, Copy, Clone)] +pub enum TxEventQuery<'a> { + Accepted(&'a str), + Applied(&'a str), } -impl TxEventQuery { +impl<'a> TxEventQuery<'a> { /// The event type to which this event query pertains - fn event_type(&self) -> &'static str { + fn event_type(self) -> &'static str { match self { - TxEventQuery::Accepted(_tx_hash) => "accepted", - TxEventQuery::Applied(_tx_hash) => "applied", + TxEventQuery::Accepted(_) => "accepted", + TxEventQuery::Applied(_) => "applied", } } /// The transaction to which this event query pertains - fn tx_hash(&self) -> &String { + fn tx_hash(self) -> &'a str { match self { TxEventQuery::Accepted(tx_hash) => tx_hash, TxEventQuery::Applied(tx_hash) => tx_hash, @@ -1503,8 +1503,8 @@ impl TxEventQuery { } } -impl From for crate::facade::tendermint::abci::Path { - fn from(tx_query: TxEventQuery) -> Self { +impl<'a> From> for crate::facade::tendermint::abci::Path { + fn from(tx_query: TxEventQuery<'a>) -> Self { format!("{}/{}", tx_query.event_type(), tx_query.tx_hash()) .parse() .expect("This operation is infallible") @@ -1512,8 +1512,8 @@ impl From for crate::facade::tendermint::abci::Path { } /// Transaction event queries are semantically a subset of general queries -impl From for Query { - fn from(tx_query: TxEventQuery) -> Self { +impl<'a> From> for Query { + fn from(tx_query: TxEventQuery<'a>) -> Self { match tx_query { TxEventQuery::Accepted(tx_hash) => { Query::default().and_eq("accepted.hash", tx_hash) @@ -1528,14 +1528,14 @@ impl From for Query { /// Lookup the full response accompanying the specified transaction event pub async fn query_tx_response( ledger_address: &TendermintAddress, - tx_query: TxEventQuery, + tx_query: TxEventQuery<'_>, ) -> Result { // Connect to the Tendermint server holding the transactions let (client, driver) = WebSocketClient::new(ledger_address.clone()).await?; let driver_handle = tokio::spawn(async move { driver.run().await }); // Find all blocks that apply a transaction with the specified hash let blocks = &client - .block_search(Query::from(tx_query.clone()), 1, 255, Order::Ascending) + .block_search(tx_query.into(), 1, 255, Order::Ascending) .await .expect("Unable to query for transaction with given hash") .blocks; @@ -1611,7 +1611,7 @@ pub async fn query_result(_ctx: Context, args: args::QueryResult) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( &args.query.ledger_address, - TxEventQuery::Applied(args.tx_hash.clone()), + TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { @@ -1625,7 +1625,7 @@ pub async fn query_result(_ctx: Context, args: args::QueryResult) { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( &args.query.ledger_address, - TxEventQuery::Accepted(args.tx_hash), + TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { From bb0c0c7a98d9159953eb0a09423b8ba7d4d4632e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 15:10:10 +0100 Subject: [PATCH 0965/1995] Make client code use ABCI queries to check tx events --- apps/src/lib/client/tendermint_rpc_types.rs | 115 ++++++++++---------- apps/src/lib/client/tx.rs | 59 +++++----- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index fc92c76369..cce5312cef 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,10 +1,11 @@ +use std::convert::TryFrom; + use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; -use crate::facade::tendermint_rpc::event::Event; -use crate::node::ledger::events::EventType as NamadaEventType; +use crate::node::ledger::events::Event; /// Data needed for broadcasting a tx and /// monitoring its progress on chain @@ -34,65 +35,69 @@ pub struct TxResponse { pub initialized_accounts: Vec
, } -impl TxResponse { - /// Parse the JSON payload received from a subscription - /// - /// Searches for custom events emitted from the ledger and converts - /// them back to thin wrapper around a hashmap for further parsing. - pub fn parse( - event: Event, - event_type: NamadaEventType, - tx_hash: &str, - ) -> Self { - let events = event - .events - .expect("We should have obtained Tx events from the RPC"); - let evt_key = event_type.to_string(); - // Find the tx with a matching hash - macro_rules! tx_error { - () => { - || { - eprintln!( - "Couldn't find tx with hash {tx_hash} in events \ - {events:?}", - ); - safe_exit(1) - } - }; +impl TryFrom for TxResponse { + type Error = String; + + fn try_from(event: Event) -> Result { + fn missing_field_err(field: &str) -> String { + format!("Field \"{field}\" not present in event") } - let (index, _) = events - .get(&format!("{evt_key}.hash")) - .unwrap_or_else(tx_error!()) - .iter() - .enumerate() - .find(|(_, hash)| hash == &tx_hash) - .unwrap_or_else(tx_error!()); - let info = events[&format!("{evt_key}.info")][index].clone(); - let log = events[&format!("{evt_key}.log")][index].clone(); - let height = events[&format!("{evt_key}.height")][index].clone(); - let code = events[&format!("{evt_key}.code")][index].clone(); - let gas_used = events[&format!("{evt_key}.gas_used")][index].clone(); - let initialized_accounts = events - [&format!("{evt_key}.initialized_accounts")] - .get(index) - .as_ref() - .map(|initialized_accounts| { - serde_json::from_str(initialized_accounts).unwrap() - }) - .unwrap_or_else(|| { - eprintln!( - "Tendermint omitted one of the expected indices in events" - ); - Vec::new() - }); - TxResponse { + + let hash = event + .get("hash") + .ok_or_else(|| missing_field_err("hash"))? + .clone(); + let info = event + .get("info") + .ok_or_else(|| missing_field_err("info"))? + .clone(); + let log = event + .get("log") + .ok_or_else(|| missing_field_err("log"))? + .clone(); + let height = event + .get("height") + .ok_or_else(|| missing_field_err("height"))? + .clone(); + let code = event + .get("code") + .ok_or_else(|| missing_field_err("code"))? + .clone(); + let gas_used = event + .get("gas_used") + .ok_or_else(|| missing_field_err("gas_used"))? + .clone(); + let initialized_accounts = event + .get("initialized_accounts") + .map(String::as_str) + // TODO: fix finalize block, to return initialized accounts, + // even when we reject a tx? + .or(Some("[]")) + // NOTE: at this point we only have `Some(vec)`, not `None` + .ok_or_else(|| unreachable!()) + .and_then(|initialized_accounts| { + serde_json::from_str(initialized_accounts) + .map_err(|err| format!("JSON decode error: {err}")) + })?; + + Ok(TxResponse { + hash, info, log, height, code, gas_used, initialized_accounts, - hash: tx_hash.to_string(), - } + }) + } +} + +impl TxResponse { + /// Convert an [`Event`] to a [`TxResponse`], or error out. + pub fn from_event(event: Event) -> Self { + event.try_into().unwrap_or_else(|err| { + eprintln!("Error fetching TxResponse: {err}"); + safe_exit(1); + }) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 7fe9f181f0..f372447506 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::env; use std::fs::File; -use std::time::Duration; use async_std::io::{self, WriteExt}; use borsh::BorshSerialize; @@ -24,6 +23,7 @@ use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, token}; use namada::{ledger, vm}; +use tokio::time::{Duration, Instant}; use super::rpc; use crate::cli::context::WalletAddress; @@ -33,13 +33,9 @@ use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::error::Error as RpcError; -use crate::facade::tendermint_rpc::query::{EventType, Query}; use crate::facade::tendermint_rpc::{Client, HttpClient}; -use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; -const ACCEPTED_QUERY_KEY: &str = "accepted.hash"; -const APPLIED_QUERY_KEY: &str = "applied.hash"; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; @@ -54,13 +50,13 @@ const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; const VP_NFT: &str = "vp_nft.wasm"; -/// Timeout for jsonrpc requests to the `/events` endpoint in Tendermint. -const ENV_VAR_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME: &str = - "ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME"; +/// Timeout for requests to the `/accepted` and `/applied` +/// ABCI query endpoints. +const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME: &str = "NAMADA_EVENTS_MAX_WAIT_TIME"; -/// Default timeout in seconds for jsonrpc requests to the `/events` endpoint in -/// Tendermint. -const DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME: u64 = 30; +/// Default timeout in seconds for requests to the `/accepted` +/// and `/applied` ABCI query endpoints. +const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME: u64 = 60; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); @@ -1241,24 +1237,26 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; + tracing::debug!("Tenderming address: {:?}", address); + + // Broadcast the supplied transaction + broadcast_tx(address.clone(), &to_broadcast).await?; + let max_wait_time = Duration::from_secs( - env::var(ENV_VAR_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME) + env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME) .ok() .and_then(|val| val.parse().ok()) - .unwrap_or(DEFAULT_ANOMA_TENDERMINT_EVENTS_MAX_WAIT_TIME), + .unwrap_or(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME), ); - tracing::debug!("Tenderming address: {:?}", address); - let rpc_cli = HttpClient::new(address.clone())?; - - // Broadcast the supplied transaction - broadcast_tx(address, &to_broadcast).await?; + let deadline = Instant::now() + max_wait_time; let parsed = { - let wrapper_query = Query::from(EventType::NewBlock) - .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - let event = rpc_cli.events(wrapper_query, max_wait_time).await?.into(); - let parsed = - TxResponse::parse(event, NamadaEventType::Accepted, wrapper_hash); + let args = args::Query { + ledger_address: address.clone(), + }; + let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); + let event = rpc::query_tx_status(wrapper_query, args, deadline).await; + let parsed = TxResponse::from_event(event); println!( "Transaction accepted with result: {}", @@ -1269,15 +1267,14 @@ pub async fn submit_tx( if parsed.code == 0.to_string() { // We also listen to the event emitted when the encrypted // payload makes its way onto the blockchain - let decrypted_query = Query::from(EventType::NewBlock) - .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); + let args = args::Query { + ledger_address: address, + }; + let decrypted_query = + rpc::TxEventQuery::Applied(decrypted_hash.as_str()); let event = - rpc_cli.events(decrypted_query, max_wait_time).await?.into(); - let parsed = TxResponse::parse( - event, - NamadaEventType::Applied, - decrypted_hash.as_str(), - ); + rpc::query_tx_status(decrypted_query, args, deadline).await; + let parsed = TxResponse::from_event(event); println!( "Transaction applied with result: {}", serde_json::to_string_pretty(&parsed).unwrap() From b8a941296da5efca4cbb4802e957fc06570e2660 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 17 Oct 2022 16:34:37 +0100 Subject: [PATCH 0966/1995] Use regular debug fmt --- apps/src/lib/client/rpc.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 72ef496350..cf8c69f969 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -95,9 +95,7 @@ pub async fn query_tx_status( }) .await .map_err(|_| { - eprintln!( - "Transaction status query deadline of {deadline:#?} exceeded" - ); + eprintln!("Transaction status query deadline of {deadline:?} exceeded"); }) .and_then(|result| result) .unwrap_or_else(|_| cli::safe_exit(1)) From 54fd92f881e9471dac3b82d153a84ef9aef79678 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 10:12:06 +0100 Subject: [PATCH 0967/1995] Add time unit suffix to timeout env var --- apps/src/lib/client/tx.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f372447506..f14778b350 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -52,11 +52,12 @@ const VP_NFT: &str = "vp_nft.wasm"; /// Timeout for requests to the `/accepted` and `/applied` /// ABCI query endpoints. -const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME: &str = "NAMADA_EVENTS_MAX_WAIT_TIME"; +const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = + "NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. -const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME: u64 = 60; +const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); @@ -1243,10 +1244,10 @@ pub async fn submit_tx( broadcast_tx(address.clone(), &to_broadcast).await?; let max_wait_time = Duration::from_secs( - env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME) + env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS) .ok() .and_then(|val| val.parse().ok()) - .unwrap_or(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME), + .unwrap_or(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS), ); let deadline = Instant::now() + max_wait_time; From 342522de982ba7e8a95cc9e0442e79a60834fff6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 10:20:03 +0100 Subject: [PATCH 0968/1995] Add more tx broadcast debugging --- apps/src/lib/client/tendermint_rpc_types.rs | 2 +- apps/src/lib/client/tx.rs | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index cce5312cef..0e94155378 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -13,7 +13,7 @@ use crate::node::ledger::events::Event; /// Txs may be either a dry run or else /// they should be encrypted and included /// in a wrapper. -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum TxBroadcastData { DryRun(Tx), Wrapper { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f14778b350..ce5fcc4743 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1196,6 +1196,11 @@ pub async fn broadcast_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; + tracing::debug!( + tendermint_rpc_address = ?address, + transaction = ?to_broadcast, + "Broadcasting transaction", + ); let rpc_cli = HttpClient::new(address)?; // TODO: configure an explicit timeout value? we need to hack away at @@ -1238,8 +1243,6 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - tracing::debug!("Tenderming address: {:?}", address); - // Broadcast the supplied transaction broadcast_tx(address.clone(), &to_broadcast).await?; @@ -1251,6 +1254,13 @@ pub async fn submit_tx( ); let deadline = Instant::now() + max_wait_time; + tracing::debug!( + tendermint_rpc_address = ?address, + transaction = ?to_broadcast, + ?deadline, + "Awaiting transaction approval", + ); + let parsed = { let args = args::Query { ledger_address: address.clone(), @@ -1286,5 +1296,10 @@ pub async fn submit_tx( } }; + tracing::debug!( + transaction = ?to_broadcast, + "Transaction approved", + ); + parsed } From df35781603e0da194363cb5b16e8a1f24f6dab82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 10:50:06 +0100 Subject: [PATCH 0969/1995] Add tx status debug logging in the client --- apps/src/lib/client/rpc.rs | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index cf8c69f969..481592756e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -53,18 +53,37 @@ pub async fn query_tx_status( args: args::Query, deadline: Instant, ) -> Event { + const ONE_SECOND: Duration = Duration::from_secs(1); + // sleep for the duration of `backoff`, + // and update the underlying value + async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { + tracing::debug!( + ?query, + duration = ?backoff, + "Retrying tx status query after timeout", + ); + // simple linear backoff - if an event is not available, + // increase the backoff duration by one second + tokio::time::sleep(*backoff).await; + *backoff += ONE_SECOND; + } tokio::time::timeout_at(deadline, async move { let client = HttpClient::new(args.ledger_address).unwrap(); - - const ONE_SECOND: Duration = Duration::from_secs(1); let mut backoff = ONE_SECOND; loop { let data = vec![]; - let response = client + tracing::debug!(query = ?status, "Querying tx status"); + let response = match client .abci_query(Some(status.into()), data, None, false) .await - .unwrap(); + { + Ok(response) => response, + Err(_) => { + sleep_update(status, &mut backoff).await; + continue; + } + }; let mut events = match response.code { Code::Ok => { match Vec::::try_from_slice(&response.value[..]) { @@ -87,10 +106,7 @@ pub async fn query_tx_status( // we should only have one event matching the query break Ok(e); } - // simple linear backoff - if an event is not available, - // increase the backoff duration by one second - tokio::time::sleep(backoff).await; - backoff += ONE_SECOND; + sleep_update(status, &mut backoff).await; } }) .await From 0a8aa96074cb40a73956499506b17e09093d22b7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 12:46:41 +0100 Subject: [PATCH 0970/1995] Pass in tm address directly to query tx status --- apps/src/lib/client/rpc.rs | 4 ++-- apps/src/lib/client/tx.rs | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 481592756e..a489cb62c2 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -50,7 +50,7 @@ use crate::node::ledger::rpc::Path; /// error. pub async fn query_tx_status( status: TxEventQuery<'_>, - args: args::Query, + address: TendermintAddress, deadline: Instant, ) -> Event { const ONE_SECOND: Duration = Duration::from_secs(1); @@ -68,7 +68,7 @@ pub async fn query_tx_status( *backoff += ONE_SECOND; } tokio::time::timeout_at(deadline, async move { - let client = HttpClient::new(args.ledger_address).unwrap(); + let client = HttpClient::new(address).unwrap(); let mut backoff = ONE_SECOND; loop { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ce5fcc4743..49d90a586d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1262,11 +1262,10 @@ pub async fn submit_tx( ); let parsed = { - let args = args::Query { - ledger_address: address.clone(), - }; let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); - let event = rpc::query_tx_status(wrapper_query, args, deadline).await; + let event = + rpc::query_tx_status(wrapper_query, address.clone(), deadline) + .await; let parsed = TxResponse::from_event(event); println!( @@ -1278,13 +1277,10 @@ pub async fn submit_tx( if parsed.code == 0.to_string() { // We also listen to the event emitted when the encrypted // payload makes its way onto the blockchain - let args = args::Query { - ledger_address: address, - }; let decrypted_query = rpc::TxEventQuery::Applied(decrypted_hash.as_str()); let event = - rpc::query_tx_status(decrypted_query, args, deadline).await; + rpc::query_tx_status(decrypted_query, address, deadline).await; let parsed = TxResponse::from_event(event); println!( "Transaction applied with result: {}", From d923df65f1afb93f01cbe87bda69325604b27d5f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 18 Oct 2022 12:48:51 +0100 Subject: [PATCH 0971/1995] Log ABCI query error --- apps/src/lib/client/rpc.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index a489cb62c2..b70c9b3483 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -79,7 +79,8 @@ pub async fn query_tx_status( .await { Ok(response) => response, - Err(_) => { + Err(err) => { + tracing::debug!(%err, "ABCI query failed"); sleep_update(status, &mut backoff).await; continue; } From 43f28f2cb7b4df8246d5c4879e6681cad19c1741 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 19 Oct 2022 11:51:09 +0000 Subject: [PATCH 0972/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2634122c96..5e78dac925 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", + "tx_from_intent.wasm": "tx_from_intent.a155e7bc5dd5502ce21387eb2378c004224d32b88b3c172a7c45f5d4da78a0e2.wasm", + "tx_ibc.wasm": "tx_ibc.f461624e4618db59ad42aee505011882d27dd4e31b02116071054e72a4529026.wasm", + "tx_init_account.wasm": "tx_init_account.b25a6b5babd91bb1101ba1dacef7c4d84acc3019dc1178ff4cdd0688a96e4395.wasm", + "tx_init_nft.wasm": "tx_init_nft.68d95a870c42023288cebe58510f9c32d3dcee3c58dbfd0122fbb45ba2619a9e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a73ca65889db6a2c9c60d6352fcf72cec3631332cdba720ba231302fb4f95e6d.wasm", + "tx_init_validator.wasm": "tx_init_validator.061d5f7a94da2949ec44cf701fab8005daf1c8903b0ecd4b11e9605a04d9f971.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.fb5de60d558232e07f2a1a2429b0589a925a7a11557a134c6057a14a853306fa.wasm", + "tx_transfer.wasm": "tx_transfer.5bc06525dacf355dbfbce567b1dbe519205d31b7f9cc10aef994a5ee230526ce.wasm", + "tx_unbond.wasm": "tx_unbond.3409ade177afa0e7ee2a4ad3f72cd7ecaa2955b7f90e81ce8446c847d370deb2.wasm", "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.794f51beec3ddc4fae9ea169f08514fb04ad6229eaf2aa354c595618667fee09.wasm", + "tx_withdraw.wasm": "tx_withdraw.eb428dd309729e59bdb4b4f15eeee9183bd11e6db0a0570a73f45f2376e33ada.wasm", + "vp_nft.wasm": "vp_nft.adb4abb9213dc5c2c59035893214bb7f44d6ab3d0915ba2cb5481077deacc1c4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1799952f853a0ba5fab662c28af1316817933d6e0bea629e603590053944dc00.wasm", "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "vp_user.wasm": "vp_user.a33860b4c4496c94dac96562dd9b8fa51f42118122021c28449a59b0e336601d.wasm" } \ No newline at end of file From 330c0c672d96366755571e272f1593bcaba8a5e7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 12:56:10 +0100 Subject: [PATCH 0973/1995] Meld is a pos --- wasm/checksums.json | 1 - 1 file changed, 1 deletion(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f21db1d98f..81ff91a27d 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -17,4 +17,3 @@ "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", "vp_user.wasm": "vp_user.a33860b4c4496c94dac96562dd9b8fa51f42118122021c28449a59b0e336601d.wasm" } -==== BASE ==== From bea5947b3be41086504620004aefd78c3b0f47e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 19 Oct 2022 12:39:47 +0000 Subject: [PATCH 0974/1995] 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 81ff91a27d..fa15395362 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.a155e7bc5dd5502ce21387eb2378c004224d32b88b3c172a7c45f5d4da78a0e2.wasm", - "tx_ibc.wasm": "tx_ibc.f461624e4618db59ad42aee505011882d27dd4e31b02116071054e72a4529026.wasm", - "tx_init_account.wasm": "tx_init_account.b25a6b5babd91bb1101ba1dacef7c4d84acc3019dc1178ff4cdd0688a96e4395.wasm", - "tx_init_nft.wasm": "tx_init_nft.68d95a870c42023288cebe58510f9c32d3dcee3c58dbfd0122fbb45ba2619a9e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a73ca65889db6a2c9c60d6352fcf72cec3631332cdba720ba231302fb4f95e6d.wasm", - "tx_init_validator.wasm": "tx_init_validator.061d5f7a94da2949ec44cf701fab8005daf1c8903b0ecd4b11e9605a04d9f971.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.fb5de60d558232e07f2a1a2429b0589a925a7a11557a134c6057a14a853306fa.wasm", - "tx_transfer.wasm": "tx_transfer.5bc06525dacf355dbfbce567b1dbe519205d31b7f9cc10aef994a5ee230526ce.wasm", - "tx_unbond.wasm": "tx_unbond.3409ade177afa0e7ee2a4ad3f72cd7ecaa2955b7f90e81ce8446c847d370deb2.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.794f51beec3ddc4fae9ea169f08514fb04ad6229eaf2aa354c595618667fee09.wasm", - "tx_withdraw.wasm": "tx_withdraw.eb428dd309729e59bdb4b4f15eeee9183bd11e6db0a0570a73f45f2376e33ada.wasm", - "vp_nft.wasm": "vp_nft.adb4abb9213dc5c2c59035893214bb7f44d6ab3d0915ba2cb5481077deacc1c4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1799952f853a0ba5fab662c28af1316817933d6e0bea629e603590053944dc00.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.a33860b4c4496c94dac96562dd9b8fa51f42118122021c28449a59b0e336601d.wasm" -} + "tx_from_intent.wasm": "tx_from_intent.fba788133250a3d4eb5774d676d4945de23447a6ac78e5cdc0f902d812e9b924.wasm", + "tx_ibc.wasm": "tx_ibc.3d0fb08544ef12f1902cb8f4bd395628bdf463247799df73735422b2c160e7d0.wasm", + "tx_init_account.wasm": "tx_init_account.b5d63716c9f24e356145837f504e042d190b0c187b6b44275d2671365a86667d.wasm", + "tx_init_nft.wasm": "tx_init_nft.04020d96fe16df42ac21db2e0dd88d03ff1b0398edae9ab6493c3a8ce830f8dd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3dadb30768ea77def98810b9a4f70341a975e5d53000ba720971624f1228a4dd.wasm", + "tx_init_validator.wasm": "tx_init_validator.d8da643042660b3119c36f546d107000f5c232d89bfd2dcac0cf9b8e7373afa9.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9416aa33f3cb549b0c9cc57e17d7449d1b53fd80df76af70e70a0b6db4683af1.wasm", + "tx_transfer.wasm": "tx_transfer.dff93b6bc74fcf615a29a8a24f0a5b61213674800232a779947d142474d32bfa.wasm", + "tx_unbond.wasm": "tx_unbond.e1542afaa9c2ec78f28732ca7de2265ee8f49b569c1cd39ba66e5a0b7c1dea9c.wasm", + "tx_update_vp.wasm": "tx_update_vp.9e55e2fa0f443d7a26cbbc9927c56bcb340d653f8624a71e1c08ed459af734f6.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.0cfeefac7ce1947badbb9be67f00edd48ab6ecd7bab03fd12bcdc43300deea4a.wasm", + "tx_withdraw.wasm": "tx_withdraw.e9f673b3f9f76908617383100875c46f1a6e0958f28571c9cab7435b9e69bf69.wasm", + "vp_nft.wasm": "vp_nft.46cd417018ec7061b53db23efbb54797c152c90656fd9b916f33d3628dd26535.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.881fc24c12de4a8e75fb6b3bf2cb9bd40d6aa503ba641b715b0eeac38068d8d4.wasm", + "vp_token.wasm": "vp_token.8b417ddc8b88c53f50789f900caacb01d000b830babe7f458ba171a549151625.wasm", + "vp_user.wasm": "vp_user.4b10b1992198c96c93dd7259be8d744676dbc4adacc19f64878c33f23bff6f59.wasm" +} \ No newline at end of file From 2e685bd581de6d5646e0d908faf3a54bc6371746 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 16:13:28 +0100 Subject: [PATCH 0975/1995] Wait for Tendermint's RPC server to start in e2e tests --- tests/src/e2e/ledger_tests.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 493e65ffa9..6ec42093ed 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -101,14 +101,17 @@ fn test_node_connectivity() -> Result<()> { 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")?; + validator_0.exp_string("Starting RPC HTTP server on")?; 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")?; + validator_1.exp_string("Starting RPC HTTP server on")?; let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; non_validator.exp_string("This node is a fullnode")?; + non_validator.exp_string("Starting RPC HTTP server on")?; let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); @@ -168,6 +171,7 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; // 2. Kill the tendermint node sleep(1); @@ -274,7 +278,7 @@ fn ledger_txs_and_queries() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; let _bg_ledger = ledger.background(); let vp_user = wasm_abs_path(VP_USER_WASM); @@ -448,8 +452,7 @@ fn invalid_transactions() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; + ledger.exp_string("Starting RPC HTTP server on")?; let bg_ledger = ledger.background(); @@ -601,7 +604,7 @@ fn pos_bonds() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -796,7 +799,7 @@ fn pos_init_validator() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -960,7 +963,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut ledger = run_as!(*test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; // Wait to commit a block ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; @@ -1050,7 +1053,7 @@ fn proposal_submission() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -1399,7 +1402,7 @@ fn proposal_offline() -> Result<()> { let mut ledger = run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(20))?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server on")?; let _bg_ledger = ledger.background(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -1553,8 +1556,6 @@ fn generate_proposal_json( /// 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 #[test] -#[ignore] -// TODO(namada#418): re-enable once working again fn test_genesis_validators() -> Result<()> { use std::collections::HashMap; use std::net::SocketAddr; @@ -1847,16 +1848,19 @@ fn test_genesis_validators() -> Result<()> { 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")?; + validator_0.exp_string("Starting RPC HTTP server on")?; 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")?; + validator_1.exp_string("Starting RPC HTTP server on")?; let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; non_validator.exp_string("This node is a fullnode")?; + non_validator.exp_string("Starting RPC HTTP server on")?; let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); From 7dee7157e65898669af30e6f3ce370fb544623e6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 16:17:14 +0100 Subject: [PATCH 0976/1995] Re-enable eth bridge tests --- tests/src/e2e.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index a935e4c2ba..a9eb2b2cf5 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -11,7 +11,7 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -// pub mod eth_bridge_tests; +pub mod eth_bridge_tests; pub mod gossip_tests; pub mod helpers; pub mod ledger_tests; From 18a8b2b1dfff895e1e8d03448011c4ad6400f972 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 16:30:10 +0100 Subject: [PATCH 0977/1995] Export ANOMA_TM_STDOUT env var in e2e tests --- tests/src/e2e/setup.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 0434d049a4..0c8cf84a82 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -665,6 +665,7 @@ where run_cmd .env("ANOMA_LOG", log_level) + .env("ANOMA_TM_STDOUT", "true") .env("TM_LOG_LEVEL", "info") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) From 4729acc39847367b07938cc0cd3f400e76f169c9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 16:44:21 +0100 Subject: [PATCH 0978/1995] Disable outdated e2e test --- tests/src/e2e/eth_bridge_tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index b276dd409e..e8efb336c8 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -19,6 +19,8 @@ fn storage_key(path: &str) -> String { } #[test] +#[ignore] +// this test is outdated, so it is ignored fn everything() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 30; const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 30; From 778275007859346e40611151a60f79937d43d47b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 19 Oct 2022 16:44:47 +0100 Subject: [PATCH 0979/1995] Move e2e test utils to setup.rs --- tests/src/e2e/ledger_tests.rs | 28 +++++----------------------- tests/src/e2e/setup.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 6ec42093ed..5e8db0c51a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -17,41 +17,21 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; -use namada::types::chain::ChainId; use namada::types::token; +use namada_apps::config::ethereum; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; -use namada_apps::config::{ethereum, Config}; use serde_json::json; use setup::constants::*; -use super::setup::working_dir; +use super::setup::{disable_eth_fullnode, working_dir}; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; -use crate::e2e::setup::{self, sleep, Bin, Test, Who}; +use crate::e2e::setup::{self, sleep, Bin, Who}; use crate::{run, run_as}; -fn update_actor_config(test: &Test, chain_id: &ChainId, who: &Who, update: F) -where - F: FnOnce(&mut Config), -{ - let validator_base_dir = test.get_base_dir(who); - let mut validator_config = - Config::load(&validator_base_dir, chain_id, None); - update(&mut validator_config); - validator_config - .write(&validator_base_dir, chain_id, true) - .unwrap(); -} - -fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { - update_actor_config(test, chain_id, who, |config| { - config.ledger.ethereum.mode = ethereum::Mode::Off; - }); -} - /// Test that when we "run-ledger" with all the possible command /// combinations from fresh state, the node starts-up successfully for both a /// validator and non-validator user. @@ -1804,6 +1784,8 @@ fn test_genesis_validators() -> Result<()> { // We have to update the ports in the configs again, because the ones from // `join-network` use the defaults + // + // TODO: use `update_actor_config` from `setup`, instead 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); diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 0c8cf84a82..1ca7a2f41c 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -21,6 +21,7 @@ use itertools::{Either, Itertools}; use namada::types::chain::ChainId; use namada_apps::client::utils; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; +use namada_apps::config::{ethereum, Config}; use namada_apps::{config, wallet}; use rand::Rng; use tempfile::{tempdir, TempDir}; @@ -55,6 +56,31 @@ pub struct Network { pub chain_id: ChainId, } +/// Update the config of some node `who`. +pub fn update_actor_config( + test: &Test, + chain_id: &ChainId, + who: &Who, + update: F, +) where + F: FnOnce(&mut Config), +{ + let validator_base_dir = test.get_base_dir(who); + let mut validator_config = + Config::load(&validator_base_dir, chain_id, None); + update(&mut validator_config); + validator_config + .write(&validator_base_dir, chain_id, true) + .unwrap(); +} + +/// Disable the Ethereum fullnode of `who`. +pub fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { + update_actor_config(test, chain_id, who, |config| { + config.ledger.ethereum.mode = ethereum::Mode::Off; + }); +} + /// 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 From ef06ea3914e58d0551bd9f06f134a2b25a5b25d9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 09:41:07 +0100 Subject: [PATCH 0980/1995] Fix wasms build Co-authored-by: Gianmarco Fraccaroli --- 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 f09c09c3c5..43a30573d9 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -6,7 +6,7 @@ nightly := $(shell cat ../../rust-nightly-version) # All the wasms that can be built from this source, switched via Cargo features # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_bond -wasms := tx_bridge_pool +wasms += tx_bridge_pool wasms += tx_from_intent wasms += tx_ibc wasms += tx_init_account From f22166258b937e8752601e8f93bea14d041dc461 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Oct 2022 09:15:16 +0000 Subject: [PATCH 0981/1995] [ci] wasm checksums update --- wasm/checksums.json | 1 + 1 file changed, 1 insertion(+) diff --git a/wasm/checksums.json b/wasm/checksums.json index fa15395362..5e4f605012 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,4 +1,5 @@ { + "tx_bond.wasm": "tx_bond.bb67d406e6c31218d32667ef8debed9be77dbadb33ee52a30d9fc377e3f6876c.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", "tx_from_intent.wasm": "tx_from_intent.fba788133250a3d4eb5774d676d4945de23447a6ac78e5cdc0f902d812e9b924.wasm", "tx_ibc.wasm": "tx_ibc.3d0fb08544ef12f1902cb8f4bd395628bdf463247799df73735422b2c160e7d0.wasm", From 118c69933610cdf38823f85d696f3e32fec4063a Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 11:23:05 +0200 Subject: [PATCH 0982/1995] [chore]: Handling code review comments --- .../ledger/eth_bridge/storage/bridge_pool.rs | 41 +++++++++---------- shared/src/ledger/storage/traits.rs | 6 +-- shared/src/types/storage.rs | 14 +++---- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f1dfc181c9..29d307a71b 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -66,7 +66,7 @@ pub fn is_protected_storage(key: &Key) -> bool { pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, - /// The underlying storage + /// The underlying storage, containing hashes of [`PendingTransfer`]s. store: BTreeSet, } @@ -88,7 +88,7 @@ impl BridgePoolTree { /// /// Returns the new root if successful. Will /// return an error if the key is malformed. - pub fn update_key(&mut self, key: &Key) -> Result { + pub fn insert_key(&mut self, key: &Key) -> Result { let hash = Self::parse_key(key)?; _ = self.store.insert(hash); self.root = self.compute_root(); @@ -104,7 +104,7 @@ impl BridgePoolTree { } /// Compute the root of the merkle tree - pub fn compute_root(&self) -> KeccakHash { + fn compute_root(&self) -> KeccakHash { let mut hashes: Vec = self.store.iter().cloned().collect(); while hashes.len() > 1 { let mut next_hashes = vec![]; @@ -242,14 +242,11 @@ impl BridgePoolTree { eyre!("Could not parse key segment as a hash").into() }) } - _ => Err(eyre!( - "Bridge pool keys should be strings, not addresses" - ) - .into()), + _ => Err(eyre!("Bridge pool keys should be strings.").into()), } } else { Err(eyre!( - "Key for the bridge pool should not have more than one segment" + "Key for the bridge pool should have exactly one segment." ) .into()) } @@ -390,7 +387,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); let root = - KeccakHash::from(tree.update_key(&key).expect("Test failed")); + KeccakHash::from(tree.insert_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); } @@ -413,7 +410,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } let expected: Hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) @@ -441,7 +438,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); let hashes: BTreeSet = @@ -475,7 +472,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); let root = - KeccakHash::from(tree.update_key(&key).expect("Test failed")); + KeccakHash::from(tree.insert_key(&key).expect("Test failed")); assert_eq!(root, transfer.keccak256()); tree.delete_key(&key).expect("Test failed"); assert_eq!(tree.root().0, [0; 32]); @@ -502,7 +499,7 @@ mod test_bridge_pool_tree { let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); tree.delete_key(&Key::from(&transfers[1])) @@ -587,7 +584,7 @@ mod test_bridge_pool_tree { payer: bertha_address(), }, }; - tree.update_key(&Key::from(&transfer)).expect("Test failed"); + tree.insert_key(&Key::from(&transfer)).expect("Test failed"); assert!( tree.contains_key(&Key::from(&transfer)) .expect("Test failed") @@ -640,7 +637,7 @@ mod test_bridge_pool_tree { }; let mut tree = BridgePoolTree::default(); let key = Key::from(&transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); let proof = tree .get_membership_proof(array::from_ref(&key), vec![transfer]) .expect("Test failed"); @@ -669,7 +666,7 @@ mod test_bridge_pool_tree { let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } let key = Key::from(&transfers[0]); let proof = tree @@ -702,7 +699,7 @@ mod test_bridge_pool_tree { let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); let keys = vec![Key::from(&transfers[0]), Key::from(&transfers[1])]; @@ -733,7 +730,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } let keys = vec![]; let values = vec![]; @@ -763,7 +760,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); @@ -793,7 +790,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().map(Key::from).collect(); @@ -823,7 +820,7 @@ mod test_bridge_pool_tree { }; let key = Key::from(&transfer); transfers.push(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); let keys: Vec<_> = transfers.iter().step_by(2).map(Key::from).collect(); @@ -887,7 +884,7 @@ mod test_bridge_pool_tree { let mut tree = BridgePoolTree::default(); for transfer in &transfers { let key = Key::from(transfer); - let _ = tree.update_key(&key).expect("Test failed"); + let _ = tree.insert_key(&key).expect("Test failed"); } to_prove.sort_by_key(|t| t.keccak256()); diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 5491b78685..67d77ac991 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -179,7 +179,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { let values = values .into_iter() .filter_map(|val| match val { - MerkleValue::Transfer(transfer) => Some(transfer), + MerkleValue::BridgePoolTransfer(transfer) => Some(transfer), _ => None, }) .collect(); @@ -195,8 +195,8 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { key: &Key, value: MerkleValue, ) -> Result { - if let MerkleValue::Transfer(_) = value { - self.update_key(key) + if let MerkleValue::BridgePoolTransfer(_) = value { + self.insert_key(key) .map_err(|err| Error::MerkleTree(err.to_string())) } else { Err(Error::InvalidValue) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index db7cada8ec..be15012cfa 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -33,8 +33,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), - #[error("Could not parse string: '{0}' into requested type: {1}")] - ParseError(String, String), + #[error("Could not parse string into a key segment: {0}")] + ParseError(String), } /// Result for functions that may fail @@ -248,7 +248,7 @@ pub enum MerkleValue { /// raw bytes Bytes(Vec), /// A transfer to be put in the Ethereum bridge pool. - Transfer(PendingTransfer), + BridgePoolTransfer(PendingTransfer), } impl From for MerkleValue @@ -262,7 +262,7 @@ where impl From for MerkleValue { fn from(transfer: PendingTransfer) -> Self { - Self::Transfer(transfer) + Self::BridgePoolTransfer(transfer) } } @@ -626,9 +626,9 @@ impl KeySeg for Address { impl KeySeg for Hash { fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) + seg.try_into().map_err(|e: crate::types::hash::Error| { + Error::ParseError(e.to_string()) + }) } fn raw(&self) -> String { From ae5f16729adf895d52c1432c21ce35f7a88864c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 10:39:38 +0100 Subject: [PATCH 0983/1995] Verify voting powers of validators in the new epoch --- .../lib/node/ledger/shell/vote_extensions/val_set_update.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index ff10b77c63..436864af75 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -94,11 +94,11 @@ where return Err(VoteExtensionError::UnexpectedEpoch); } }; - // verify if the voting powers in storage match the voting powers in the - // vote extensions + // verify if the new epoch validators' voting powers in storage match + // the voting powers in the vote extensions for (eth_addr_book, namada_addr, namada_power) in self .storage - .get_active_eth_addresses(Some(ext_height_epoch)) + .get_active_eth_addresses(Some(ext_height_epoch.next())) { let &ext_power = match ext.data.voting_powers.get(ð_addr_book) { Some(voting_power) => voting_power, From 0f595ccd17fdfb8835b92ffaea0d08959ecbeb96 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 11:42:22 +0200 Subject: [PATCH 0984/1995] [fix]: Added asset type to Pending Transfer serialization --- shared/src/types/eth_bridge_pool.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 36a378b8db..904c2c7eec 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -5,7 +5,6 @@ use ethabi::token::Token; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; -use crate::types::keccak; use crate::types::keccak::encode::Encode; use crate::types::storage::{DbKeySeg, Key}; use crate::types::token::Amount; @@ -55,14 +54,15 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl keccak::encode::Encode for PendingTransfer { +impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { - let from = Token::String(self.gas_fee.payer.to_string()); + let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); + let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, nonce] + vec![from, fee, to, amount, fee_from, nonce] } } From 5c006aa3bbfc8a0a4391e820770f3d3e1a044b1d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 10:42:36 +0100 Subject: [PATCH 0985/1995] Small comment fix --- .../src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 436864af75..7adb37f3f8 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -95,7 +95,7 @@ where } }; // verify if the new epoch validators' voting powers in storage match - // the voting powers in the vote extensions + // the voting powers in the vote extension for (eth_addr_book, namada_addr, namada_power) in self .storage .get_active_eth_addresses(Some(ext_height_epoch.next())) From 645bfc2145e4c02069d27cd59671a48085b4fd1e Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 14:31:28 +0200 Subject: [PATCH 0986/1995] [fix]: More code review fixes --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 21 +++++++------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b7b8cf1cf2..3eada7068f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -177,6 +177,7 @@ where tracing::debug!("The bridge pools escrow was not credited."); return Ok(false); } + tracing::info!("The Ethereum bridge pool VP accepted the transfer {:?}.", transfer); Ok(true) } diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 29d307a71b..0a3afe7eda 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -108,14 +108,10 @@ impl BridgePoolTree { let mut hashes: Vec = self.store.iter().cloned().collect(); while hashes.len() > 1 { let mut next_hashes = vec![]; - let left_leaves = hashes.iter().step_by(2); - let mut right_leaves = hashes.iter(); - _ = right_leaves.next(); - let mut right_leaves = right_leaves.step_by(2); - - for left in left_leaves { - let right = right_leaves.next().cloned().unwrap_or_default(); - next_hashes.push(hash_pair(left.clone(), right)); + for pair in hashes.chunks(2) { + let left = pair[0].clone(); + let right = pair.get(1).cloned().unwrap_or_default(); + next_hashes.push(hash_pair(left, right)); } hashes = next_hashes; } @@ -182,13 +178,10 @@ impl BridgePoolTree { while hashes.len() > 1 { let mut next_hashes = vec![]; - let left_leaves = hashes.iter().step_by(2); - let mut right_leaves = hashes.iter(); - _ = right_leaves.next(); - let mut right_leaves = right_leaves.step_by(2); - for left in left_leaves { - let right = right_leaves.next().cloned().unwrap_or_default(); + for pair in hashes.chunks(2) { + let left = pair[0].clone(); + let right = pair.get(1).cloned().unwrap_or_default(); match (left, right) { (Node::OnPath(left), Node::OnPath(right)) => { flags.push(true); From 49e08d0f948dddc4012e36b0d31129a3818b821a Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 14:45:22 +0200 Subject: [PATCH 0987/1995] [fix]: Fixed an import --- 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 1d175e83d2..3add9dd7f0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -11,7 +11,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof, NonExistenceProof}; use prost::Message; -use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; @@ -22,6 +21,7 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ }; use crate::ledger::storage::ics23_specs::ibc_leaf_spec; use crate::ledger::storage::{ics23_specs, types}; +use crate::tendermint::merkle::proof::{Proof, ProofOp}; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::keccak::KeccakHash; From 09677d531fa4ebd3466079684bb7cf9b6a015c04 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 14:46:17 +0200 Subject: [PATCH 0988/1995] [fix]: Formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3eada7068f..0624a83ae1 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -177,7 +177,10 @@ where tracing::debug!("The bridge pools escrow was not credited."); return Ok(false); } - tracing::info!("The Ethereum bridge pool VP accepted the transfer {:?}.", transfer); + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); Ok(true) } From 1185da7440d2945c323bd314248fea0a2f8eb4b9 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 0989/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 0624a83ae1..f558c2d2dc 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -232,8 +215,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -244,9 +227,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -256,9 +237,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 0a3afe7eda..8de9bcfef9 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool #[derive( Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index be15012cfa..78e54a154a 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From f414afd08afc1e7cf539590872be1810fe41bafa Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 0990/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 +++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - 2 files changed, 124 insertions(+), 88 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index f558c2d2dc..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -272,19 +271,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -340,10 +341,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -360,10 +358,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -372,13 +372,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -389,13 +391,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -406,13 +410,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -423,13 +429,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -440,58 +448,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -502,13 +535,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -539,11 +577,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 8de9bcfef9..42e20b6065 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; From 44ccbc5d878b5f6df8fbcd0fbda7954b9059ac3b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 0991/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From 23048b485b100595855fe139f9236ff13cb698f7 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 0992/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 +++++++++------ shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..97aa273088 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -116,11 +117,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { - if *key != pending_key { + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." ); return Ok(false); } @@ -131,7 +132,9 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool." + ); return Ok(false); } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 78e54a154a..4cb6d91f69 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -657,6 +657,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 5e0a1c39817c5561bedc1b9d8ba1c6c23343d63d Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 0993/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 97aa273088..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -117,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -132,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } From 82bc30b145c8acd29e495358a257b7dc6b7b27af Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 16:06:12 +0200 Subject: [PATCH 0994/1995] [fix]: Added some more logging --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -120,7 +120,10 @@ where if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + incorrect key in the pending transaction pool: {}.\n \ + Expected key: {}", + key, + pending_key ); return Ok(false); } @@ -131,7 +134,12 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool: {:?}.\n \ + Expected: {:?}", + transfer, + pending + ); return Ok(false); } From d4778c0c04e48217dc31ce034532eb4f63dc813c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 14:05:41 +0100 Subject: [PATCH 0995/1995] scripts: Update unwrap_e2e_log.py --- scripts/unwrap_e2e_log.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/unwrap_e2e_log.py b/scripts/unwrap_e2e_log.py index 7fc60301f7..6193dd4a07 100755 --- a/scripts/unwrap_e2e_log.py +++ b/scripts/unwrap_e2e_log.py @@ -21,11 +21,24 @@ def process_file(f): sys.stdout.flush() def process_line(line): - prefix = 'read: ' for m in UNICODE.findall(line): line = line.replace(f'\\u{{{m}}}', f'\\u{int(m, 16):04x}') - line = eval(line[len(prefix):]) + line = \ + try_parse_line_str(line) or \ + try_parse_line_bytes (line) or \ + '' sys.stdout.write(line) +def try_parse_line_str(line): + prefix_full = 'read: "' + prefix = prefix_full[:-1] + if line.startswith(prefix_full): + return eval(line[len(prefix):]) + +def try_parse_line_bytes(line): + prefix = 'read:(bytes): ' + if line.startswith(prefix): + return bytes(eval(line[len(prefix):])).decode("utf-8", "backslashreplace") + if __name__ == '__main__': main() From 3d45649609ce8050e43ed7465187556bef9a6f70 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 14:09:58 +0100 Subject: [PATCH 0996/1995] Remove extra space --- scripts/unwrap_e2e_log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/unwrap_e2e_log.py b/scripts/unwrap_e2e_log.py index 6193dd4a07..a468a35acb 100755 --- a/scripts/unwrap_e2e_log.py +++ b/scripts/unwrap_e2e_log.py @@ -25,7 +25,7 @@ def process_line(line): line = line.replace(f'\\u{{{m}}}', f'\\u{int(m, 16):04x}') line = \ try_parse_line_str(line) or \ - try_parse_line_bytes (line) or \ + try_parse_line_bytes(line) or \ '' sys.stdout.write(line) From 88d5e9946810b20a3b5136a0f24be8116efc2def Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 12:34:33 +0100 Subject: [PATCH 0997/1995] Add VoteTracking struct --- .../node/ledger/protocol/transactions/mod.rs | 2 ++ .../ledger/protocol/transactions/votes.rs | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 apps/src/lib/node/ledger/protocol/transactions/votes.rs diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 41884b2153..f1ff235a98 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -6,3 +6,5 @@ //! transactions. #[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; +#[cfg(not(feature = "abcipp"))] +mod votes; diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs new file mode 100644 index 0000000000..d4554d5176 --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -0,0 +1,28 @@ +//! Logic and data types relating to tracking validators' votes for pieces of +//! data stored in the ledger, where those pieces of data should only be acted +//! on once they have received enough votes +use std::collections::BTreeSet; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada::types::address::Address; +use namada::types::voting_power::FractionalVotingPower; + +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +/// Represents all the information needed to track a piece of data that may be +/// voted for over multiple epochs +pub struct VoteTracking { + /// The total voting power that's voted for this event across all epochs + pub voting_power: FractionalVotingPower, + /// The addresses of validators that voted for this event. We use a + /// set type as validators should only be able to vote at most once, + /// and [`BTreeSet`] specifically as we want this field to be + /// deterministically ordered for storage. + pub seen_by: BTreeSet
, + /// Whether this event has been acted on or not - this should only ever + /// transition from `false` to `true`, once there is enough voting power + // TODO: this field is redundant - we can derive whether an event is seen + // or not from looking at `voting_power` + pub seen: bool, +} From 7aae9282d9f28ecfba21e60b2ca090455e3b0278 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 12:42:04 +0100 Subject: [PATCH 0998/1995] Use VoteTracking struct in ethereum_events module --- .../transactions/ethereum_events/eth_msgs.rs | 11 +- .../transactions/ethereum_events/mod.rs | 103 ++++++++++-------- wasm/checksums.json | 35 +++--- 3 files changed, 77 insertions(+), 72 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index dbd3917fac..c080ac0b6a 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -5,7 +5,8 @@ use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use namada::types::voting_power::FractionalVotingPower; + +use crate::node::ledger::protocol::transactions::votes::VoteTracking; /// Represents an Ethereum event being seen by some validators #[derive( @@ -48,12 +49,8 @@ impl From for EthMsgUpdate { pub struct EthMsg { /// The event being stored pub body: EthereumEvent, - /// The total voting power that's voted for this event across all epochs - pub voting_power: FractionalVotingPower, - /// The addresses of validators that voted for this event - pub seen_by: BTreeSet
, - /// Whether this event has been acted on or not - pub seen: bool, + /// Tracking of votes for this event + pub vote_tracking: VoteTracking, } #[cfg(test)] diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index e98e681269..d26192e971 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -23,6 +23,7 @@ use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; +use crate::node::ledger::protocol::transactions::votes::VoteTracking; use crate::node::ledger::shell::queries::QueriesExt; /// The keys changed while applying a protocol transaction @@ -170,6 +171,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { + let body = update.body.to_owned(); let eth_msg_keys = Keys::from(&update.body); // we arbitrarily look at whether the seen key is present to @@ -177,31 +179,41 @@ where // is a less arbitrary way to do this let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; - let (eth_msg_post, changed, confirmed) = if !exists_in_storage { - let (eth_msg_post, changed) = - calculate_new_eth_msg(update, voting_powers)?; - let confirmed = eth_msg_post.seen; - (eth_msg_post, changed, confirmed) + let (vote_tracking, changed, confirmed) = if !exists_in_storage { + tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); + let vote_tracking = + calculate_new_vote_tracking(&update.seen_by, voting_powers)?; + let changed = eth_msg_keys.into_iter().collect(); + let confirmed = vote_tracking.seen; + (vote_tracking, changed, confirmed) } else { - let (eth_msg_post, changed) = - calculate_updated_eth_msg(storage, update, voting_powers)?; + tracing::debug!( + %eth_msg_keys.prefix, + "Ethereum event already exists in storage", + ); + let (vote_tracking, changed) = calculate_updated_vote_tracking( + storage, + ð_msg_keys, + voting_powers, + )?; let confirmed = - eth_msg_post.seen && changed.contains(ð_msg_keys.seen()); - (eth_msg_post, changed, confirmed) + vote_tracking.seen && changed.contains(ð_msg_keys.seen()); + (vote_tracking, changed, confirmed) + }; + let eth_msg_post = EthMsg { + body, + vote_tracking, }; write_eth_msg(storage, ð_msg_keys, ð_msg_post)?; Ok((changed, confirmed)) } -fn calculate_new_eth_msg( - update: EthMsgUpdate, +fn calculate_new_vote_tracking( + seen_by: &BTreeSet<(Address, BlockHeight)>, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result<(EthMsg, ChangedKeys)> { - let eth_msg_keys = Keys::from(&update.body); - tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); - +) -> Result { let mut seen_by_voting_power = FractionalVotingPower::default(); - for (validator, block_height) in &update.seen_by { + for (validator, block_height) in seen_by { match voting_powers .get(&(validator.to_owned(), block_height.to_owned())) { @@ -217,35 +229,25 @@ fn calculate_new_eth_msg( let newly_confirmed = seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; - Ok(( - EthMsg { - body: update.body, - voting_power: seen_by_voting_power, - seen_by: update - .seen_by - .into_iter() - .map(|(validator, _)| validator) - .collect(), - seen: newly_confirmed, - }, - eth_msg_keys.into_iter().collect(), - )) + Ok(VoteTracking { + voting_power: seen_by_voting_power, + seen_by: seen_by + .into_iter() + .map(|(validator, _)| validator.to_owned()) + .collect(), + seen: newly_confirmed, + }) } -fn calculate_updated_eth_msg( +fn calculate_updated_vote_tracking( store: &mut Storage, - update: EthMsgUpdate, + eth_msg_keys: &Keys, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result<(EthMsg, ChangedKeys)> +) -> Result<(VoteTracking, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let eth_msg_keys = Keys::from(&update.body); - tracing::debug!( - %eth_msg_keys.prefix, - "Ethereum event already exists in storage", - ); let body: EthereumEvent = read::value(store, ð_msg_keys.body())?; let seen: bool = read::value(store, ð_msg_keys.seen())?; let seen_by: BTreeSet
= @@ -255,24 +257,25 @@ where let eth_msg_pre = EthMsg { body, - voting_power, - seen_by, - seen, + vote_tracking: VoteTracking { + voting_power, + seen_by, + seen, + }, }; tracing::debug!("Read EthMsg - {:#?}", ð_msg_pre); - Ok(calculate_diff(eth_msg_pre, update, voting_powers)) + Ok(calculate_diff(eth_msg_pre, voting_powers)) } fn calculate_diff( eth_msg: EthMsg, - _update: EthMsgUpdate, _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> (EthMsg, ChangedKeys) { +) -> (VoteTracking, ChangedKeys) { tracing::warn!( "Updating Ethereum events is not yet implemented, so this Ethereum \ event won't change" ); - (eth_msg, BTreeSet::default()) + (eth_msg.vote_tracking, BTreeSet::default()) } fn write_eth_msg( @@ -286,11 +289,17 @@ where { tracing::debug!("writing EthMsg - {:#?}", eth_msg); storage.write(ð_msg_keys.body(), ð_msg.body.try_to_vec()?)?; - storage.write(ð_msg_keys.seen(), ð_msg.seen.try_to_vec()?)?; - storage.write(ð_msg_keys.seen_by(), ð_msg.seen_by.try_to_vec()?)?; + storage.write( + ð_msg_keys.seen(), + ð_msg.vote_tracking.seen.try_to_vec()?, + )?; + storage.write( + ð_msg_keys.seen_by(), + ð_msg.vote_tracking.seen_by.try_to_vec()?, + )?; storage.write( ð_msg_keys.voting_power(), - ð_msg.voting_power.try_to_vec()?, + ð_msg.vote_tracking.voting_power.try_to_vec()?, )?; Ok(()) } diff --git a/wasm/checksums.json b/wasm/checksums.json index d61712a5fd..acd4bc6b94 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.bb67d406e6c31218d32667ef8debed9be77dbadb33ee52a30d9fc377e3f6876c.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9915822bea588b0a18b4ec51bc249e88fdc3041bc3eddaaa2cc880dcd23c5eb0.wasm", - "tx_ibc.wasm": "tx_ibc.913192b268db668ebba6b415eea106c19e742b9b6be6ebb795a661dca82482af.wasm", - "tx_init_account.wasm": "tx_init_account.bd544ce16dae46177f9d9bd5281a53b4f4fe741833013a1f412b1f104c4bf6a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.1f1b18628e97758d837d0f25c313979cc529abc56797be240ce4c74032d603c5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ad5ba0ef45cef6b59dab228dfaeb0dc643235639851c1adcff5d52152d2c01f5.wasm", - "tx_init_validator.wasm": "tx_init_validator.9b1ddb7e6dca6beadbf42c4da91771c92228562f4239494e4e56d57fd3c3b538.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.bd061f19c4d2e5801f671adb60e7e4d787a6f493a28c29f20a57e4a256a8dcbc.wasm", - "tx_transfer.wasm": "tx_transfer.1a8d43355f663c040a29d6e10e6cdfe6c73634aa5cd6863e1f3f5d58fe9ec560.wasm", - "tx_unbond.wasm": "tx_unbond.84a2a809d5b617740b991ddc5b88ad213ffa17aa178afe1f657b90cecebbaa7a.wasm", - "tx_update_vp.wasm": "tx_update_vp.84b896bce441e449cdbfa2401f2da8ea6f0c10725c1a8d9e1a03763b49056817.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8b171e9bdc02f6321f0b7ae6a881d00d26d7c5ecb00bb7aa0d77e411767517c9.wasm", - "tx_withdraw.wasm": "tx_withdraw.aede879ea5c81b4e479d89b2e9c5d9e5d80bf7e0a9e8a0a8c7bf7f2bb2b049f5.wasm", - "vp_nft.wasm": "vp_nft.87b7fc9e596891dd676294e18a467fd8ef16c372a3e22f7876b125fb9c31d606.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3ed495ea630de4a8d5459e09882c1862e768b826654aa1b700ed13e1e5cf8827.wasm", - "vp_token.wasm": "vp_token.b95cfb6bff76ffec7034f29453482937297cf713fd7cfd3f4f7045a2cb044bca.wasm", - "vp_user.wasm": "vp_user.fac7690818a1bc043cdb13d65b7f9d687343845c2e31e3eed841dc074803bff9.wasm" + "tx_bridge_pool.wasm": "tx_bridge_pool.dee69ea94dca815418a893081463df0f98046ff8df3b18273e12093f29c845ab.wasm", + "tx_from_intent.wasm": "tx_from_intent.984344584332d0d6984ec3d1c1b90ed3621c2733e1d888a760f2c3db1df36a55.wasm", + "tx_ibc.wasm": "tx_ibc.e6b32fda57dd22fdc344d69b838c60d83b64bf23f2617e435f1a0eb81932583d.wasm", + "tx_init_account.wasm": "tx_init_account.4bd496a146830c0404c84fd3041ea9df88c864b90964abaa90be4baed30495ee.wasm", + "tx_init_nft.wasm": "tx_init_nft.a1ac2554b952518615fc59fc1dca335ba39c12ad0c1f2068174bc66c453fa64d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6c108ba6e59cfe2447a5be92d7ff9c32ec8e1711a0dfb8bc58e1a87d7eca4a2e.wasm", + "tx_init_validator.wasm": "tx_init_validator.1e5907e84f56fe41b325cefc73e227f755f2532b5e9304ea62f74b21eced125f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.27310454c09ef7348dbc1ecd64514d7cc853231e6e3256d5fb5b98a2740222fe.wasm", + "tx_transfer.wasm": "tx_transfer.27424affe1a1695a3bd057ebfd61924f45d909099f555a4fd0c4b40a036799a0.wasm", + "tx_unbond.wasm": "tx_unbond.4922603455afa8a0e2fe443e46c73e51f27cc9d38a1dc205f5ce1db734c2dfeb.wasm", + "tx_update_vp.wasm": "tx_update_vp.e65aaf5fee4e712831137d88a0049161f102cb21802687760d35c215776aaa94.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.98f7d46dc9b819575fa314ea7f86bfe0716c68c49360a7c08bf3b5ce3dca0de2.wasm", + "tx_withdraw.wasm": "tx_withdraw.8d03dcbc6e05524940609125a7101472fd1f52f6bda2412ece20d55a5c16ac14.wasm", + "vp_nft.wasm": "vp_nft.6d7c545feaf8e91a16a1620fe75f956cd3daf5a345f559ec39ddecf6556b96bc.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0bdf61b141ff4f68ffdcc3036a0e82eedf7d7a920f504d3ef0a890ecbdd4fe38.wasm", + "vp_token.wasm": "vp_token.b60774ff41b664a4d70b9f0ec844ac3082ede65efac8352347dee53af556dd8b.wasm", + "vp_user.wasm": "vp_user.cb5fe67fd0d25bffc5013aaa9b3b2815779a657f9ae58d3ecf8f62baa010c239.wasm" } \ No newline at end of file From dec407d63b411368da037eea4773fed92b1fd5b6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 13:39:17 +0100 Subject: [PATCH 0999/1995] Make eth_msgs::Keys struct more generic --- .../transactions/ethereum_events/mod.rs | 60 ++++++-------- shared/src/ledger/eth_bridge/storage/mod.rs | 2 +- .../storage/{eth_msgs.rs => vote_tracked.rs} | 81 ++++++++++--------- 3 files changed, 72 insertions(+), 71 deletions(-) rename shared/src/ledger/eth_bridge/storage/{eth_msgs.rs => vote_tracked.rs} (74%) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index d26192e971..be9ed7d202 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -9,10 +9,10 @@ mod utils; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::{eyre, Result}; -use namada::ledger::eth_bridge::storage::eth_msgs::Keys; +use namada::ledger::eth_bridge::storage::vote_tracked; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; @@ -172,7 +172,7 @@ where H: 'static + StorageHasher + Sync, { let body = update.body.to_owned(); - let eth_msg_keys = Keys::from(&update.body); + let eth_msg_keys = vote_tracked::Keys::from(&update.body); // we arbitrarily look at whether the seen key is present to // determine if the /eth_msg already exists in storage, but maybe there @@ -191,11 +191,12 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let (vote_tracking, changed) = calculate_updated_vote_tracking( + let vote_tracking = calculate_updated_vote_tracking( storage, ð_msg_keys, voting_powers, )?; + let changed = BTreeSet::default(); // TODO(namada#515): calculate changed keys let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) @@ -239,48 +240,39 @@ fn calculate_new_vote_tracking( }) } -fn calculate_updated_vote_tracking( +fn calculate_updated_vote_tracking( store: &mut Storage, - eth_msg_keys: &Keys, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result<(VoteTracking, ChangedKeys)> + keys: &vote_tracked::Keys, + _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, +) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, + T: BorshDeserialize, { - let body: EthereumEvent = read::value(store, ð_msg_keys.body())?; - let seen: bool = read::value(store, ð_msg_keys.seen())?; - let seen_by: BTreeSet
= - read::value(store, ð_msg_keys.seen_by())?; + let _body: T = read::value(store, &keys.body())?; + let seen: bool = read::value(store, &keys.seen())?; + let seen_by: BTreeSet
= read::value(store, &keys.seen_by())?; let voting_power: FractionalVotingPower = - read::value(store, ð_msg_keys.voting_power())?; + read::value(store, &keys.voting_power())?; - let eth_msg_pre = EthMsg { - body, - vote_tracking: VoteTracking { - voting_power, - seen_by, - seen, - }, + let vote_tracking = VoteTracking { + voting_power, + seen_by, + seen, }; - tracing::debug!("Read EthMsg - {:#?}", ð_msg_pre); - Ok(calculate_diff(eth_msg_pre, voting_powers)) -} -fn calculate_diff( - eth_msg: EthMsg, - _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> (VoteTracking, ChangedKeys) { tracing::warn!( - "Updating Ethereum events is not yet implemented, so this Ethereum \ - event won't change" + ?vote_tracking, + "Updating events is not implemented yet, so the returned VoteTracking \ + will be identical to the one in storage", ); - (eth_msg.vote_tracking, BTreeSet::default()) + Ok(vote_tracking) } fn write_eth_msg( storage: &mut Storage, - eth_msg_keys: &Keys, + eth_msg_keys: &vote_tracked::Keys, eth_msg: &EthMsg, ) -> Result<()> where @@ -359,7 +351,7 @@ mod tests { let changed_keys = apply_updates(&mut storage, updates, voting_powers)?; - let eth_msg_keys: Keys = (&body).into(); + let eth_msg_keys: vote_tracked::Keys = (&body).into(); let wrapped_erc20_keys: wrapped_erc20s::Keys = (&asset).into(); assert_eq!( BTreeSet::from_iter(vec![ @@ -470,7 +462,7 @@ mod tests { tx_result.gas_used, 0, "No gas should be used for a derived transaction" ); - let eth_msg_keys = Keys::from(&event); + let eth_msg_keys = vote_tracked::Keys::from(&event); let dai_keys = wrapped_erc20s::Keys::from(&DAI_ERC20_ETH_ADDRESS); assert_eq!( tx_result.changed_keys, @@ -523,7 +515,7 @@ mod tests { Err(err) => panic!("unexpected error: {:#?}", err), }; - let eth_msg_keys = Keys::from(&event); + let eth_msg_keys = vote_tracked::Keys::from(&event); assert_eq!( tx_result.changed_keys, BTreeSet::from_iter(vec![ diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 5cfdbf0efb..a383666fb9 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -1,6 +1,6 @@ //! Functionality for accessing the storage subspace pub mod bridge_pool; -pub mod eth_msgs; +pub mod vote_tracked; pub mod wrapped_erc20s; use super::ADDRESS; diff --git a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs similarity index 74% rename from shared/src/ledger/eth_bridge/storage/eth_msgs.rs rename to shared/src/ledger/eth_bridge/storage/vote_tracked.rs index 88b168fdc3..077546a0f7 100644 --- a/shared/src/ledger/eth_bridge/storage/eth_msgs.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs @@ -6,27 +6,21 @@ use crate::types::storage::Key; #[allow(missing_docs)] pub const PREFIX_KEY_SEGMENT: &str = "eth_msgs"; -/// Get the key prefix corresponding to where details of seen [`EthereumEvent`]s -/// are stored -pub fn prefix() -> Key { - super::prefix() - .push(&PREFIX_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") -} - const BODY_KEY_SEGMENT: &str = "body"; const SEEN_KEY_SEGMENT: &str = "seen"; const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; -/// Generator for the keys under which details of an [`EthereumEvent`] is stored -pub struct Keys { - /// The prefix under which the details are an [`EthereumEvent`] is stored +/// Generator for the keys under which details of a vote tracked event is stored +pub struct Keys { + /// The prefix under which the details of a vote tracked event is stored pub prefix: Key, + _phantom: std::marker::PhantomData, } -impl Keys { - /// Get the `body` key- there should be an [`EthereumEvent`] stored here. +impl Keys { + /// Get the `body` key- there should be a Borsh-serialized [`T`] stored + /// here. pub fn body(&self) -> Key { self.prefix .push(&BODY_KEY_SEGMENT.to_owned()) @@ -57,7 +51,7 @@ impl Keys { } } -impl IntoIterator for &Keys { +impl IntoIterator for &Keys { type IntoIter = std::vec::IntoIter; type Item = Key; @@ -72,7 +66,15 @@ impl IntoIterator for &Keys { } } -impl From<&EthereumEvent> for Keys { +/// Get the key prefix corresponding to where details of seen [`EthereumEvent`]s +/// are stored +pub fn eth_msgs_prefix() -> Key { + super::prefix() + .push(&PREFIX_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") +} + +impl From<&EthereumEvent> for Keys { fn from(event: &EthereumEvent) -> Self { let hash = event .hash() @@ -81,13 +83,16 @@ impl From<&EthereumEvent> for Keys { } } -impl From<&Hash> for Keys { +impl From<&Hash> for Keys { fn from(hash: &Hash) -> Self { let hex = format!("{}", hash); - let prefix = prefix() + let prefix = eth_msgs_prefix() .push(&hex) .expect("should always be able to construct this key"); - Self { prefix } + Keys { + prefix, + _phantom: std::marker::PhantomData, + } } } @@ -97,20 +102,24 @@ mod test { use crate::ledger::eth_bridge::ADDRESS; use crate::types::storage::DbKeySeg; - fn arbitrary_event_with_hash() -> (EthereumEvent, String) { - ( - EthereumEvent::TransfersToNamada { - nonce: 1.into(), - transfers: vec![], - }, - "06799912C0FD8785EE29E13DFB84FE2778AF6D9CA026BD5B054F86CE9FE8C017" - .to_owned(), - ) + mod helpers { + use super::*; + + pub(super) fn arbitrary_event_with_hash() -> (EthereumEvent, String) { + ( + EthereumEvent::TransfersToNamada { + nonce: 1.into(), + transfers: vec![], + }, + "06799912C0FD8785EE29E13DFB84FE2778AF6D9CA026BD5B054F86CE9FE8C017" + .to_owned(), + ) + } } #[test] fn test_prefix() { - assert_matches!(&prefix().segments[..], [ + assert_matches!(ð_msgs_prefix().segments[..], [ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(s), ] if s == PREFIX_KEY_SEGMENT) @@ -118,8 +127,8 @@ mod test { #[test] fn test_keys_all_keys() { - let (event, hash) = arbitrary_event_with_hash(); - let keys: Keys = (&event).into(); + let (event, hash) = helpers::arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); let prefix = vec![ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), @@ -156,8 +165,8 @@ mod test { #[test] fn test_keys_into_iter() { - let (event, _) = arbitrary_event_with_hash(); - let keys: Keys = (&event).into(); + let (event, _) = helpers::arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); let as_keys: Vec<_> = keys.into_iter().collect(); assert_eq!( as_keys, @@ -172,8 +181,8 @@ mod test { #[test] fn test_keys_from_ethereum_event() { - let (event, hash) = arbitrary_event_with_hash(); - let keys: Keys = (&event).into(); + let (event, hash) = helpers::arbitrary_event_with_hash(); + let keys: Keys = (&event).into(); let expected = vec![ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), @@ -184,8 +193,8 @@ mod test { #[test] fn test_keys_from_hash() { - let (event, hash) = arbitrary_event_with_hash(); - let keys: Keys = (&event.hash().unwrap()).into(); + let (event, hash) = helpers::arbitrary_event_with_hash(); + let keys: Keys = (&event.hash().unwrap()).into(); let expected = vec![ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), From 6f10badda093f942bf2ca23b7a6f2b5ef98a7d52 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:14:35 +0100 Subject: [PATCH 1000/1995] Move vote tracking logic out of ethereum_events module --- .../transactions/ethereum_events/events.rs | 2 +- .../transactions/ethereum_events/mod.rs | 71 ++----------------- .../node/ledger/protocol/transactions/mod.rs | 6 ++ .../{ethereum_events => }/read.rs | 2 +- .../{ethereum_events => }/update.rs | 4 +- .../ledger/protocol/transactions/votes.rs | 69 +++++++++++++++++- 6 files changed, 83 insertions(+), 71 deletions(-) rename apps/src/lib/node/ledger/protocol/transactions/{ethereum_events => }/read.rs (97%) rename apps/src/lib/node/ledger/protocol/transactions/{ethereum_events => }/update.rs (95%) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs index b639a01fd2..2ed1b79cf2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/events.rs @@ -9,7 +9,7 @@ use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::storage::Key; -use super::update; +use crate::node::ledger::protocol::transactions::update; /// Updates storage based on the given confirmed `event`. For example, for a /// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index be9ed7d202..774de488c5 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -3,15 +3,13 @@ //! transactions. mod eth_msgs; mod events; -mod read; -mod update; mod utils; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshSerialize; use eth_msgs::{EthMsg, EthMsgUpdate}; -use eyre::{eyre, Result}; +use eyre::Result; use namada::ledger::eth_bridge::storage::vote_tracked; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::storage::traits::StorageHasher; @@ -23,7 +21,9 @@ use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; -use crate::node::ledger::protocol::transactions::votes::VoteTracking; +use crate::node::ledger::protocol::transactions::votes::{ + calculate_new_vote_tracking, calculate_updated_vote_tracking, +}; use crate::node::ledger::shell::queries::QueriesExt; /// The keys changed while applying a protocol transaction @@ -209,67 +209,6 @@ where Ok((changed, confirmed)) } -fn calculate_new_vote_tracking( - seen_by: &BTreeSet<(Address, BlockHeight)>, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result { - let mut seen_by_voting_power = FractionalVotingPower::default(); - for (validator, block_height) in seen_by { - match voting_powers - .get(&(validator.to_owned(), block_height.to_owned())) - { - Some(voting_power) => seen_by_voting_power += voting_power, - None => { - return Err(eyre!( - "voting power was not provided for validator {}", - validator - )); - } - }; - } - - let newly_confirmed = - seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; - Ok(VoteTracking { - voting_power: seen_by_voting_power, - seen_by: seen_by - .into_iter() - .map(|(validator, _)| validator.to_owned()) - .collect(), - seen: newly_confirmed, - }) -} - -fn calculate_updated_vote_tracking( - store: &mut Storage, - keys: &vote_tracked::Keys, - _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - T: BorshDeserialize, -{ - let _body: T = read::value(store, &keys.body())?; - let seen: bool = read::value(store, &keys.seen())?; - let seen_by: BTreeSet
= read::value(store, &keys.seen_by())?; - let voting_power: FractionalVotingPower = - read::value(store, &keys.voting_power())?; - - let vote_tracking = VoteTracking { - voting_power, - seen_by, - seen, - }; - - tracing::warn!( - ?vote_tracking, - "Updating events is not implemented yet, so the returned VoteTracking \ - will be identical to the one in storage", - ); - Ok(vote_tracking) -} - fn write_eth_msg( storage: &mut Storage, eth_msg_keys: &vote_tracked::Keys, diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index f1ff235a98..45d3d684be 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -8,3 +8,9 @@ pub(super) mod ethereum_events; #[cfg(not(feature = "abcipp"))] mod votes; + +#[cfg(not(feature = "abcipp"))] +mod read; + +#[cfg(not(feature = "abcipp"))] +mod update; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs b/apps/src/lib/node/ledger/protocol/transactions/read.rs similarity index 97% rename from apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs rename to apps/src/lib/node/ledger/protocol/transactions/read.rs index 90cc960509..fbbf5e1608 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/read.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/read.rs @@ -58,7 +58,7 @@ mod tests { use namada::types::storage; use namada::types::token::Amount; - use crate::node::ledger::protocol::transactions::ethereum_events::read; + use crate::node::ledger::protocol::transactions::read; #[test] fn test_amount_returns_zero_for_uninitialized_storage() { diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs b/apps/src/lib/node/ledger/protocol/transactions/update.rs similarity index 95% rename from apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs rename to apps/src/lib/node/ledger/protocol/transactions/update.rs index 092dada10a..a4e900d00d 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/update.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/update.rs @@ -7,7 +7,7 @@ use namada::types::storage; use namada::types::token::Amount; /// Reads the `Amount` from key, applies update then writes it back -pub(super) fn amount( +pub fn amount( store: &mut Storage, key: &storage::Key, update: impl FnOnce(&mut Amount), @@ -24,7 +24,7 @@ where #[allow(dead_code)] /// Reads an arbitrary value, applies update then writes it back -pub(super) fn value( +pub fn value( store: &mut Storage, key: &storage::Key, update: impl FnOnce(&mut T), diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index d4554d5176..928510b297 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -1,12 +1,18 @@ //! Logic and data types relating to tracking validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::{eyre, Result}; +use namada::ledger::eth_bridge::storage::vote_tracked; +use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada::types::address::Address; +use namada::types::storage::BlockHeight; use namada::types::voting_power::FractionalVotingPower; +use crate::node::ledger::protocol::transactions::read; + #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -26,3 +32,64 @@ pub struct VoteTracking { // or not from looking at `voting_power` pub seen: bool, } + +pub fn calculate_new_vote_tracking( + seen_by: &BTreeSet<(Address, BlockHeight)>, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, +) -> Result { + let mut seen_by_voting_power = FractionalVotingPower::default(); + for (validator, block_height) in seen_by { + match voting_powers + .get(&(validator.to_owned(), block_height.to_owned())) + { + Some(voting_power) => seen_by_voting_power += voting_power, + None => { + return Err(eyre!( + "voting power was not provided for validator {}", + validator + )); + } + }; + } + + let newly_confirmed = + seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; + Ok(VoteTracking { + voting_power: seen_by_voting_power, + seen_by: seen_by + .iter() + .map(|(validator, _)| validator.to_owned()) + .collect(), + seen: newly_confirmed, + }) +} + +pub fn calculate_updated_vote_tracking( + store: &mut Storage, + keys: &vote_tracked::Keys, + _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + T: BorshDeserialize, +{ + let _body: T = read::value(store, &keys.body())?; + let seen: bool = read::value(store, &keys.seen())?; + let seen_by: BTreeSet
= read::value(store, &keys.seen_by())?; + let voting_power: FractionalVotingPower = + read::value(store, &keys.voting_power())?; + + let vote_tracking = VoteTracking { + voting_power, + seen_by, + seen, + }; + + tracing::warn!( + ?vote_tracking, + "Updating events is not implemented yet, so the returned VoteTracking \ + will be identical to the one in storage", + ); + Ok(vote_tracking) +} From d0a7d3b52918e50d22e97ae7a77bd383973e06b9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:30:31 +0100 Subject: [PATCH 1001/1995] Move utils module --- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 2 +- apps/src/lib/node/ledger/protocol/transactions/mod.rs | 3 +++ .../protocol/transactions/{ethereum_events => }/utils.rs | 0 3 files changed, 4 insertions(+), 1 deletion(-) rename apps/src/lib/node/ledger/protocol/transactions/{ethereum_events => }/utils.rs (100%) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 774de488c5..2b1f9591df 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -3,7 +3,6 @@ //! transactions. mod eth_msgs; mod events; -mod utils; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; @@ -21,6 +20,7 @@ use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; +use crate::node::ledger::protocol::transactions::utils; use crate::node::ledger::protocol::transactions::votes::{ calculate_new_vote_tracking, calculate_updated_vote_tracking, }; diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 45d3d684be..79c3cb990d 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -14,3 +14,6 @@ mod read; #[cfg(not(feature = "abcipp"))] mod update; + +#[cfg(not(feature = "abcipp"))] +mod utils; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs similarity index 100% rename from apps/src/lib/node/ledger/protocol/transactions/ethereum_events/utils.rs rename to apps/src/lib/node/ledger/protocol/transactions/utils.rs From ced2cc3dc0ac29a04ccaefb399adcda168e10f6b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:44:44 +0100 Subject: [PATCH 1002/1995] Fix cargo doc --- shared/src/ledger/eth_bridge/storage/vote_tracked.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs index 077546a0f7..13468f814c 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs @@ -19,7 +19,7 @@ pub struct Keys { } impl Keys { - /// Get the `body` key- there should be a Borsh-serialized [`T`] stored + /// Get the `body` key - there should be a Borsh-serialized `T` stored /// here. pub fn body(&self) -> Key { self.prefix From cd201270448bde6ce3702c5c39523c69cf487d79 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:50:35 +0100 Subject: [PATCH 1003/1995] Move vote tracking write logic out --- .../transactions/ethereum_events/mod.rs | 48 +++++-------------- .../ledger/protocol/transactions/votes.rs | 25 +++++++++- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 2b1f9591df..4aad8c7ad1 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -6,7 +6,6 @@ mod events; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use borsh::BorshSerialize; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; use namada::ledger::eth_bridge::storage::vote_tracked; @@ -14,7 +13,6 @@ use namada::ledger::pos::types::WeightedValidator; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; -use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::{self, BlockHeight}; use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; @@ -22,7 +20,7 @@ use namada::types::voting_power::FractionalVotingPower; use crate::node::ledger::protocol::transactions::utils; use crate::node::ledger::protocol::transactions::votes::{ - calculate_new_vote_tracking, calculate_updated_vote_tracking, + calculate_new, calculate_updated, write, }; use crate::node::ledger::shell::queries::QueriesExt; @@ -181,8 +179,7 @@ where let (vote_tracking, changed, confirmed) = if !exists_in_storage { tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); - let vote_tracking = - calculate_new_vote_tracking(&update.seen_by, voting_powers)?; + let vote_tracking = calculate_new(&update.seen_by, voting_powers)?; let changed = eth_msg_keys.into_iter().collect(); let confirmed = vote_tracking.seen; (vote_tracking, changed, confirmed) @@ -191,11 +188,8 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let vote_tracking = calculate_updated_vote_tracking( - storage, - ð_msg_keys, - voting_powers, - )?; + let vote_tracking = + calculate_updated(storage, ð_msg_keys, voting_powers)?; let changed = BTreeSet::default(); // TODO(namada#515): calculate changed keys let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); @@ -205,34 +199,14 @@ where body, vote_tracking, }; - write_eth_msg(storage, ð_msg_keys, ð_msg_post)?; - Ok((changed, confirmed)) -} - -fn write_eth_msg( - storage: &mut Storage, - eth_msg_keys: &vote_tracked::Keys, - eth_msg: &EthMsg, -) -> Result<()> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - tracing::debug!("writing EthMsg - {:#?}", eth_msg); - storage.write(ð_msg_keys.body(), ð_msg.body.try_to_vec()?)?; - storage.write( - ð_msg_keys.seen(), - ð_msg.vote_tracking.seen.try_to_vec()?, - )?; - storage.write( - ð_msg_keys.seen_by(), - ð_msg.vote_tracking.seen_by.try_to_vec()?, - )?; - storage.write( - ð_msg_keys.voting_power(), - ð_msg.vote_tracking.voting_power.try_to_vec()?, + tracing::debug!("writing EthMsg - {:#?}", ð_msg_post); + write( + storage, + ð_msg_keys, + ð_msg_post.body, + ð_msg_post.vote_tracking, )?; - Ok(()) + Ok((changed, confirmed)) } #[cfg(test)] diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index 928510b297..a68864c860 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -33,7 +33,7 @@ pub struct VoteTracking { pub seen: bool, } -pub fn calculate_new_vote_tracking( +pub fn calculate_new( seen_by: &BTreeSet<(Address, BlockHeight)>, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result { @@ -64,7 +64,7 @@ pub fn calculate_new_vote_tracking( }) } -pub fn calculate_updated_vote_tracking( +pub fn calculate_updated( store: &mut Storage, keys: &vote_tracked::Keys, _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, @@ -93,3 +93,24 @@ where ); Ok(vote_tracking) } + +pub fn write( + storage: &mut Storage, + keys: &vote_tracked::Keys, + body: &T, + vote_tracking: &VoteTracking, +) -> Result<()> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + T: BorshSerialize, +{ + storage.write(&keys.body(), &body.try_to_vec()?)?; + storage.write(&keys.seen(), &vote_tracking.seen.try_to_vec()?)?; + storage.write(&keys.seen_by(), &vote_tracking.seen_by.try_to_vec()?)?; + storage.write( + &keys.voting_power(), + &vote_tracking.voting_power.try_to_vec()?, + )?; + Ok(()) +} From d84ef2ff35ff35ca46edcaa46f5a39e1add61e78 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:52:49 +0100 Subject: [PATCH 1004/1995] Move get_active_validators to utils --- .../transactions/ethereum_events/mod.rs | 29 ++++--------------- .../ledger/protocol/transactions/utils.rs | 23 +++++++++++++++ .../ledger/protocol/transactions/votes.rs | 3 +- shared/src/types/hash.rs | 2 +- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 4aad8c7ad1..dd661d0bdd 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -4,12 +4,11 @@ mod eth_msgs; mod events; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; use namada::ledger::eth_bridge::storage::vote_tracked; -use namada::ledger::pos::types::WeightedValidator; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; @@ -18,11 +17,12 @@ use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; -use crate::node::ledger::protocol::transactions::utils; +use crate::node::ledger::protocol::transactions::utils::{ + self, get_active_validators, +}; use crate::node::ledger::protocol::transactions::votes::{ calculate_new, calculate_updated, write, }; -use crate::node::ledger::shell::queries::QueriesExt; /// The keys changed while applying a protocol transaction type ChangedKeys = BTreeSet; @@ -63,25 +63,6 @@ where }) } -fn get_active_validators( - storage: &Storage, - block_heights: HashSet, -) -> BTreeMap>> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - let mut active_validators = BTreeMap::default(); - for height in block_heights.into_iter() { - let epoch = storage.get_epoch(height).expect( - "The epoch of the last block height should always be known", - ); - _ = active_validators - .insert(height, storage.get_active_validators(Some(epoch))); - } - active_validators -} - /// Constructs a map of all validators who voted for an event to their /// fractional voting power for block heights at which they voted for an event fn get_voting_powers( @@ -217,7 +198,7 @@ mod tests { use namada::ledger::eth_bridge::storage::wrapped_erc20s; use namada::ledger::pos::namada_proof_of_stake::epoched::Epoched; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::pos::types::ValidatorSet; + use namada::ledger::pos::types::{ValidatorSet, WeightedValidator}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::testing::TestStorage; use namada::ledger::storage::traits::Sha256Hasher; diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index 877b0179f4..68f6b8a08e 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -3,11 +3,34 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; use itertools::Itertools; use namada::ledger::pos::types::{VotingPower, WeightedValidator}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; +use crate::node::ledger::shell::queries::QueriesExt; + +pub(super) fn get_active_validators( + storage: &Storage, + block_heights: HashSet, +) -> BTreeMap>> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let mut active_validators = BTreeMap::default(); + for height in block_heights.into_iter() { + let epoch = storage.get_epoch(height).expect( + "The epoch of the last block height should always be known", + ); + _ = active_validators + .insert(height, storage.get_active_validators(Some(epoch))); + } + active_validators +} + /// Extract all the voters and the block heights at which they voted from the /// given events. pub(super) fn get_votes_for_events<'a>( diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index a68864c860..b922418fee 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -6,7 +6,8 @@ use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use namada::ledger::eth_bridge::storage::vote_tracked; -use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::storage::BlockHeight; use namada::types::voting_power::FractionalVotingPower; diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 7764bc6fc5..ee06451635 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -147,4 +147,4 @@ mod tests { let _: Hash = hex_hash.try_into().unwrap(); } } - +} From d13ec5c4bddfcdd31f80eb105baa053663033b27 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 14:54:32 +0100 Subject: [PATCH 1005/1995] Revert wasm/checksums.json --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index acd4bc6b94..2634122c96 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bridge_pool.wasm": "tx_bridge_pool.dee69ea94dca815418a893081463df0f98046ff8df3b18273e12093f29c845ab.wasm", - "tx_from_intent.wasm": "tx_from_intent.984344584332d0d6984ec3d1c1b90ed3621c2733e1d888a760f2c3db1df36a55.wasm", - "tx_ibc.wasm": "tx_ibc.e6b32fda57dd22fdc344d69b838c60d83b64bf23f2617e435f1a0eb81932583d.wasm", - "tx_init_account.wasm": "tx_init_account.4bd496a146830c0404c84fd3041ea9df88c864b90964abaa90be4baed30495ee.wasm", - "tx_init_nft.wasm": "tx_init_nft.a1ac2554b952518615fc59fc1dca335ba39c12ad0c1f2068174bc66c453fa64d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6c108ba6e59cfe2447a5be92d7ff9c32ec8e1711a0dfb8bc58e1a87d7eca4a2e.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e5907e84f56fe41b325cefc73e227f755f2532b5e9304ea62f74b21eced125f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.27310454c09ef7348dbc1ecd64514d7cc853231e6e3256d5fb5b98a2740222fe.wasm", - "tx_transfer.wasm": "tx_transfer.27424affe1a1695a3bd057ebfd61924f45d909099f555a4fd0c4b40a036799a0.wasm", - "tx_unbond.wasm": "tx_unbond.4922603455afa8a0e2fe443e46c73e51f27cc9d38a1dc205f5ce1db734c2dfeb.wasm", - "tx_update_vp.wasm": "tx_update_vp.e65aaf5fee4e712831137d88a0049161f102cb21802687760d35c215776aaa94.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.98f7d46dc9b819575fa314ea7f86bfe0716c68c49360a7c08bf3b5ce3dca0de2.wasm", - "tx_withdraw.wasm": "tx_withdraw.8d03dcbc6e05524940609125a7101472fd1f52f6bda2412ece20d55a5c16ac14.wasm", - "vp_nft.wasm": "vp_nft.6d7c545feaf8e91a16a1620fe75f956cd3daf5a345f559ec39ddecf6556b96bc.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.0bdf61b141ff4f68ffdcc3036a0e82eedf7d7a920f504d3ef0a890ecbdd4fe38.wasm", - "vp_token.wasm": "vp_token.b60774ff41b664a4d70b9f0ec844ac3082ede65efac8352347dee53af556dd8b.wasm", - "vp_user.wasm": "vp_user.cb5fe67fd0d25bffc5013aaa9b3b2815779a657f9ae58d3ecf8f62baa010c239.wasm" + "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", + "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", + "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", + "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", + "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", + "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", + "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", + "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", + "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", + "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", + "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", + "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" } \ No newline at end of file From 426c5ad354df9a4bc61eb381c859ba36f171f1ad Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 17 Oct 2022 15:00:42 +0100 Subject: [PATCH 1006/1995] Fix up TODOs and unnecessary clone --- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 3 +-- apps/src/lib/node/ledger/protocol/transactions/votes.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index dd661d0bdd..1bed6444bb 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -150,7 +150,6 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let body = update.body.to_owned(); let eth_msg_keys = vote_tracked::Keys::from(&update.body); // we arbitrarily look at whether the seen key is present to @@ -177,7 +176,7 @@ where (vote_tracking, changed, confirmed) }; let eth_msg_post = EthMsg { - body, + body: update.body, vote_tracking, }; tracing::debug!("writing EthMsg - {:#?}", ð_msg_post); diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index b922418fee..0f83f78e26 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -29,8 +29,6 @@ pub struct VoteTracking { pub seen_by: BTreeSet
, /// Whether this event has been acted on or not - this should only ever /// transition from `false` to `true`, once there is enough voting power - // TODO: this field is redundant - we can derive whether an event is seen - // or not from looking at `voting_power` pub seen: bool, } @@ -75,6 +73,7 @@ where H: 'static + StorageHasher + Sync, T: BorshDeserialize, { + // TODO(namada#515): implement this let _body: T = read::value(store, &keys.body())?; let seen: bool = read::value(store, &keys.seen())?; let seen_by: BTreeSet
= read::value(store, &keys.seen_by())?; From 0ece5c09c29d21d1c3be94448262785101150a94 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 14:24:01 +0000 Subject: [PATCH 1007/1995] 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 2634122c96..56704917b0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7a56e31fb9977d230d5d601129fd11e67bf54159ab2d694184fc934ec2244314.wasm", - "tx_ibc.wasm": "tx_ibc.fe0036ec90159b3e704247201345ddff0aea5fa2e234e85d0e4df895b509d692.wasm", - "tx_init_account.wasm": "tx_init_account.313e8ab8c7eac72e75f916c67a6c7ec2f204f77d60d89e825acc78b4856a70c5.wasm", - "tx_init_nft.wasm": "tx_init_nft.5339cef3b36a3a31ea370cd67e6a43f99e03d13032bb786335976dd251988d4e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d5598fcac82ebd5c618b84f4582992a2de80c2dfbb37f10fb574afaa36045ec2.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e1374e7bcd0973cf7d3166085946ddf43443bb67037b56cfa6f2a5715d44ef2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.15b0e03bc27c41feda24918f56263f85d84b26f43be831998c6c619baa92a601.wasm", - "tx_transfer.wasm": "tx_transfer.2195fe962f9cb0c776d8518d0df9a450cc2521510d60423b57f0e2744ce38564.wasm", - "tx_unbond.wasm": "tx_unbond.a7313bb14d1c3a852630f86fc07d5e048e5df19a8f4be193d95c21fbeb49ae03.wasm", - "tx_update_vp.wasm": "tx_update_vp.f21d0233f9629193cea49c9c3742c2cac6764b09dd0adc7360d1f649543f2c39.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.08daa0021aac28a1255ebb28005e1f07e01fc8118496934a0d46ee4456da7470.wasm", - "tx_withdraw.wasm": "tx_withdraw.0de22e367b9931045cf4c6b844cdd2e1390796e9cc7f8b15288d7753913e3ca9.wasm", - "vp_nft.wasm": "vp_nft.4656bec0cf0ff84b672605e45e25b16915a36ee982ffddf1ee0b7d6c72871e5a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.41c53f6536966989374dfb4b732b389919c0b980fba316ed842dc4b889b3abc1.wasm", - "vp_token.wasm": "vp_token.6eff1ea735d6b79e72e9f860a261e3eb91004b829cda2e3e3cc8f1b24e3b6715.wasm", - "vp_user.wasm": "vp_user.7829b1e5eb367785abc50bb7eaefa917665bec380495c162e83e08eaa57065f7.wasm" + "tx_from_intent.wasm": "tx_from_intent.11f65405520a8654684b5f8c1469709a21b8975abfb97201dda4854efd796edb.wasm", + "tx_ibc.wasm": "tx_ibc.5bc81b3873a7e654594f7ec9880895d54feff3bd0dc663c206a8bb8d33db47af.wasm", + "tx_init_account.wasm": "tx_init_account.63483e5eba74a72adaad09eca036488cc75596cf20a1b7e40bf1f94524d8fbf9.wasm", + "tx_init_nft.wasm": "tx_init_nft.c4d63451431a3a14a5d7be96eb7502bf9078fe2d1427657f1550ee9420bbd85d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e546b64ca0a4239d5566d18c41c894f1d798e184df87c9ca64ba036073be2732.wasm", + "tx_init_validator.wasm": "tx_init_validator.873b826233c49c509acfe411f536c8e54647e14f6d79492d12d62f0ae6328ebd.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.5ce93ac41d6d7e9451beeb05524ba0c312d38cf901eeaeacc9543202fd1a98ec.wasm", + "tx_transfer.wasm": "tx_transfer.e07585d0ec6d0ddef61925b2fe53eee5288e31b0b2320877104b006dee7b583b.wasm", + "tx_unbond.wasm": "tx_unbond.84651e6300fb97545a1fa168aa0affb2600240d3752ff56c4e241e8e289a5b1c.wasm", + "tx_update_vp.wasm": "tx_update_vp.1c5d6146e3c0d1f4a63269219afb02ac9b4b6ff05347d3978975260e3bb2a300.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3aeca8fa5d199363637cae74249b11d202d346eff09367fcbfcee1c6cddaf392.wasm", + "tx_withdraw.wasm": "tx_withdraw.dc2ef745109ac07bc583fcc2e04ecae4cec4fa469f4193f319c8f57a2cdff8fb.wasm", + "vp_nft.wasm": "vp_nft.73b71e2c0fa2b9ac939263c14477e2fb1aac06c378e71e0db65edad9d8d7cca8.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.bf4c667bcb103d01a5f3085868474e1a79100d8cd654ea0760c9432628bf389b.wasm", + "vp_token.wasm": "vp_token.696eb442ee3d7671e90bc811292d85ad7277e261e6b6f5c84db15f739c8305a8.wasm", + "vp_user.wasm": "vp_user.67a7b4cd440cd97a33f274a76195c4f14a9af00ed3c93e0c8350d97943a47b88.wasm" } \ No newline at end of file From acbd0b24d203e47371385501090b0dbac3d8b51a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 18 Oct 2022 09:43:14 +0100 Subject: [PATCH 1008/1995] Rename const to ETH_MSGS_PREFIX_KEY_SEGMENT --- shared/src/ledger/eth_bridge/storage/vote_tracked.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs index 13468f814c..b6e99d5243 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs @@ -4,7 +4,7 @@ use crate::types::hash::Hash; use crate::types::storage::Key; #[allow(missing_docs)] -pub const PREFIX_KEY_SEGMENT: &str = "eth_msgs"; +pub const ETH_MSGS_PREFIX_KEY_SEGMENT: &str = "eth_msgs"; const BODY_KEY_SEGMENT: &str = "body"; const SEEN_KEY_SEGMENT: &str = "seen"; @@ -70,7 +70,7 @@ impl IntoIterator for &Keys { /// are stored pub fn eth_msgs_prefix() -> Key { super::prefix() - .push(&PREFIX_KEY_SEGMENT.to_owned()) + .push(Ð_MSGS_PREFIX_KEY_SEGMENT.to_owned()) .expect("should always be able to construct this key") } @@ -122,7 +122,7 @@ mod test { assert_matches!(ð_msgs_prefix().segments[..], [ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(s), - ] if s == PREFIX_KEY_SEGMENT) + ] if s == ETH_MSGS_PREFIX_KEY_SEGMENT) } #[test] @@ -131,7 +131,7 @@ mod test { let keys: Keys = (&event).into(); let prefix = vec![ DbKeySeg::AddressSeg(ADDRESS), - DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(ETH_MSGS_PREFIX_KEY_SEGMENT.to_owned()), DbKeySeg::StringSeg(hash), ]; let body_key = keys.body(); @@ -185,7 +185,7 @@ mod test { let keys: Keys = (&event).into(); let expected = vec![ DbKeySeg::AddressSeg(ADDRESS), - DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(ETH_MSGS_PREFIX_KEY_SEGMENT.to_owned()), DbKeySeg::StringSeg(hash), ]; assert_eq!(&keys.prefix.segments[..], &expected[..]); @@ -197,7 +197,7 @@ mod test { let keys: Keys = (&event.hash().unwrap()).into(); let expected = vec![ DbKeySeg::AddressSeg(ADDRESS), - DbKeySeg::StringSeg(PREFIX_KEY_SEGMENT.to_owned()), + DbKeySeg::StringSeg(ETH_MSGS_PREFIX_KEY_SEGMENT.to_owned()), DbKeySeg::StringSeg(hash), ]; assert_eq!(&keys.prefix.segments[..], &expected[..]); From f7d51275795ec69a27f7274de0077c8ba3e55a93 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 18 Oct 2022 09:48:43 +0100 Subject: [PATCH 1009/1995] Update test names to reference eth --- shared/src/ledger/eth_bridge/storage/vote_tracked.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs index b6e99d5243..79176bbeb0 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs @@ -118,7 +118,7 @@ mod test { } #[test] - fn test_prefix() { + fn test_eth_msgs_prefix() { assert_matches!(ð_msgs_prefix().segments[..], [ DbKeySeg::AddressSeg(ADDRESS), DbKeySeg::StringSeg(s), @@ -126,7 +126,7 @@ mod test { } #[test] - fn test_keys_all_keys() { + fn test_ethereum_event_keys_all_keys() { let (event, hash) = helpers::arbitrary_event_with_hash(); let keys: Keys = (&event).into(); let prefix = vec![ @@ -164,7 +164,7 @@ mod test { } #[test] - fn test_keys_into_iter() { + fn test_ethereum_event_keys_into_iter() { let (event, _) = helpers::arbitrary_event_with_hash(); let keys: Keys = (&event).into(); let as_keys: Vec<_> = keys.into_iter().collect(); @@ -180,7 +180,7 @@ mod test { } #[test] - fn test_keys_from_ethereum_event() { + fn test_ethereum_event_keys_from_ethereum_event() { let (event, hash) = helpers::arbitrary_event_with_hash(); let keys: Keys = (&event).into(); let expected = vec![ @@ -192,7 +192,7 @@ mod test { } #[test] - fn test_keys_from_hash() { + fn test_ethereum_event_keys_from_hash() { let (event, hash) = helpers::arbitrary_event_with_hash(); let keys: Keys = (&event.hash().unwrap()).into(); let expected = vec![ From 638afcc05f39ffed931d6d63439c990cf1a0ad32 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 18 Oct 2022 09:51:52 +0100 Subject: [PATCH 1010/1995] Make Keys use PhantomData<&'static T> --- shared/src/ledger/eth_bridge/storage/vote_tracked.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs index 79176bbeb0..aa6e34f5be 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tracked.rs @@ -12,10 +12,10 @@ const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; /// Generator for the keys under which details of a vote tracked event is stored -pub struct Keys { +pub struct Keys { /// The prefix under which the details of a vote tracked event is stored pub prefix: Key, - _phantom: std::marker::PhantomData, + _phantom: std::marker::PhantomData<&'static T>, } impl Keys { From fa942a93471f8f5a189e3b1d2c2a82307a4a9157 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 19 Oct 2022 14:32:37 +0100 Subject: [PATCH 1011/1995] Rename VoteTracking -> Tally --- .../transactions/ethereum_events/eth_msgs.rs | 6 ++-- .../transactions/ethereum_events/mod.rs | 4 +-- .../ledger/protocol/transactions/votes.rs | 31 +++++++++---------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index c080ac0b6a..47e0caf111 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -6,7 +6,7 @@ use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::node::ledger::protocol::transactions::votes::VoteTracking; +use crate::node::ledger::protocol::transactions::votes::Tally; /// Represents an Ethereum event being seen by some validators #[derive( @@ -49,8 +49,8 @@ impl From for EthMsgUpdate { pub struct EthMsg { /// The event being stored pub body: EthereumEvent, - /// Tracking of votes for this event - pub vote_tracking: VoteTracking, + /// Tallying of votes for this event + pub votes: Tally, } #[cfg(test)] diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 1bed6444bb..e5a70c098e 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -177,14 +177,14 @@ where }; let eth_msg_post = EthMsg { body: update.body, - vote_tracking, + votes: vote_tracking, }; tracing::debug!("writing EthMsg - {:#?}", ð_msg_post); write( storage, ð_msg_keys, ð_msg_post.body, - ð_msg_post.vote_tracking, + ð_msg_post.votes, )?; Ok((changed, confirmed)) } diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index 0f83f78e26..6a6f9102bb 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -1,4 +1,4 @@ -//! Logic and data types relating to tracking validators' votes for pieces of +//! Logic and data types relating to tallying validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes use std::collections::{BTreeSet, HashMap}; @@ -17,9 +17,9 @@ use crate::node::ledger::protocol::transactions::read; #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] -/// Represents all the information needed to track a piece of data that may be +/// Represents all the information needed to tally a piece of data that may be /// voted for over multiple epochs -pub struct VoteTracking { +pub struct Tally { /// The total voting power that's voted for this event across all epochs pub voting_power: FractionalVotingPower, /// The addresses of validators that voted for this event. We use a @@ -35,7 +35,7 @@ pub struct VoteTracking { pub fn calculate_new( seen_by: &BTreeSet<(Address, BlockHeight)>, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result { +) -> Result { let mut seen_by_voting_power = FractionalVotingPower::default(); for (validator, block_height) in seen_by { match voting_powers @@ -53,7 +53,7 @@ pub fn calculate_new( let newly_confirmed = seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; - Ok(VoteTracking { + Ok(Tally { voting_power: seen_by_voting_power, seen_by: seen_by .iter() @@ -67,7 +67,7 @@ pub fn calculate_updated( store: &mut Storage, keys: &vote_tracked::Keys, _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result +) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -80,25 +80,25 @@ where let voting_power: FractionalVotingPower = read::value(store, &keys.voting_power())?; - let vote_tracking = VoteTracking { + let tally = Tally { voting_power, seen_by, seen, }; tracing::warn!( - ?vote_tracking, - "Updating events is not implemented yet, so the returned VoteTracking \ + ?tally, + "Updating events is not implemented yet, so the returned vote tally \ will be identical to the one in storage", ); - Ok(vote_tracking) + Ok(tally) } pub fn write( storage: &mut Storage, keys: &vote_tracked::Keys, body: &T, - vote_tracking: &VoteTracking, + tally: &Tally, ) -> Result<()> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -106,11 +106,8 @@ where T: BorshSerialize, { storage.write(&keys.body(), &body.try_to_vec()?)?; - storage.write(&keys.seen(), &vote_tracking.seen.try_to_vec()?)?; - storage.write(&keys.seen_by(), &vote_tracking.seen_by.try_to_vec()?)?; - storage.write( - &keys.voting_power(), - &vote_tracking.voting_power.try_to_vec()?, - )?; + storage.write(&keys.seen(), &tally.seen.try_to_vec()?)?; + storage.write(&keys.seen_by(), &tally.seen_by.try_to_vec()?)?; + storage.write(&keys.voting_power(), &tally.voting_power.try_to_vec()?)?; Ok(()) } From 1d15e12d450212c93d1784cc82489337e6a0f0a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Oct 2022 13:33:49 +0000 Subject: [PATCH 1012/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index d61712a5fd..f555b3064e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.bb67d406e6c31218d32667ef8debed9be77dbadb33ee52a30d9fc377e3f6876c.wasm", + "tx_bond.wasm": "tx_bond.d0ce8b21186695f0b14c6f8f1649b73e61e91dd3c357207088f23992127ce031.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9915822bea588b0a18b4ec51bc249e88fdc3041bc3eddaaa2cc880dcd23c5eb0.wasm", - "tx_ibc.wasm": "tx_ibc.913192b268db668ebba6b415eea106c19e742b9b6be6ebb795a661dca82482af.wasm", - "tx_init_account.wasm": "tx_init_account.bd544ce16dae46177f9d9bd5281a53b4f4fe741833013a1f412b1f104c4bf6a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.1f1b18628e97758d837d0f25c313979cc529abc56797be240ce4c74032d603c5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ad5ba0ef45cef6b59dab228dfaeb0dc643235639851c1adcff5d52152d2c01f5.wasm", - "tx_init_validator.wasm": "tx_init_validator.9b1ddb7e6dca6beadbf42c4da91771c92228562f4239494e4e56d57fd3c3b538.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.bd061f19c4d2e5801f671adb60e7e4d787a6f493a28c29f20a57e4a256a8dcbc.wasm", - "tx_transfer.wasm": "tx_transfer.1a8d43355f663c040a29d6e10e6cdfe6c73634aa5cd6863e1f3f5d58fe9ec560.wasm", - "tx_unbond.wasm": "tx_unbond.84a2a809d5b617740b991ddc5b88ad213ffa17aa178afe1f657b90cecebbaa7a.wasm", - "tx_update_vp.wasm": "tx_update_vp.84b896bce441e449cdbfa2401f2da8ea6f0c10725c1a8d9e1a03763b49056817.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8b171e9bdc02f6321f0b7ae6a881d00d26d7c5ecb00bb7aa0d77e411767517c9.wasm", - "tx_withdraw.wasm": "tx_withdraw.aede879ea5c81b4e479d89b2e9c5d9e5d80bf7e0a9e8a0a8c7bf7f2bb2b049f5.wasm", - "vp_nft.wasm": "vp_nft.87b7fc9e596891dd676294e18a467fd8ef16c372a3e22f7876b125fb9c31d606.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3ed495ea630de4a8d5459e09882c1862e768b826654aa1b700ed13e1e5cf8827.wasm", - "vp_token.wasm": "vp_token.b95cfb6bff76ffec7034f29453482937297cf713fd7cfd3f4f7045a2cb044bca.wasm", - "vp_user.wasm": "vp_user.fac7690818a1bc043cdb13d65b7f9d687343845c2e31e3eed841dc074803bff9.wasm" + "tx_from_intent.wasm": "tx_from_intent.202f60934158067bec29454b4dd899ea902158e8321e51b6a479d94d8d1677c1.wasm", + "tx_ibc.wasm": "tx_ibc.a5a14d5c6061cbcfad16242ef71d9a949527c3f47453491cbe216d4da3c3bf52.wasm", + "tx_init_account.wasm": "tx_init_account.b2a97bd164b7c4804c2ab08a478bdb8235386be0d07d05f66ff3663868dc1dfc.wasm", + "tx_init_nft.wasm": "tx_init_nft.ba4c2519c1de43c1c638cfbcdbb734e7f1b6fbc3e960fd21f305dd8a1e6ac1f5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a2a3a6bf3972001aae12fb423fdc97eda6d4b017d8409d5f4b721b4eb03e0d37.wasm", + "tx_init_validator.wasm": "tx_init_validator.012e64f8736402e9e9fefb667ea684bb9db15f1bb25d9d175b4ba193d724e583.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.b0715685db7b04cf7d9b8ec5ec57c63927e9133edfb9e68055c618c5d879ace7.wasm", + "tx_transfer.wasm": "tx_transfer.5692122746b321e787e1965d1f67f9f450ff8b33f1f1d24f2a3260034072c16b.wasm", + "tx_unbond.wasm": "tx_unbond.b167452a7e135d0c942d7c6fc9a03f37ef4255844c02e00158b91d59fed86939.wasm", + "tx_update_vp.wasm": "tx_update_vp.b0f682a2d5e621a3c5f76e4c2bf272d54e612fe63847e2d746a6d452cefc7823.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9444e00877c7c5b9d0da568dbfb7e9082ad4508e331e63f398c7fe75c3041e29.wasm", + "tx_withdraw.wasm": "tx_withdraw.4d49f78c85574c176c65d0aa3ab1002540ca5c660768f2833047e5bc380c52f7.wasm", + "vp_nft.wasm": "vp_nft.916a59e2abdac12f165ec07c783edb11642e03b911c41bc5a39d3c622b05ccdd.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.72bea74cb811103c258477bc155dc67c06ee6c14c105b8a5598b79519949b0b6.wasm", + "vp_token.wasm": "vp_token.886885caefe8e264644126bdb329d3e564e7ea9015df3093001c1a297dc35709.wasm", + "vp_user.wasm": "vp_user.bc69d9c0eb26a25f55ace17319f3e77ea8bda00c53c3222949f2b22d8e62cae6.wasm" } \ No newline at end of file From 5c20b7e118bca8c654f464117be72348e1a3f783 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 15:40:29 +0200 Subject: [PATCH 1013/1995] [fix]: Fixed some garbage created by rebasing --- shared/src/types/storage.rs | 39 +++---------------------------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 655c7be0ed..eee9b5b847 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,7 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::keccak::KeccakHash; +use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -643,41 +643,8 @@ impl KeySeg for Hash { impl KeySeg for KeccakHash { fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) + seg.try_into() + .map_err(|e: TryFromError| Error::ParseError(e.to_string())) } fn raw(&self) -> String { From 8735ed22ec0fe14771a332b63c337324d7bae94c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 19 Oct 2022 14:37:10 +0100 Subject: [PATCH 1014/1995] Rename vote_tracked -> vote_tallies --- .../protocol/transactions/ethereum_events/mod.rs | 10 +++++----- .../src/lib/node/ledger/protocol/transactions/votes.rs | 6 +++--- shared/src/ledger/eth_bridge/storage/mod.rs | 2 +- .../storage/{vote_tracked.rs => vote_tallies.rs} | 8 +++++--- 4 files changed, 14 insertions(+), 12 deletions(-) rename shared/src/ledger/eth_bridge/storage/{vote_tracked.rs => vote_tallies.rs} (95%) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index e5a70c098e..f6038589b3 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -8,7 +8,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; -use namada::ledger::eth_bridge::storage::vote_tracked; +use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; @@ -150,7 +150,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let eth_msg_keys = vote_tracked::Keys::from(&update.body); + let eth_msg_keys = vote_tallies::Keys::from(&update.body); // we arbitrarily look at whether the seen key is present to // determine if the /eth_msg already exists in storage, but maybe there @@ -244,7 +244,7 @@ mod tests { let changed_keys = apply_updates(&mut storage, updates, voting_powers)?; - let eth_msg_keys: vote_tracked::Keys = (&body).into(); + let eth_msg_keys: vote_tallies::Keys = (&body).into(); let wrapped_erc20_keys: wrapped_erc20s::Keys = (&asset).into(); assert_eq!( BTreeSet::from_iter(vec![ @@ -355,7 +355,7 @@ mod tests { tx_result.gas_used, 0, "No gas should be used for a derived transaction" ); - let eth_msg_keys = vote_tracked::Keys::from(&event); + let eth_msg_keys = vote_tallies::Keys::from(&event); let dai_keys = wrapped_erc20s::Keys::from(&DAI_ERC20_ETH_ADDRESS); assert_eq!( tx_result.changed_keys, @@ -408,7 +408,7 @@ mod tests { Err(err) => panic!("unexpected error: {:#?}", err), }; - let eth_msg_keys = vote_tracked::Keys::from(&event); + let eth_msg_keys = vote_tallies::Keys::from(&event); assert_eq!( tx_result.changed_keys, BTreeSet::from_iter(vec![ diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index 6a6f9102bb..b20e203c38 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -5,7 +5,7 @@ use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; -use namada::ledger::eth_bridge::storage::vote_tracked; +use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; @@ -65,7 +65,7 @@ pub fn calculate_new( pub fn calculate_updated( store: &mut Storage, - keys: &vote_tracked::Keys, + keys: &vote_tallies::Keys, _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result where @@ -96,7 +96,7 @@ where pub fn write( storage: &mut Storage, - keys: &vote_tracked::Keys, + keys: &vote_tallies::Keys, body: &T, tally: &Tally, ) -> Result<()> diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index a383666fb9..595f403034 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -1,6 +1,6 @@ //! Functionality for accessing the storage subspace pub mod bridge_pool; -pub mod vote_tracked; +pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; diff --git a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs similarity index 95% rename from shared/src/ledger/eth_bridge/storage/vote_tracked.rs rename to shared/src/ledger/eth_bridge/storage/vote_tallies.rs index aa6e34f5be..629eba066f 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tracked.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs @@ -1,4 +1,4 @@ -//! Functionality for accessing the `eth_msgs/...` subspace +//! Functionality for accessing keys to do with tallying votes use crate::types::ethereum_events::EthereumEvent; use crate::types::hash::Hash; use crate::types::storage::Key; @@ -11,9 +11,11 @@ const SEEN_KEY_SEGMENT: &str = "seen"; const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; -/// Generator for the keys under which details of a vote tracked event is stored +/// Generator for the keys under which details of votes for some piece of data +/// is stored pub struct Keys { - /// The prefix under which the details of a vote tracked event is stored + /// The prefix under which the details of a piece of data for which we are + /// tallying votes is stored pub prefix: Key, _phantom: std::marker::PhantomData<&'static T>, } From e55ed8d4bf2c5c6fbd317b2d994dcaed7dbd9df3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 11:55:11 +0100 Subject: [PATCH 1015/1995] Add docstrings --- apps/src/lib/node/ledger/protocol/transactions/votes.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index b20e203c38..d597d9e3b2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -32,6 +32,8 @@ pub struct Tally { pub seen: bool, } +/// Calculate a new [`Tally`] based on some validators' fractional voting powers +/// as specific block heights pub fn calculate_new( seen_by: &BTreeSet<(Address, BlockHeight)>, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, @@ -63,6 +65,8 @@ pub fn calculate_new( }) } +/// Calculate an updated [`Tally`] based on one that is in storage under `keys`, +/// and some new votes pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, From 2ac95d34e21e42015f59ec5228376dee007d994d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Oct 2022 11:26:40 +0000 Subject: [PATCH 1016/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 56704917b0..2270217a90 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { + "tx_bond.wasm": "tx_bond.4e82663ada91d4f2081a9ca2f01216394f960c236dcdf027fe17431b1dac0af4.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.11f65405520a8654684b5f8c1469709a21b8975abfb97201dda4854efd796edb.wasm", - "tx_ibc.wasm": "tx_ibc.5bc81b3873a7e654594f7ec9880895d54feff3bd0dc663c206a8bb8d33db47af.wasm", - "tx_init_account.wasm": "tx_init_account.63483e5eba74a72adaad09eca036488cc75596cf20a1b7e40bf1f94524d8fbf9.wasm", - "tx_init_nft.wasm": "tx_init_nft.c4d63451431a3a14a5d7be96eb7502bf9078fe2d1427657f1550ee9420bbd85d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e546b64ca0a4239d5566d18c41c894f1d798e184df87c9ca64ba036073be2732.wasm", - "tx_init_validator.wasm": "tx_init_validator.873b826233c49c509acfe411f536c8e54647e14f6d79492d12d62f0ae6328ebd.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.5ce93ac41d6d7e9451beeb05524ba0c312d38cf901eeaeacc9543202fd1a98ec.wasm", - "tx_transfer.wasm": "tx_transfer.e07585d0ec6d0ddef61925b2fe53eee5288e31b0b2320877104b006dee7b583b.wasm", - "tx_unbond.wasm": "tx_unbond.84651e6300fb97545a1fa168aa0affb2600240d3752ff56c4e241e8e289a5b1c.wasm", - "tx_update_vp.wasm": "tx_update_vp.1c5d6146e3c0d1f4a63269219afb02ac9b4b6ff05347d3978975260e3bb2a300.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3aeca8fa5d199363637cae74249b11d202d346eff09367fcbfcee1c6cddaf392.wasm", - "tx_withdraw.wasm": "tx_withdraw.dc2ef745109ac07bc583fcc2e04ecae4cec4fa469f4193f319c8f57a2cdff8fb.wasm", - "vp_nft.wasm": "vp_nft.73b71e2c0fa2b9ac939263c14477e2fb1aac06c378e71e0db65edad9d8d7cca8.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.bf4c667bcb103d01a5f3085868474e1a79100d8cd654ea0760c9432628bf389b.wasm", - "vp_token.wasm": "vp_token.696eb442ee3d7671e90bc811292d85ad7277e261e6b6f5c84db15f739c8305a8.wasm", - "vp_user.wasm": "vp_user.67a7b4cd440cd97a33f274a76195c4f14a9af00ed3c93e0c8350d97943a47b88.wasm" + "tx_from_intent.wasm": "tx_from_intent.583dbdb4f2ea5183309a3da757a3670cd3fa5a3cc923471864ee290b6028497f.wasm", + "tx_ibc.wasm": "tx_ibc.64993c224847300a6801602e27a7c02ecb58cb5c7e7968dfe9092e6b4d6a1705.wasm", + "tx_init_account.wasm": "tx_init_account.5da56620f2d9c343ce3c5856575f2b7e8663a51887c61a220286ac1f65791a02.wasm", + "tx_init_nft.wasm": "tx_init_nft.795a4fb295a2daa00df7d23359a65a49db2fd3bd297a7a6fb070a0e02971a0f1.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4cd00565a47a6bfbe49a16f1279c61d2b393534f9eb7665d74106c817f54c689.wasm", + "tx_init_validator.wasm": "tx_init_validator.5267f2d0b18b71ccb8f0ed24227aa07152adc856f8c5ba1175dcbc1306efa8d2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.c8f7a5d7ff825d97f9ff8dd5dac7b84a6d3c95374187d596a4eab95658cfde35.wasm", + "tx_transfer.wasm": "tx_transfer.d6f272506ea16053ed8509e8934b370d4078049ac0bd00e7e5fb72d2df589a04.wasm", + "tx_unbond.wasm": "tx_unbond.1a70fd9309dae9f107dfd4b8bf7323b38ce197278dd21667f97ff4fa6bc35a00.wasm", + "tx_update_vp.wasm": "tx_update_vp.7115829f28245f51642788da490a6d8e2983230a269409aac8352448426a0fb0.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4e1bf81d2388ae72a48fbf4ba3424b3a86789f1c83b53d2da455436ce90a632a.wasm", + "tx_withdraw.wasm": "tx_withdraw.8f303839ba10b08b1d76a5aa67b3b023705862f09e2ff336bb3f0f6d3935582f.wasm", + "vp_nft.wasm": "vp_nft.adbafd6b28c09c729f58af01c87e6404b3bbe41a74214b242fe27b38a346a293.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.5a360669dd9cfbc1da75eb7db825ff33e72c16c207fc2cf702d66fe51774fc46.wasm", + "vp_token.wasm": "vp_token.a603df86f683731fadaf8096fdd38987f8ce038fad7df0d31ee3f3d8642df5bf.wasm", + "vp_user.wasm": "vp_user.e09b796d7d41bbeb89b32eeb3524c7882a95924947a66f74135d01537ee9eb61.wasm" } \ No newline at end of file From b3f6ec741aef7764f987dbdb7892fce4bb44124e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1017/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 31 +++- apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 62 +++++--- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 54 ++++++- 7 files changed, 287 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 4cbedca8bc..25816cc426 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -17,7 +17,10 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block. Epoch, - /// Read a storage value with exact storage key. + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), + /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix. Prefix(storage::Key), @@ -29,6 +32,16 @@ pub enum Path { Applied { tx_hash: Hash }, } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -39,6 +52,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -59,6 +75,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } Path::Accepted { tx_hash } => { write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } @@ -77,6 +100,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9203d4f3e8..9208f32f2d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,22 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -21,6 +30,7 @@ use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::events::log::dumb_queries; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -87,6 +97,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, Path::Accepted { tx_hash } => { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) @@ -337,6 +355,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7628c11a36..f71320e095 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -83,7 +84,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.store.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -114,9 +115,9 @@ impl BridgePoolTree { } } - /// Return the root as a [struct@`Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + /// Return the root as a [`struct@Hash`] type. + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store @@ -334,6 +335,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { use std::array; @@ -396,9 +422,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -433,7 +458,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -489,9 +514,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -625,7 +649,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(array::from_ref(&key), vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -659,7 +683,7 @@ mod test_bridge_pool_tree { vec![transfers.remove(0)], ) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -691,7 +715,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -721,7 +745,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -751,7 +775,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -781,7 +805,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, transfers) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -812,7 +836,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(&keys, values) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -879,7 +903,7 @@ mod test_bridge_pool_tree { values.push(transfer); } let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 3add9dd7f0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index c2464c52d1..12ccd198a5 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -21,7 +21,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 67d77ac991..fc2082df4d 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 904c2c7eec..4c5c5496a3 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,10 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -56,13 +58,15 @@ pub struct PendingTransfer { impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, fee_from, nonce] + vec![version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -96,3 +100,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From 3c1e903cfb9c49bb5f1730a47ebb9e60e0740e3e Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1018/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 7 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 9 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 8 files changed, 235 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f18113ce..f038bdb2e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4328,6 +4328,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9208f32f2d..d53ca1fd55 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,13 +5,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -20,7 +20,8 @@ use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -364,13 +365,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -420,12 +433,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -438,22 +446,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -468,6 +468,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -869,10 +870,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1047,4 +1058,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..7c2cfbec2b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,6 +104,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f71320e095..5b3cce219b 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 12ccd198a5..8cc6fe6dcf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -430,15 +430,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl Into, ) -> Result<(u64, i64)> { + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); self.block.tree.update(key, value.clone())?; - let len = value.as_ref().len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4c5c5496a3..ebdce1d15f 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -43,6 +44,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -89,6 +91,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index eee9b5b847..50df74286c 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -245,6 +245,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -267,6 +268,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From a0b845b5ea47be56e0727b06651522d46e173aee Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1019/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/mod.rs | 13 ++++- apps/src/lib/node/ledger/shell/queries.rs | 18 +++---- shared/Cargo.toml | 1 - shared/src/types/key/secp256k1.rs | 60 ++++++++++++++++------- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f038bdb2e6..c2f18113ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4328,7 +4328,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4f6bdeedbe..f926d940b8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -874,10 +874,19 @@ mod test_utils { sig_bytes[0] = sig_bytes[0].wrapping_add(1); common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + common::Signature::Secp256k1(secp256k1::Signature( + ref sig, + ref recovery_id, + )) => { let mut sig_bytes = sig.serialize(); + let recovery_id_bytes = recovery_id.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) + let bytes: [u8; 65] = + [sig_bytes.as_slice(), [recovery_id_bytes].as_slice()] + .concat() + .try_into() + .unwrap(); + common::Signature::Secp256k1((&bytes).try_into().unwrap()) } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d53ca1fd55..0d384d50cd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,6 +16,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; @@ -1083,7 +1084,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1120,7 +1121,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1128,7 +1129,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1136,7 +1137,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1180,7 +1181,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1194,14 +1195,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7c2cfbec2b..ccb4a3752d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,7 +104,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 96b892fdbf..7ab6172774 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -266,7 +267,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(pub libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature, pub RecoveryId); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; @@ -304,6 +305,7 @@ impl Serialize for Signature { for elem in &arr[..] { seq.serialize_element(elem)?; } + seq.serialize_element(&self.1.serialize())?; seq.end() } } @@ -316,7 +318,7 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( @@ -325,13 +327,13 @@ impl<'de> Deserialize<'de> for Signature { )) } - fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + fn visit_seq(self, mut seq: A) -> Result<[u8; 65], A::Error> where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -341,23 +343,34 @@ impl<'de> Deserialize<'de> for Signature { } let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE, + libsecp256k1::util::SIGNATURE_SIZE + 1, ByteArrayVisitor, )?; - let sig = libsecp256k1::Signature::parse_standard(&arr_res) + let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); + let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); - Ok(Signature(sig.unwrap())) + Ok(Signature( + sig.unwrap(), + RecoveryId::parse(arr_res[64]).map_err(Error::custom)?, + )) } } impl BorshDeserialize for Signature { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first + let (sig_bytes, recovery_id) = BorshDeserialize::deserialize(buf)?; + Ok(Signature( - libsecp256k1::Signature::parse_standard( - &(BorshDeserialize::deserialize(buf)?), - ) - .map_err(|e| { + libsecp256k1::Signature::parse_standard(&sig_bytes).map_err( + |e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + }, + )?, + RecoveryId::parse(recovery_id).map_err(|e| { std::io::Error::new( ErrorKind::InvalidInput, format!("Error decoding secp256k1 signature: {}", e), @@ -369,7 +382,10 @@ impl BorshDeserialize for Signature { impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - BorshSerialize::serialize(&self.0.serialize(), writer) + BorshSerialize::serialize( + &(self.0.serialize(), self.1.serialize()), + writer, + ) } } @@ -405,12 +421,19 @@ impl PartialOrd for Signature { } } -impl TryFrom<&[u8; 64]> for Signature { +impl TryFrom<&[u8; 65]> for Signature { type Error = ParseSignatureError; - fn try_from(sig: &[u8; 64]) -> Result { - libsecp256k1::Signature::parse_standard(sig) - .map(Self) + fn try_from(sig: &[u8; 65]) -> Result { + let sig_bytes = sig[..64].try_into().unwrap(); + let recovery_id = RecoveryId::parse(sig[64]).map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + })?; + libsecp256k1::Signature::parse_standard(&sig_bytes) + .map(|sig| Self(sig, recovery_id)) .map_err(|err| { ParseSignatureError::InvalidEncoding(std::io::Error::new( ErrorKind::Other, @@ -467,7 +490,8 @@ impl super::SigScheme for SigScheme { 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) + let (sig, recovery_id) = libsecp256k1::sign(&message, &keypair.0); + Signature(sig, recovery_id) } } From eafbe024228749217e66a92eaea44916d3854145 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 15:20:59 +0200 Subject: [PATCH 1020/1995] [feat]: Fixed secp256k key signatures --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 13 ++++++++---- shared/src/types/key/dkg_session_keys.rs | 7 ++++-- shared/src/types/key/ed25519.rs | 13 ++++++++---- shared/src/types/key/mod.rs | 27 +++++++++++++++++++++--- shared/src/types/key/secp256k1.rs | 27 +++++++++++++++++------- wasm/tx_template/Cargo.lock | 7 ++++++ wasm/vp_template/Cargo.lock | 7 ++++++ wasm/wasm_source/Cargo.lock | 7 ++++++ wasm_for_tests/wasm_source/Cargo.lock | 7 ++++++ 11 files changed, 96 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f18113ce..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4301,6 +4301,7 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 59196e1ff0..633367053c 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -70,7 +71,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -78,7 +79,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -196,7 +199,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -204,7 +207,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index dbcf9fe04c..052461de9a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -106,7 +107,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -114,7 +115,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -203,7 +206,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -211,7 +214,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1c5ac85558..5a16838455 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -7,6 +7,7 @@ use std::hash::Hash; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -85,7 +86,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -96,7 +97,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -107,7 +108,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] @@ -356,6 +357,26 @@ 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 + } + common::PublicKey::Secp256k1(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + +/// 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 { + HEXUPPER.encode(raw_hash.as_ref()) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 7ab6172774..9c8825dd98 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -115,7 +116,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -123,7 +124,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -245,7 +248,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -253,7 +256,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } @@ -396,10 +401,16 @@ impl BorshSchema for Signature { 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 }; + // Encoded as `([u8; SIGNATURE_SIZE], u8)` + let signature = + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::declaration(); + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::add_definitions_recursively( + definitions, + ); + let recovery = "u8".into(); + let definition = borsh::schema::Definition::Tuple { + elements: vec![signature, recovery], + }; definitions.insert(Self::declaration(), definition); } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 64bad284a2..b59950f447 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3a3058df34..392c010281 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 2b891bf4e1..4905239cb0 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1402,6 +1408,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f0cabb9569..56e8a94bfb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -620,6 +620,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1403,6 +1409,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", From 8ea7ffd00c5cdf515e06781c76a0683f7c6184ef Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 1021/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++- .../ledger/eth_bridge/storage/bridge_pool.rs | 5 ++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0d384d50cd..e097e244ec 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -401,7 +401,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1225,4 +1225,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 5b3cce219b..9c9340a565 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -157,6 +157,11 @@ impl BridgePoolTree { } leaves.insert(hash); } + if !leaves.is_subset(&self.store) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; From c6edd9ca0bf2158415ffa4f9ab5db1f441b46e1e Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1022/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 11 +++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e097e244ec..3f9026c5fc 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -459,6 +459,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1221,6 +1223,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1263,7 +1267,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 9c9340a565..986a2c3441 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -160,7 +160,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.store) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; @@ -345,8 +346,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -361,12 +362,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index ebdce1d15f..f0a2d5f093 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -58,8 +58,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); @@ -68,7 +68,7 @@ impl Encode for PendingTransfer { let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, fee_from, nonce] + [version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -117,17 +117,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -139,13 +137,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9c8825dd98..89016530b0 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -419,6 +421,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From 1a32bcfe02f6589206b5e03dce65ad402f7c1b24 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 1023/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3f9026c5fc..d942a573be 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -457,6 +457,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1221,6 +1223,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index f0a2d5f093..4cfc557987 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -133,6 +134,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -141,11 +144,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..24e0145ac5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -301,6 +301,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 034404a5b69839d7281382fc30acad37951ce500 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1024/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d942a573be..b1951eb800 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -18,7 +18,6 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From f92f5f6f47b7b3acfd4170f3ee4deee7adf66b13 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Oct 2022 14:34:43 +0000 Subject: [PATCH 1025/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2270217a90..ab8eee9331 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.4e82663ada91d4f2081a9ca2f01216394f960c236dcdf027fe17431b1dac0af4.wasm", + "tx_bond.wasm": "tx_bond.5e608312b724832512a8681a1eac493aceb0b01e7119af8fce8699c7463629d0.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.583dbdb4f2ea5183309a3da757a3670cd3fa5a3cc923471864ee290b6028497f.wasm", - "tx_ibc.wasm": "tx_ibc.64993c224847300a6801602e27a7c02ecb58cb5c7e7968dfe9092e6b4d6a1705.wasm", - "tx_init_account.wasm": "tx_init_account.5da56620f2d9c343ce3c5856575f2b7e8663a51887c61a220286ac1f65791a02.wasm", - "tx_init_nft.wasm": "tx_init_nft.795a4fb295a2daa00df7d23359a65a49db2fd3bd297a7a6fb070a0e02971a0f1.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4cd00565a47a6bfbe49a16f1279c61d2b393534f9eb7665d74106c817f54c689.wasm", - "tx_init_validator.wasm": "tx_init_validator.5267f2d0b18b71ccb8f0ed24227aa07152adc856f8c5ba1175dcbc1306efa8d2.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.c8f7a5d7ff825d97f9ff8dd5dac7b84a6d3c95374187d596a4eab95658cfde35.wasm", - "tx_transfer.wasm": "tx_transfer.d6f272506ea16053ed8509e8934b370d4078049ac0bd00e7e5fb72d2df589a04.wasm", - "tx_unbond.wasm": "tx_unbond.1a70fd9309dae9f107dfd4b8bf7323b38ce197278dd21667f97ff4fa6bc35a00.wasm", - "tx_update_vp.wasm": "tx_update_vp.7115829f28245f51642788da490a6d8e2983230a269409aac8352448426a0fb0.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.4e1bf81d2388ae72a48fbf4ba3424b3a86789f1c83b53d2da455436ce90a632a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8f303839ba10b08b1d76a5aa67b3b023705862f09e2ff336bb3f0f6d3935582f.wasm", - "vp_nft.wasm": "vp_nft.adbafd6b28c09c729f58af01c87e6404b3bbe41a74214b242fe27b38a346a293.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.5a360669dd9cfbc1da75eb7db825ff33e72c16c207fc2cf702d66fe51774fc46.wasm", - "vp_token.wasm": "vp_token.a603df86f683731fadaf8096fdd38987f8ce038fad7df0d31ee3f3d8642df5bf.wasm", - "vp_user.wasm": "vp_user.e09b796d7d41bbeb89b32eeb3524c7882a95924947a66f74135d01537ee9eb61.wasm" + "tx_from_intent.wasm": "tx_from_intent.c3b41d60a3444ed8c38d8728da09f4dcffa60130ddd8d9d64d6646cd2a7e0c73.wasm", + "tx_ibc.wasm": "tx_ibc.713602f66910f957fc6299f21013df8493481055313f6d58562a97e2a0b4baac.wasm", + "tx_init_account.wasm": "tx_init_account.00893b42e9978059c86d0023f696983691ec6d39b77c82139a783b83015b1e5e.wasm", + "tx_init_nft.wasm": "tx_init_nft.2dbb574a17fc1ecf9f427039db6698aa61b4d291e1564d4fc24a7caca232688a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3893d6b6ddac8753778a37bbb06442264f50102dc744196590e4ec8c4959c217.wasm", + "tx_init_validator.wasm": "tx_init_validator.37e6a67db9ae203599b97cd28c6a185851a87149d1415ad9856598f5fdf2a039.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.637f850863b89a452fd4b034287b39e70602d0313466daba6a8c3e66a9ec1d7d.wasm", + "tx_transfer.wasm": "tx_transfer.271146a326465e3cf2af71ff868a32c8e80def0009c13ec3e0864075085c0cfc.wasm", + "tx_unbond.wasm": "tx_unbond.2ff2438bbc49122d5c04a6305e2413c68e3c8f34b38f3d41b19797c587ae7d8c.wasm", + "tx_update_vp.wasm": "tx_update_vp.597dd03b92214a9859bb9a9a3de675f20a097c30a4eddc7721fa2131ba425d20.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d685034f8b9baa522f8efbe9202180dcd5799864c01c8353c701b5d357c0b34e.wasm", + "tx_withdraw.wasm": "tx_withdraw.54365901243fb078f91c098778e9fd48d58289df0f6823ae754a103fd13841b0.wasm", + "vp_nft.wasm": "vp_nft.1cf3f20f98bff1737ff2a126eebabf1948bfdc28c2ba537449e82f1d10408e75.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.ccebb06a5ea466e4e995bde85d029b6d1729afbfff4f96069d5876ce67fbc2b7.wasm", + "vp_token.wasm": "vp_token.9cdda44d45fddef739ccf41c9b88373b1504d83aa8eb9d8a5b521496254e13ea.wasm", + "vp_user.wasm": "vp_user.c75345de3a9c3de71f59d27d26f37eb8f805c99961a7f136536097afd0b4d960.wasm" } \ No newline at end of file From bbe70ecc569e5744d2a0fda3f3fe2001501a0c9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Oct 2022 14:34:45 +0000 Subject: [PATCH 1026/1995] [ci] wasm checksums update --- wasm/checksums.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index ab8eee9331..56914d94a2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.5e608312b724832512a8681a1eac493aceb0b01e7119af8fce8699c7463629d0.wasm", + "tx_bond.wasm": "tx_bond.7917f20ba026198a26288aa42e2e7a00683feecf7aa020c84900bb009e42286f.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.c3b41d60a3444ed8c38d8728da09f4dcffa60130ddd8d9d64d6646cd2a7e0c73.wasm", - "tx_ibc.wasm": "tx_ibc.713602f66910f957fc6299f21013df8493481055313f6d58562a97e2a0b4baac.wasm", - "tx_init_account.wasm": "tx_init_account.00893b42e9978059c86d0023f696983691ec6d39b77c82139a783b83015b1e5e.wasm", - "tx_init_nft.wasm": "tx_init_nft.2dbb574a17fc1ecf9f427039db6698aa61b4d291e1564d4fc24a7caca232688a.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3893d6b6ddac8753778a37bbb06442264f50102dc744196590e4ec8c4959c217.wasm", - "tx_init_validator.wasm": "tx_init_validator.37e6a67db9ae203599b97cd28c6a185851a87149d1415ad9856598f5fdf2a039.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.637f850863b89a452fd4b034287b39e70602d0313466daba6a8c3e66a9ec1d7d.wasm", - "tx_transfer.wasm": "tx_transfer.271146a326465e3cf2af71ff868a32c8e80def0009c13ec3e0864075085c0cfc.wasm", - "tx_unbond.wasm": "tx_unbond.2ff2438bbc49122d5c04a6305e2413c68e3c8f34b38f3d41b19797c587ae7d8c.wasm", + "tx_from_intent.wasm": "tx_from_intent.ea405bf027b05afa51765c73d324a410222f04ac03c11b23196537947523db0c.wasm", + "tx_ibc.wasm": "tx_ibc.efe82694cf1dbfac6ca295908d974411e63b4a166e616861335d8857ea923859.wasm", + "tx_init_account.wasm": "tx_init_account.fe9fd42d78aa9336d8a193d8c90342b131d684cabbc8801b290de5e3561c5a22.wasm", + "tx_init_nft.wasm": "tx_init_nft.a69e07fc4e662247491ca0829b0de4c747ebbea1b5ab392edb7495f5a8f99b46.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8cf10a8ac6e3645dd0430fab615a5251ba8c28a5fc7075d1b9da2e01f012ba32.wasm", + "tx_init_validator.wasm": "tx_init_validator.d4d6afd6d6d848f508922a10623f54dfb11e2be2c1666edbfd7321643fe9fd0f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.acd01ee6127f604ed20884bfce3186ff6d3e45947ede7c7327b5a632ab1b6a08.wasm", + "tx_transfer.wasm": "tx_transfer.52ddd66c08730453fcd4b40634cb6eaa9e0bf02e8f7b212bf4e4bea87e6c4935.wasm", + "tx_unbond.wasm": "tx_unbond.f4147b4bdc32fd01c78604223225295d09d823fff9ac96045d34d346dbf4e773.wasm", "tx_update_vp.wasm": "tx_update_vp.597dd03b92214a9859bb9a9a3de675f20a097c30a4eddc7721fa2131ba425d20.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d685034f8b9baa522f8efbe9202180dcd5799864c01c8353c701b5d357c0b34e.wasm", - "tx_withdraw.wasm": "tx_withdraw.54365901243fb078f91c098778e9fd48d58289df0f6823ae754a103fd13841b0.wasm", - "vp_nft.wasm": "vp_nft.1cf3f20f98bff1737ff2a126eebabf1948bfdc28c2ba537449e82f1d10408e75.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ccebb06a5ea466e4e995bde85d029b6d1729afbfff4f96069d5876ce67fbc2b7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.380ff2c91586e43864c573c6c5a3bb2ee1469a4f0cce06fddc279d750060c7c8.wasm", + "tx_withdraw.wasm": "tx_withdraw.e549788277620412e5969ffd566cff005a2fdb2a7972fbfa7396c28db76f7751.wasm", + "vp_nft.wasm": "vp_nft.95f500766279ea27b87a33b5819f01f46036bddb58eb71e71008cf389a7735dc.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.efc950e2ee04e8b86d68d44c43c684b0fced74d215812e5ac26e9a2b1b9f5dbe.wasm", "vp_token.wasm": "vp_token.9cdda44d45fddef739ccf41c9b88373b1504d83aa8eb9d8a5b521496254e13ea.wasm", - "vp_user.wasm": "vp_user.c75345de3a9c3de71f59d27d26f37eb8f805c99961a7f136536097afd0b4d960.wasm" + "vp_user.wasm": "vp_user.ec4b88f6d7986c3719a23b9536d5fcfc06f3014a4dae62c9f9e04063a096660e.wasm" } \ No newline at end of file From f181a5761f7040922e4475060a46534e90880779 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 20 Oct 2022 16:12:02 +0100 Subject: [PATCH 1027/1995] Check if a tx is applied on chain --- tests/src/e2e/ledger_tests.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 5e8db0c51a..fd42df7e72 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -119,6 +119,7 @@ fn test_node_connectivity() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -607,6 +608,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -629,6 +631,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -650,6 +653,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -672,6 +676,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -713,6 +718,7 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -733,6 +739,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -804,6 +811,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -829,6 +837,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); // Then self-bond the tokens: @@ -850,6 +859,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -874,6 +884,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -894,6 +905,7 @@ fn pos_init_validator() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1057,6 +1069,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1102,6 +1115,7 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1258,6 +1272,7 @@ fn proposal_submission() -> Result<()> { submit_proposal_vote, Some(15) )?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1275,6 +1290,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1294,6 +1310,7 @@ fn proposal_submission() -> Result<()> { // this is valid because the client filter ALBERT delegation and there are // none let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1406,6 +1423,7 @@ fn proposal_offline() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1871,6 +1889,7 @@ fn test_genesis_validators() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction applied with result:")?; client.exp_string("Transaction is valid.")?; client.assert_success(); From de981248318cc8be30103abf5ac6d467a447d386 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 17:21:10 +0100 Subject: [PATCH 1028/1995] Log the outcome of trying to send an abort signal --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index d26aaadea7..bd6b04aa19 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -47,7 +47,14 @@ impl Drop for Oracle { // send an abort signal to shut down the // rest of the ledger gracefully let abort = self.abort.take().unwrap(); - let _ = abort.send(()); + match abort.send(()) { + Ok(()) => tracing::debug!("Oracle sent abort signal"), + Err(()) => { + // this isn't necessarily an issue as the ledger may have shut + // down first + tracing::debug!("Oracle was unable to send an abort signal") + } + }; } } From 6ededf3801b9027ebf120de3ca9c25f6a5e2fd36 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 20:48:02 +0100 Subject: [PATCH 1029/1995] Check if oracle sender is closed at await points --- .../lib/node/ledger/ethereum_node/oracle.rs | 155 +++++++++--------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index bd6b04aa19..68ec9d050b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -90,12 +90,6 @@ impl Oracle { } true } - - /// Check if the receiver in the ledger has hung up. - /// Used to help determine when to stop the oracle - fn connected(&self) -> bool { - !self.sender.is_closed() - } } /// Set up an Oracle and run the process where the Oracle @@ -128,6 +122,8 @@ pub fn run_oracle( }) } +const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); + /// Given an oracle, watch for new Ethereum events, processing /// them into Anoma native types. /// @@ -138,96 +134,93 @@ async fn run_oracle_aux(oracle: Oracle) { // the latest block height seen and a queue of events // awaiting a certain number of confirmations let mut pending: Vec = Vec::new(); - const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); loop { - tokio::time::sleep(SLEEP_DUR).await; - // update the latest block height - let latest_block = loop { - match oracle.eth_block_number().await { - Ok(height) => break height, - Err(error) => { - tracing::warn!( - ?error, - "Couldn't get the latest Ethereum block height, will \ - keep trying" - ); - tokio::time::sleep(SLEEP_DUR).await; + tokio::select! { + should_continue = run_oracle_aux_inner(&oracle, &mut pending) => { + if !should_continue { + break; } - } - if !oracle.connected() { + }, + _ = oracle.sender.closed() => { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + receiver has hung up. Shutting down" ); - return; + break } }; - tracing::debug!(?latest_block, "Got latest Ethereum block height"); - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + tokio::time::sleep(SLEEP_DUR).await; + } +} + +// returns whether to continue or not +async fn run_oracle_aux_inner( + oracle: &Oracle, + pending: &mut Vec, +) -> bool { + // update the latest block height + let latest_block = loop { + match oracle.eth_block_number().await { + Ok(height) => break height, + Err(error) => { + tracing::warn!( + ?error, + "Couldn't get the latest Ethereum block height, will keep \ + trying" ); - return; + return true; } - continue; } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); - // check for events with at least `[MIN_CONFIRMATIONS]` - // confirmations. - for sig in signatures::SIGNATURES { - let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), - }; - // fetch the events for matching the given signature - let mut events = loop { - if let Ok(pending) = oracle - .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), - vec![addr], - vec![sig], - ) - .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) - { - break pending; - } - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" - ); - return; - } - }; - pending.append(&mut events); - if !oracle - .send(process_queue(&latest_block, &mut pending)) + }; + tracing::debug!(?latest_block, "Got latest Ethereum block height"); + // No blocks in existence yet with enough confirmations + if Uint256::from(MIN_CONFIRMATIONS) > latest_block { + return true; + } + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` + // confirmations. + for sig in signatures::SIGNATURES { + let addr: Address = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => MINT_CONTRACT.0.into(), + signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), + }; + // fetch the events for matching the given signature + let mut events = loop { + if let Ok(pending) = oracle + .check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) + .ok() + }) + .collect::>() + }) { - tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" - ); - return; + break pending; } + }; + pending.append(&mut events); + if !oracle.send(process_queue(&latest_block, pending)).await { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return false; } } + true } /// Check which events in the queue have reached their From 80b25663249abbf91c43566ed22c5b3ce7165a5b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 22:37:55 +0100 Subject: [PATCH 1030/1995] Check blocks starting from 0 --- .../lib/node/ledger/ethereum_node/oracle.rs | 162 +++++++++++++----- 1 file changed, 120 insertions(+), 42 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 68ec9d050b..449fc95098 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,6 +1,7 @@ use std::ops::Deref; use clarity::Address; +use eyre::{eyre, Result}; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; @@ -122,8 +123,6 @@ pub fn run_oracle( }) } -const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); - /// Given an oracle, watch for new Ethereum events, processing /// them into Anoma native types. /// @@ -134,16 +133,26 @@ async fn run_oracle_aux(oracle: Oracle) { // the latest block height seen and a queue of events // awaiting a certain number of confirmations let mut pending: Vec = Vec::new(); + + // TODO(namada#560): get the appropriate Ethereum block height to start + // checking from rather than starting from zero every time + let mut next_block_to_check: Uint256 = 0u8.into(); + loop { + tracing::info!( + ?next_block_to_check, + "Checking Ethereum block for bridge events" + ); tokio::select! { - should_continue = run_oracle_aux_inner(&oracle, &mut pending) => { - if !should_continue { - break; + result = process(&oracle, &mut pending, next_block_to_check.clone()) => { + match result { + Ok(()) => next_block_to_check += 1u8.into(), + Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), } }, _ = oracle.sender.closed() => { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ + "Ethereum oracle can not send events to the ledger; the \ receiver has hung up. Shutting down" ); break @@ -153,31 +162,46 @@ async fn run_oracle_aux(oracle: Oracle) { } } -// returns whether to continue or not -async fn run_oracle_aux_inner( +const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); + +/// Checks if the given block has any events relating to the bridge, and if so, +/// sends them to the oracle's `sender` +async fn process( oracle: &Oracle, pending: &mut Vec, -) -> bool { + block_to_check: Uint256, +) -> Result<()> { // update the latest block height let latest_block = loop { - match oracle.eth_block_number().await { - Ok(height) => break height, + let latest_block = match oracle.eth_block_number().await { + Ok(height) => height, Err(error) => { - tracing::warn!( - ?error, - "Couldn't get the latest Ethereum block height, will keep \ - trying" - ); - return true; + return Err(eyre!( + "Couldn't get the latest synced Ethereum block height \ + from the RPC endpoint: {:?}", + error + )); } + }; + let minimum_latest_block = + block_to_check.clone() + Uint256::from(MIN_CONFIRMATIONS); + if minimum_latest_block > latest_block { + tracing::debug!( + ?block_to_check, + ?latest_block, + ?minimum_latest_block, + "Waiting for enough Ethereum blocks to be synced" + ); + tokio::time::sleep(SLEEP_DUR).await; + continue; } + break latest_block; }; - tracing::debug!(?latest_block, "Got latest Ethereum block height"); - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - return true; - } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + tracing::debug!( + ?block_to_check, + ?latest_block, + "Got latest Ethereum block height" + ); // check for events with at least `[MIN_CONFIRMATIONS]` // confirmations. for sig in signatures::SIGNATURES { @@ -185,9 +209,15 @@ async fn run_oracle_aux_inner( signatures::SigType::Bridge => MINT_CONTRACT.0.into(), signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; + tracing::debug!( + ?block_to_check, + ?addr, + ?sig, + "Checking for bridge events" + ); // fetch the events for matching the given signature let mut events = loop { - if let Ok(pending) = oracle + let logs = match oracle .check_for_events( block_to_check.clone(), Some(block_to_check.clone()), @@ -195,32 +225,80 @@ async fn run_oracle_aux_inner( vec![sig], ) .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) { - break pending; + Ok(logs) => logs, + Err(error) => { + return Err(eyre!( + "Couldn't check for events ({sig} from {addr}) with \ + the RPC endpoint: {:?}", + error + )); + } + }; + if !logs.is_empty() { + tracing::info!( + ?block_to_check, + ?addr, + ?sig, + n_events = logs.len(), + "Found bridge events in Ethereum block" + ) } + break logs + .into_iter() + .filter_map(|log| { + match PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) { + Ok(event) => Some(event), + Err(error) => { + tracing::error!( + ?error, + ?block_to_check, + ?addr, + ?sig, + "Couldn't decode event: {:#?}", + log + ); + None + } + } + }) + .collect(); }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, pending)).await { + if !pending.is_empty() { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + ?block_to_check, + ?addr, + ?sig, + pending = pending.len(), + "There are Ethereum events pending" ); - return false; + } + let confirmed = process_queue(&latest_block, pending); + if !confirmed.is_empty() { + tracing::info!( + ?block_to_check, + ?addr, + ?sig, + pending = pending.len(), + confirmed = confirmed.len(), + ?MIN_CONFIRMATIONS, + "Some events that have reached the minimum number of \ + confirmations and will be sent onwards" + ); + } + if !oracle.send(confirmed).await { + return Err(eyre!( + "Could not send all bridge events ({sig} from {addr}) to the \ + shell" + )); } } - true + Ok(()) } /// Check which events in the queue have reached their From 03f72145d323a27edfa825e99904471262c507ae Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 11:25:44 +0100 Subject: [PATCH 1031/1995] Make oracle sleep time configurable --- .../lib/node/ledger/ethereum_node/oracle.rs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 449fc95098..686f443fa2 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,4 +1,5 @@ use std::ops::Deref; +use std::time::Duration; use clarity::Address; use eyre::{eyre, Result}; @@ -21,6 +22,9 @@ pub(crate) const MIN_CONFIRMATIONS: u64 = 100; const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); +/// The default amount of time the oracle will wait between checking blocks +const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); + /// A client that can talk to geth and parse /// and relay events relevant to Anoma to the /// ledger process @@ -33,6 +37,8 @@ pub struct Oracle { /// A channel to signal that the ledger should shut down /// because the Oracle has stopped abort: Option>, + /// How long the oracle should wait between checking blocks + backoff: Duration, } impl Deref for Oracle { @@ -65,11 +71,13 @@ impl Oracle { url: &str, sender: BoundedSender, abort: Sender<()>, + backoff: Duration, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), sender, abort: Some(abort), + backoff, } } @@ -91,6 +99,10 @@ impl Oracle { } true } + + async fn sleep(&self) { + tokio::time::sleep(self.backoff).await; + } } /// Set up an Oracle and run the process where the Oracle @@ -110,7 +122,12 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = Oracle::new(&url, sender, abort_sender); + let oracle = Oracle::new( + &url, + sender, + abort_sender, + DEFAULT_BACKOFF, + ); run_oracle_aux(oracle).await; tracing::info!( @@ -158,12 +175,10 @@ async fn run_oracle_aux(oracle: Oracle) { break } }; - tokio::time::sleep(SLEEP_DUR).await; + oracle.sleep().await; } } -const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); - /// Checks if the given block has any events relating to the bridge, and if so, /// sends them to the oracle's `sender` async fn process( @@ -192,7 +207,9 @@ async fn process( ?minimum_latest_block, "Waiting for enough Ethereum blocks to be synced" ); - tokio::time::sleep(SLEEP_DUR).await; + // this isn't an error condition, so we continue in the loop here + // with a back off + oracle.sleep().await; continue; } break latest_block; @@ -352,6 +369,8 @@ mod test_oracle { client, sender: eth_sender, abort: Some(abort), + // backoff should be short for tests so that they run faster + backoff: Duration::from_millis(5), }, admin_channel, eth_recv: eth_receiver, From e8b9650f61606dbf111accad1b1bba7136a3c5f3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 12:28:34 +0100 Subject: [PATCH 1032/1995] Add blocks_checked channel and test --- .../lib/node/ledger/ethereum_node/oracle.rs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 686f443fa2..fc4ce23ec4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -39,6 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, + /// If provided, the oracle will attempt to send blocks that it has checked + /// to this channel, but won't pause if this channel is full or + /// disconnected. + blocks_checked: Option>, } impl Deref for Oracle { @@ -78,6 +82,7 @@ impl Oracle { sender, abort: Some(abort), backoff, + blocks_checked: None, } } @@ -163,7 +168,14 @@ async fn run_oracle_aux(oracle: Oracle) { tokio::select! { result = process(&oracle, &mut pending, next_block_to_check.clone()) => { match result { - Ok(()) => next_block_to_check += 1u8.into(), + Ok(()) => { + if let Some(blocks_checked) = &oracle.blocks_checked { + if let Err(error) = blocks_checked.try_send(next_block_to_check.clone()) { + tracing::warn!(?error, block = ?next_block_to_check, "Failed to send block checked to channel"); + }; + } + next_block_to_check += 1u8.into() + }, Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), } }, @@ -342,6 +354,7 @@ fn process_queue( mod test_oracle { use namada::types::ethereum_events::TransferToEthereum; use tokio::sync::oneshot::{channel, Receiver}; + use tokio::time::timeout; use super::*; use crate::node::ledger::ethereum_node::events::{ @@ -356,6 +369,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, + blocks_checked_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -363,6 +377,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); + let (blocks_checked_sender, blocks_checked_receiver) = + tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -371,9 +387,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), + blocks_checked: Some(blocks_checked_sender), }, admin_channel, eth_recv: eth_receiver, + blocks_checked_recv: blocks_checked_receiver, abort_recv, } } @@ -645,4 +663,61 @@ mod test_oracle { drop(eth_recv); oracle.join().expect("Test failed"); } + + /// Test that Ethereum blocks are checked in sequence up to the latest block + /// that has reached the minimum number of confirmations + #[tokio::test] + async fn test_blocks_checked_sequence() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + mut blocks_checked_recv, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + + // set the height of the chain such that the first `n` blocks are deep + // enough to be considered confirmed by the oracle + let n = 10; + for height in 0..MIN_CONFIRMATIONS + n { + admin_channel + .send(TestCmd::NewHeight(Uint256::from(height))) + .expect("Test failed"); + } + // check that the oracle has indeed processed the first `n` blocks + for height in 0u64..n { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + // check that the oracle hasn't yet checked any further blocks + assert!( + timeout(Duration::from_secs(1), blocks_checked_recv.recv()) + .await + .is_err() + ); + + // increase the height of the chain by one, and check that the oracle + // has now processed the `n+1`th block + admin_channel + .send(TestCmd::NewHeight(Uint256::from(MIN_CONFIRMATIONS + n))) + .expect("Test failed"); + + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(n)); + + drop(eth_recv); + oracle.join().expect("Test failed"); + } } From ef8e1ce4445c66bfd6465edf3305c83da679f671 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 14:43:07 +0100 Subject: [PATCH 1033/1995] Add test_all_blocks_checked --- .../lib/node/ledger/ethereum_node/oracle.rs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index fc4ce23ec4..9a0d012d9a 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -720,4 +720,61 @@ mod test_oracle { drop(eth_recv); oracle.join().expect("Test failed"); } + + /// Test that if the Ethereum RPC endpoint returns a latest block that is + /// more than one block later than the previous latest block we received, we + /// still check all the blocks inbetween + #[tokio::test] + async fn test_all_blocks_checked() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + mut blocks_checked_recv, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + + let initially_confirmed_blocks = 10; + admin_channel + .send(TestCmd::NewHeight(Uint256::from( + MIN_CONFIRMATIONS + initially_confirmed_blocks, + ))) + .expect("Test failed"); + + // check that the oracle has indeed processed the first `n` blocks, even + // though the first latest block that the oracle received was not 0 + for height in 0u64..initially_confirmed_blocks { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + // the next time the oracle checks, the latest block will have increased + // by more than one + let latest_block = initially_confirmed_blocks + 10; + admin_channel + .send(TestCmd::NewHeight(Uint256::from( + MIN_CONFIRMATIONS + latest_block, + ))) + .expect("Test failed"); + + // check that the oracle still checks the blocks inbetween + for height in initially_confirmed_blocks..latest_block { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + drop(eth_recv); + oracle.join().expect("Test failed"); + } } From b32f9e58a29eb3fae81f2f23073e0bf569002e46 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 14:49:07 +0100 Subject: [PATCH 1034/1995] Remove unnecessary `loop` --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 9a0d012d9a..e236690943 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -245,7 +245,7 @@ async fn process( "Checking for bridge events" ); // fetch the events for matching the given signature - let mut events = loop { + let mut events = { let logs = match oracle .check_for_events( block_to_check.clone(), @@ -273,8 +273,7 @@ async fn process( "Found bridge events in Ethereum block" ) } - break logs - .into_iter() + logs.into_iter() .filter_map(|log| { match PendingEvent::decode( sig, From 16975ec8ce71478b02af8193d70002b6d5c89797 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 15:28:16 +0100 Subject: [PATCH 1035/1995] Clear up tests and terminology --- .../lib/node/ledger/ethereum_node/oracle.rs | 151 +++++++++--------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index e236690943..94fc91a201 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -22,7 +22,7 @@ pub(crate) const MIN_CONFIRMATIONS: u64 = 100; const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); -/// The default amount of time the oracle will wait between checking blocks +/// The default amount of time the oracle will wait between processing blocks const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); /// A client that can talk to geth and parse @@ -39,10 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will attempt to send blocks that it has checked - /// to this channel, but won't pause if this channel is full or - /// disconnected. - blocks_checked: Option>, + /// If provided, the oracle will attempt to send block heights that it has + /// processed to this channel, but won't pause if this channel is full + /// or disconnected. + blocks_processed: Option>, } impl Deref for Oracle { @@ -57,13 +57,15 @@ impl Drop for Oracle { fn drop(&mut self) { // send an abort signal to shut down the // rest of the ledger gracefully - let abort = self.abort.take().unwrap(); - match abort.send(()) { - Ok(()) => tracing::debug!("Oracle sent abort signal"), + match self.abort.take().unwrap().send(()) { + Ok(()) => tracing::info!("Oracle sent abort signal"), Err(()) => { // this isn't necessarily an issue as the ledger may have shut // down first - tracing::debug!("Oracle was unable to send an abort signal") + tracing::debug!( + "Oracle was unable to send an abort signal as the abort \ + channel was already closed" + ) } }; } @@ -82,7 +84,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - blocks_checked: None, + blocks_processed: None, } } @@ -151,32 +153,31 @@ pub fn run_oracle( /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process async fn run_oracle_aux(oracle: Oracle) { - // Initialize our local state. This includes - // the latest block height seen and a queue of events - // awaiting a certain number of confirmations + // Initialize a queue to keep events which are awaiting a certain number of + // confirmations let mut pending: Vec = Vec::new(); // TODO(namada#560): get the appropriate Ethereum block height to start // checking from rather than starting from zero every time - let mut next_block_to_check: Uint256 = 0u8.into(); + let mut next_block_to_process: Uint256 = 0u8.into(); loop { tracing::info!( - ?next_block_to_check, + ?next_block_to_process, "Checking Ethereum block for bridge events" ); tokio::select! { - result = process(&oracle, &mut pending, next_block_to_check.clone()) => { + result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_checked) = &oracle.blocks_checked { - if let Err(error) = blocks_checked.try_send(next_block_to_check.clone()) { - tracing::warn!(?error, block = ?next_block_to_check, "Failed to send block checked to channel"); + if let Some(blocks_processed) = &oracle.blocks_processed { + if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { + tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); }; } - next_block_to_check += 1u8.into() + next_block_to_process += 1u8.into() }, - Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), + Err(error) => tracing::warn!(?error, block = ?next_block_to_process, "Error while trying to process Ethereum block"), } }, _ = oracle.sender.closed() => { @@ -192,11 +193,11 @@ async fn run_oracle_aux(oracle: Oracle) { } /// Checks if the given block has any events relating to the bridge, and if so, -/// sends them to the oracle's `sender` +/// sends them to the oracle's `sender` channel async fn process( oracle: &Oracle, pending: &mut Vec, - block_to_check: Uint256, + block_to_process: Uint256, ) -> Result<()> { // update the latest block height let latest_block = loop { @@ -211,10 +212,10 @@ async fn process( } }; let minimum_latest_block = - block_to_check.clone() + Uint256::from(MIN_CONFIRMATIONS); + block_to_process.clone() + Uint256::from(MIN_CONFIRMATIONS); if minimum_latest_block > latest_block { tracing::debug!( - ?block_to_check, + ?block_to_process, ?latest_block, ?minimum_latest_block, "Waiting for enough Ethereum blocks to be synced" @@ -227,7 +228,7 @@ async fn process( break latest_block; }; tracing::debug!( - ?block_to_check, + ?block_to_process, ?latest_block, "Got latest Ethereum block height" ); @@ -239,7 +240,7 @@ async fn process( signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; tracing::debug!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, "Checking for bridge events" @@ -248,8 +249,8 @@ async fn process( let mut events = { let logs = match oracle .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), + block_to_process.clone(), + Some(block_to_process.clone()), vec![addr], vec![sig], ) @@ -266,7 +267,7 @@ async fn process( }; if !logs.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, n_events = logs.len(), @@ -277,14 +278,14 @@ async fn process( .filter_map(|log| { match PendingEvent::decode( sig, - block_to_check.clone(), + block_to_process.clone(), log.data.0.as_slice(), ) { Ok(event) => Some(event), Err(error) => { tracing::error!( ?error, - ?block_to_check, + ?block_to_process, ?addr, ?sig, "Couldn't decode event: {:#?}", @@ -294,12 +295,12 @@ async fn process( } } }) - .collect(); + .collect() }; pending.append(&mut events); if !pending.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, pending = pending.len(), @@ -309,7 +310,7 @@ async fn process( let confirmed = process_queue(&latest_block, pending); if !confirmed.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, pending = pending.len(), @@ -368,7 +369,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - blocks_checked_recv: tokio::sync::mpsc::Receiver, + blocks_processed_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -376,7 +377,7 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (blocks_checked_sender, blocks_checked_receiver) = + let (blocks_processed, blocks_processed_recv) = tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { @@ -386,11 +387,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - blocks_checked: Some(blocks_checked_sender), + blocks_processed: Some(blocks_processed), }, admin_channel, eth_recv: eth_receiver, - blocks_checked_recv: blocks_checked_receiver, + blocks_processed_recv, abort_recv, } } @@ -663,58 +664,62 @@ mod test_oracle { oracle.join().expect("Test failed"); } - /// Test that Ethereum blocks are checked in sequence up to the latest block - /// that has reached the minimum number of confirmations + /// Test that Ethereum blocks are processed in sequence up to the latest + /// block that has reached the minimum number of confirmations #[tokio::test] async fn test_blocks_checked_sequence() { let TestPackage { oracle, eth_recv, admin_channel, - mut blocks_checked_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); - // set the height of the chain such that the first `n` blocks are deep + // set the height of the chain such that there are some blocks deep // enough to be considered confirmed by the oracle - let n = 10; - for height in 0..MIN_CONFIRMATIONS + n { + let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations + let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; + for height in 0..synced_block_height + 1 { admin_channel .send(TestCmd::NewHeight(Uint256::from(height))) .expect("Test failed"); } - // check that the oracle has indeed processed the first `n` blocks - for height in 0u64..n { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + // check that the oracle indeed processes the confirmed blocks + for height in 0u64..confirmed_block_height + 1 { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks + // TODO: check this in a deterministic way rather than just waiting a + // bit assert!( - timeout(Duration::from_secs(1), blocks_checked_recv.recv()) + timeout(Duration::from_secs(1), blocks_processed_recv.recv()) .await .is_err() ); // increase the height of the chain by one, and check that the oracle - // has now processed the `n+1`th block + // processed the next confirmed block + let synced_block_height = synced_block_height + 1; admin_channel - .send(TestCmd::NewHeight(Uint256::from(MIN_CONFIRMATIONS + n))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(n)); + assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); oracle.join().expect("Test failed"); @@ -729,48 +734,48 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_checked_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); - let initially_confirmed_blocks = 10; + let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations + let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; admin_channel - .send(TestCmd::NewHeight(Uint256::from( - MIN_CONFIRMATIONS + initially_confirmed_blocks, - ))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 - for height in 0u64..initially_confirmed_blocks { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + for height in 0u64..confirmed_block_height + 1 { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } // the next time the oracle checks, the latest block will have increased // by more than one - let latest_block = initially_confirmed_blocks + 10; + let difference = 10; + let synced_block_height = synced_block_height + difference; admin_channel - .send(TestCmd::NewHeight(Uint256::from( - MIN_CONFIRMATIONS + latest_block, - ))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); // check that the oracle still checks the blocks inbetween - for height in initially_confirmed_blocks..latest_block { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + for height in (confirmed_block_height + 1) + ..(confirmed_block_height + difference + 1) + { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } drop(eth_recv); From 9f06535c36c339aa1856109d76f45ef3c4e566bc Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:20:38 +0100 Subject: [PATCH 1036/1995] Reformat tracing call --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 94fc91a201..df13093be5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -177,7 +177,11 @@ async fn run_oracle_aux(oracle: Oracle) { } next_block_to_process += 1u8.into() }, - Err(error) => tracing::warn!(?error, block = ?next_block_to_process, "Error while trying to process Ethereum block"), + Err(error) => tracing::warn!( + ?error, + block = ?next_block_to_process, + "Error while trying to process Ethereum block" + ), } }, _ = oracle.sender.closed() => { From 8aa0830d02a5094aad270a8e20cb08778ad48d72 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:23:34 +0100 Subject: [PATCH 1037/1995] Inline error variable into format string --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index df13093be5..b15643082f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -210,8 +210,7 @@ async fn process( Err(error) => { return Err(eyre!( "Couldn't get the latest synced Ethereum block height \ - from the RPC endpoint: {:?}", - error + from the RPC endpoint: {error:?}", )); } }; @@ -264,8 +263,7 @@ async fn process( Err(error) => { return Err(eyre!( "Couldn't check for events ({sig} from {addr}) with \ - the RPC endpoint: {:?}", - error + the RPC endpoint: {error:?}", )); } }; From f99a093067e75f5dd7937288cf4cd2158b4fc664 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:24:30 +0100 Subject: [PATCH 1038/1995] Fix misspelling --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index b15643082f..565a12f37b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -729,7 +729,7 @@ mod test_oracle { /// Test that if the Ethereum RPC endpoint returns a latest block that is /// more than one block later than the previous latest block we received, we - /// still check all the blocks inbetween + /// still check all the blocks in between #[tokio::test] async fn test_all_blocks_checked() { let TestPackage { From 8178fe51cdb52507117451710c031175ad1c0bc9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:03:48 +0100 Subject: [PATCH 1039/1995] Use a watch channel for making the latest block processed visible --- .../lib/node/ledger/ethereum_node/oracle.rs | 110 +++++++++++------- 1 file changed, 67 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 565a12f37b..7795e297b1 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,6 +7,7 @@ use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; +use tokio::sync::watch; use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -39,10 +40,8 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will attempt to send block heights that it has - /// processed to this channel, but won't pause if this channel is full - /// or disconnected. - blocks_processed: Option>, + /// If provided, the oracle will put here the latest block it has processed + latest_block_processed: Option>>, } impl Deref for Oracle { @@ -84,7 +83,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - blocks_processed: None, + latest_block_processed: None, } } @@ -170,10 +169,16 @@ async fn run_oracle_aux(oracle: Oracle) { result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_processed) = &oracle.blocks_processed { - if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { - tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); - }; + if let Some(blocks_processed) = &oracle.latest_block_processed { + _ = blocks_processed.send_if_modified(|block: &mut Option| { + if let Some(current) = block { + if *current == next_block_to_process { + return false; + } + } + block.replace(next_block_to_process.clone()); + true + }); } next_block_to_process += 1u8.into() }, @@ -371,7 +376,8 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - blocks_processed_recv: tokio::sync::mpsc::Receiver, + latest_block_processed_recv: + tokio::sync::watch::Receiver>, abort_recv: Receiver<()>, } @@ -379,8 +385,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (blocks_processed, blocks_processed_recv) = - tokio::sync::mpsc::channel(1000); + let (latest_block_processed_send, latest_block_processed_recv) = + tokio::sync::watch::channel(None); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -389,11 +395,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - blocks_processed: Some(blocks_processed), + latest_block_processed: Some(latest_block_processed_send), }, admin_channel, eth_recv: eth_receiver, - blocks_processed_recv, + latest_block_processed_recv, abort_recv, } } @@ -674,7 +680,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_processed_recv, + mut latest_block_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -692,22 +698,22 @@ mod test_oracle { } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks - // TODO: check this in a deterministic way rather than just waiting a - // bit - assert!( - timeout(Duration::from_secs(1), blocks_processed_recv.recv()) - .await - .is_err() - ); + assert!(!latest_block_processed_recv.has_changed().unwrap()); // increase the height of the chain by one, and check that the oracle // processed the next confirmed block @@ -716,11 +722,17 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .unwrap(); assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); @@ -736,7 +748,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_processed_recv, + mut latest_block_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -752,11 +764,17 @@ mod test_oracle { // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } @@ -772,11 +790,17 @@ mod test_oracle { for height in (confirmed_block_height + 1) ..(confirmed_block_height + difference + 1) { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } From 954cd3b14f59e0670731b66587bc916ae19c39f9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 18:06:55 +0100 Subject: [PATCH 1040/1995] Revert "Use a watch channel for making the latest block processed visible" This reverts commit 8178fe51cdb52507117451710c031175ad1c0bc9. --- .../lib/node/ledger/ethereum_node/oracle.rs | 110 +++++++----------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 7795e297b1..565a12f37b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,7 +7,6 @@ use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; -use tokio::sync::watch; use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -40,8 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will put here the latest block it has processed - latest_block_processed: Option>>, + /// If provided, the oracle will attempt to send block heights that it has + /// processed to this channel, but won't pause if this channel is full + /// or disconnected. + blocks_processed: Option>, } impl Deref for Oracle { @@ -83,7 +84,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - latest_block_processed: None, + blocks_processed: None, } } @@ -169,16 +170,10 @@ async fn run_oracle_aux(oracle: Oracle) { result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_processed) = &oracle.latest_block_processed { - _ = blocks_processed.send_if_modified(|block: &mut Option| { - if let Some(current) = block { - if *current == next_block_to_process { - return false; - } - } - block.replace(next_block_to_process.clone()); - true - }); + if let Some(blocks_processed) = &oracle.blocks_processed { + if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { + tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); + }; } next_block_to_process += 1u8.into() }, @@ -376,8 +371,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - latest_block_processed_recv: - tokio::sync::watch::Receiver>, + blocks_processed_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -385,8 +379,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (latest_block_processed_send, latest_block_processed_recv) = - tokio::sync::watch::channel(None); + let (blocks_processed, blocks_processed_recv) = + tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -395,11 +389,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - latest_block_processed: Some(latest_block_processed_send), + blocks_processed: Some(blocks_processed), }, admin_channel, eth_recv: eth_receiver, - latest_block_processed_recv, + blocks_processed_recv, abort_recv, } } @@ -680,7 +674,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut latest_block_processed_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -698,22 +692,22 @@ mod test_oracle { } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks - assert!(!latest_block_processed_recv.has_changed().unwrap()); + // TODO: check this in a deterministic way rather than just waiting a + // bit + assert!( + timeout(Duration::from_secs(1), blocks_processed_recv.recv()) + .await + .is_err() + ); // increase the height of the chain by one, and check that the oracle // processed the next confirmed block @@ -722,17 +716,11 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .unwrap(); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); @@ -748,7 +736,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut latest_block_processed_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -764,17 +752,11 @@ mod test_oracle { // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 for height in 0u64..confirmed_block_height + 1 { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } @@ -790,17 +772,11 @@ mod test_oracle { for height in (confirmed_block_height + 1) ..(confirmed_block_height + difference + 1) { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } From 3a51f8077e84f7163176bbbd6687a5f8e4064a32 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1041/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- shared/src/types/eth_bridge_pool.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4cfc557987..33bf927823 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -59,8 +59,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode<7> for PendingTransfer { - fn tokenize(&self) -> [Token; 7] { +impl Encode<8> for PendingTransfer { + fn tokenize(&self) -> [Token; 8] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); From 70126cd881b00b88d72f01ab2add9365f669bb5c Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1042/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 50df74286c..d077c3cab2 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -278,6 +278,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From db7f0ddbbd877ba7758bff20e35ddbaa6e1df7fa Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1043/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 9cbd5351c47ced16c0f76ae6d7a12c6722076e90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 08:39:54 +0000 Subject: [PATCH 1044/1995] [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 48dd45169b..5f5b21643b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { + "tx_bond.wasm": "tx_bond.730e12ff5cebf2e8bdb23882a0e3ab9ed20df6bfe20e2d6b3578bb474c97a04d.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7e9893f8e2affcfbb4e04fe936bb18571645353da08d8e08d880971f2cfca053.wasm", - "tx_ibc.wasm": "tx_ibc.29d0881d94f45a2fd67bb0f8cbc432a4c2c18ce96c1e17712b721a3ae04788d2.wasm", - "tx_init_account.wasm": "tx_init_account.fff73dad57c9f1f670593939b4a0a895ed538324a9a39958e3724a51b1fc1524.wasm", - "tx_init_nft.wasm": "tx_init_nft.378007b60d8324df95f0db09b568b8924b411d6402a76e0280ce5bb95835dff2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.59c9755b31a9727c7b5908c2c404f193b9de7ffac919833c342e06e4bca31953.wasm", - "tx_init_validator.wasm": "tx_init_validator.34b7afb1af8f7b23c00812e969f89058754c8447704a9cd1acd595281c58d16a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.7962f00c4735a88cae4363ca172e6bbe1cd0b75c7271b5203b141e29a2d039ff.wasm", - "tx_transfer.wasm": "tx_transfer.e818885f13cbe1349c8702fa848991263b4032cae6c2f68df75cf9e4a7d4b4db.wasm", - "tx_unbond.wasm": "tx_unbond.1e3a5e7e5e85cf6222af2255e8bea9658693758473aba1022304c4b5af4d7f78.wasm", + "tx_from_intent.wasm": "tx_from_intent.f3add97540ff97ee0e7af21c8b628f8cd6221d2c7150354f9a33fbda4f7da85a.wasm", + "tx_ibc.wasm": "tx_ibc.49f1e97533207af795362dadfc5be7ce6c119a51830707ee12ab14e3eb5e60ba.wasm", + "tx_init_account.wasm": "tx_init_account.5eedb88e7fa75066fbaeaf79a7b63f78346ce5c55ad76325ca7b2ee786cb124d.wasm", + "tx_init_nft.wasm": "tx_init_nft.f4b14f460478f6ab5268a431948da24add566188129b0297a39230e86a945b38.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ed94170605b1b1b6e0440d2235d25598f1c6976a4a0dbf3d90a1e0154f815740.wasm", + "tx_init_validator.wasm": "tx_init_validator.8db2f2f7b180310591b8163b5db9494e80b59b66bcea72f71d8af09ec8c6da0d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.deca7087433d476e273094c5017e3b83534d7c8f576c35561f05ab48aeffdea8.wasm", + "tx_transfer.wasm": "tx_transfer.a7f182f0b1398a1da79e8a74d41b70d2b0e39329b1598f9774fb99e46791c895.wasm", + "tx_unbond.wasm": "tx_unbond.7fedc39de1b6f197de757563b142ad43dfe280234df7f0f0434f083419fd7476.wasm", "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d61eb971b9e8eab8d8d2ddf5ff238f2de769bc227cbcb14beb6e36462102ff36.wasm", - "tx_withdraw.wasm": "tx_withdraw.76c8c25e235a9e351796caef61a783e15e289f0a8b8ebdfba6ffefb027ec4f68.wasm", - "vp_nft.wasm": "vp_nft.acd5da5193e1d02caf3a278ea88eaa22943cd280e9c84a8d381217399c8bdabf.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.cc2633219e7fe4a807fb6e25ae8c7248b35dcea239dd6b108293bfee26d3a512.wasm", - "vp_token.wasm": "vp_token.cee6c283ec93ecf78d473cf382b2ec78a891d705fa48ca5c7393c61b74bb2aa7.wasm", - "vp_user.wasm": "vp_user.62b8491c45cb9fb1b2b4f4d4590a119ba34fcca8d63539ed2717c3bef365aff1.wasm" - + "tx_vote_proposal.wasm": "tx_vote_proposal.af655290e235244a73d7fe16211d790bb45c08c903be40726bb806f84ae2bd2f.wasm", + "tx_withdraw.wasm": "tx_withdraw.9ecd4a4b8b037b79900605f946791677236b7544ea0fa41890d0a3f9bc264a68.wasm", + "vp_nft.wasm": "vp_nft.a7a93adb21605e532bef10274c580d5b8d11c8f49298f6c08ad2e1cd22db5b89.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8c7c23b7dc72c9b4ecaf1602989431d6338111f730ad85f93b80dfd6a7806417.wasm", + "vp_token.wasm": "vp_token.e2d4cebc4c592ec68a452e1ef55a6bb6b16bd0cf7d77ec3527b4cde82d243251.wasm", + "vp_user.wasm": "vp_user.0e094c576229c2f72d39cdf1ec4a65d35c76dc5823ff4ea2537d22f8c7856977.wasm" } \ No newline at end of file From c2cea8c349c2029895b317eae5107421c000cd71 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:44:52 +0200 Subject: [PATCH 1045/1995] [chore]: rebasing on changes from previous feature prs --- shared/src/types/storage.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index d077c3cab2..6e7eb4e284 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -273,17 +273,9 @@ impl MerkleValue { pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + Self::BridgePoolTransfer(transfer) => { + transfer.try_to_vec().unwrap() + } } } } From 0aa62f97356a659a68c533c414431668a2a293f4 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:50:33 +0200 Subject: [PATCH 1046/1995] [fix]: Added missing from a browser merge --- shared/src/types/hash.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 7764bc6fc5..ee06451635 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -147,4 +147,4 @@ mod tests { let _: Hash = hex_hash.try_into().unwrap(); } } - +} From cefcf623bc30c3cc87d8cdad90b8ba782c6faf70 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:54:52 +0200 Subject: [PATCH 1047/1995] [fix]: Removed duplicated code block --- shared/src/types/storage.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index ffe2f905e8..5e91c068eb 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -269,7 +269,7 @@ impl From for MerkleValue { } impl MerkleValue { - /// Get the natural byte repesentation of the value + /// Get the natural byte representation of the value pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, @@ -280,16 +280,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 472d167afe515b881e69e6b417c01e5dc821d05b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 11:05:46 +0200 Subject: [PATCH 1048/1995] [fix]: Broken doc link --- shared/src/types/key/secp256k1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 67d3352ab3..d83f2ea953 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -21,7 +21,7 @@ use super::{ }; use crate::types::ethereum_events::EthAddress; -/// [`libsecp265k1::util::SIGNATURE_SIZE`] is for a traditional +/// The provided constant is for a traditional /// signature on this curve. For Ethereum, an extra byte is included /// that prevents malleability attacks. pub const SIGNATURE_LENGTH: usize = libsecp256k1::util::SIGNATURE_SIZE + 1; From dc283a667884952b538a5ce4ef6e1c868670710d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 09:07:03 +0000 Subject: [PATCH 1049/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f555b3064e..7a6840ef5f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.d0ce8b21186695f0b14c6f8f1649b73e61e91dd3c357207088f23992127ce031.wasm", + "tx_bond.wasm": "tx_bond.870a6cd86c7725df65306a4ef49ae05f39ac5d2f7c39bf12c901def38587c016.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.202f60934158067bec29454b4dd899ea902158e8321e51b6a479d94d8d1677c1.wasm", - "tx_ibc.wasm": "tx_ibc.a5a14d5c6061cbcfad16242ef71d9a949527c3f47453491cbe216d4da3c3bf52.wasm", - "tx_init_account.wasm": "tx_init_account.b2a97bd164b7c4804c2ab08a478bdb8235386be0d07d05f66ff3663868dc1dfc.wasm", - "tx_init_nft.wasm": "tx_init_nft.ba4c2519c1de43c1c638cfbcdbb734e7f1b6fbc3e960fd21f305dd8a1e6ac1f5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a2a3a6bf3972001aae12fb423fdc97eda6d4b017d8409d5f4b721b4eb03e0d37.wasm", - "tx_init_validator.wasm": "tx_init_validator.012e64f8736402e9e9fefb667ea684bb9db15f1bb25d9d175b4ba193d724e583.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.b0715685db7b04cf7d9b8ec5ec57c63927e9133edfb9e68055c618c5d879ace7.wasm", - "tx_transfer.wasm": "tx_transfer.5692122746b321e787e1965d1f67f9f450ff8b33f1f1d24f2a3260034072c16b.wasm", - "tx_unbond.wasm": "tx_unbond.b167452a7e135d0c942d7c6fc9a03f37ef4255844c02e00158b91d59fed86939.wasm", - "tx_update_vp.wasm": "tx_update_vp.b0f682a2d5e621a3c5f76e4c2bf272d54e612fe63847e2d746a6d452cefc7823.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9444e00877c7c5b9d0da568dbfb7e9082ad4508e331e63f398c7fe75c3041e29.wasm", - "tx_withdraw.wasm": "tx_withdraw.4d49f78c85574c176c65d0aa3ab1002540ca5c660768f2833047e5bc380c52f7.wasm", - "vp_nft.wasm": "vp_nft.916a59e2abdac12f165ec07c783edb11642e03b911c41bc5a39d3c622b05ccdd.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.72bea74cb811103c258477bc155dc67c06ee6c14c105b8a5598b79519949b0b6.wasm", - "vp_token.wasm": "vp_token.886885caefe8e264644126bdb329d3e564e7ea9015df3093001c1a297dc35709.wasm", - "vp_user.wasm": "vp_user.bc69d9c0eb26a25f55ace17319f3e77ea8bda00c53c3222949f2b22d8e62cae6.wasm" + "tx_from_intent.wasm": "tx_from_intent.f14bd1cf1bc9adce29ff5161d36cade38f75db58daaab0ac4446aeb3752ca491.wasm", + "tx_ibc.wasm": "tx_ibc.d88b6ea37611c9e82d012b468475a2dfe125d5f0ecf8162ee361a2e07ac48a7c.wasm", + "tx_init_account.wasm": "tx_init_account.1424aad69e094e6ddbbcc41c28484675d2ce181699d5820b365cade406a33e3a.wasm", + "tx_init_nft.wasm": "tx_init_nft.b6d193bf41c5b105de075ed832ed9ac5bb0ee03943c0f70ace1888474f50ece7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.951e3667382e0d81de17ebda9248046a8f1cc2d7e714b0cfa7ae58920c0d02d6.wasm", + "tx_init_validator.wasm": "tx_init_validator.d9ec814be773d6bfd6c36ddb37ace2463effef02d7e089495c3f74de43de556f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.90b8cf9d1f52b758ae419cefaaa51b270d7492877348ea02357d589f83695099.wasm", + "tx_transfer.wasm": "tx_transfer.ad9707d3e02d01b261675fdb09eab6aedeebea0b8035726df76f68c7279b864b.wasm", + "tx_unbond.wasm": "tx_unbond.f73b997890f6ad61289fdfa01770940bc26e28bf06a8a2e856d2615acdcbe6ae.wasm", + "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.25bb52d647106263537467e229ddbc1ef57970c222d49830d1bc182727afe4f4.wasm", + "tx_withdraw.wasm": "tx_withdraw.ebe91941b7f8559e651ceeba786d5cf08fba7ee17d2991ef460cbf65e6501664.wasm", + "vp_nft.wasm": "vp_nft.e8bcdf806148418e765aad6fa509993c490a7c2fb7cbc9abe8e469babd371e05.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.d210846a57a8290b726689c50d8002ce374e54c5af08ca7b4edc852832cd42d8.wasm", + "vp_token.wasm": "vp_token.e2d4cebc4c592ec68a452e1ef55a6bb6b16bd0cf7d77ec3527b4cde82d243251.wasm", + "vp_user.wasm": "vp_user.856857cea31ce03e076dcb1a9e5d2a9cf30d3c883365e7230f0c8fa1d4c1dc8a.wasm" } \ No newline at end of file From 203521a0c9e7013db0c55ca3fb4767111268fb04 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 09:31:25 +0000 Subject: [PATCH 1050/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index d61712a5fd..370f2e5aa2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.bb67d406e6c31218d32667ef8debed9be77dbadb33ee52a30d9fc377e3f6876c.wasm", + "tx_bond.wasm": "tx_bond.bf0cd5aedaaed86b3bd07f27e7df9eae9375e305cc7b1aa6640c87c321355aee.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9915822bea588b0a18b4ec51bc249e88fdc3041bc3eddaaa2cc880dcd23c5eb0.wasm", - "tx_ibc.wasm": "tx_ibc.913192b268db668ebba6b415eea106c19e742b9b6be6ebb795a661dca82482af.wasm", - "tx_init_account.wasm": "tx_init_account.bd544ce16dae46177f9d9bd5281a53b4f4fe741833013a1f412b1f104c4bf6a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.1f1b18628e97758d837d0f25c313979cc529abc56797be240ce4c74032d603c5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.ad5ba0ef45cef6b59dab228dfaeb0dc643235639851c1adcff5d52152d2c01f5.wasm", - "tx_init_validator.wasm": "tx_init_validator.9b1ddb7e6dca6beadbf42c4da91771c92228562f4239494e4e56d57fd3c3b538.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.bd061f19c4d2e5801f671adb60e7e4d787a6f493a28c29f20a57e4a256a8dcbc.wasm", - "tx_transfer.wasm": "tx_transfer.1a8d43355f663c040a29d6e10e6cdfe6c73634aa5cd6863e1f3f5d58fe9ec560.wasm", - "tx_unbond.wasm": "tx_unbond.84a2a809d5b617740b991ddc5b88ad213ffa17aa178afe1f657b90cecebbaa7a.wasm", - "tx_update_vp.wasm": "tx_update_vp.84b896bce441e449cdbfa2401f2da8ea6f0c10725c1a8d9e1a03763b49056817.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8b171e9bdc02f6321f0b7ae6a881d00d26d7c5ecb00bb7aa0d77e411767517c9.wasm", - "tx_withdraw.wasm": "tx_withdraw.aede879ea5c81b4e479d89b2e9c5d9e5d80bf7e0a9e8a0a8c7bf7f2bb2b049f5.wasm", - "vp_nft.wasm": "vp_nft.87b7fc9e596891dd676294e18a467fd8ef16c372a3e22f7876b125fb9c31d606.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3ed495ea630de4a8d5459e09882c1862e768b826654aa1b700ed13e1e5cf8827.wasm", - "vp_token.wasm": "vp_token.b95cfb6bff76ffec7034f29453482937297cf713fd7cfd3f4f7045a2cb044bca.wasm", - "vp_user.wasm": "vp_user.fac7690818a1bc043cdb13d65b7f9d687343845c2e31e3eed841dc074803bff9.wasm" + "tx_from_intent.wasm": "tx_from_intent.f73091d9d780b5213a5d035a50cb456bd7aaf4d66d82fc140a750414c7e3549d.wasm", + "tx_ibc.wasm": "tx_ibc.2b26950a5b9e5d2e910b03be82974bb29e2ae5200415ec200733e64b76554fb3.wasm", + "tx_init_account.wasm": "tx_init_account.8bd690f83ba5389cb86ea58ed686f0b3ea95d395e47273c7014a73423a9516c6.wasm", + "tx_init_nft.wasm": "tx_init_nft.34b41f8e9daab8a8371334e16c94bbe39c7310b2ebc5d87e7c3700ee387a558e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e8e519dd3c8d21216d4a7824f8f481831738fad68849a80601264818f442d200.wasm", + "tx_init_validator.wasm": "tx_init_validator.6bf715c730574108bf002ea213df28d6778eaaedfa5fdffd073e65a23de2d374.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.427d942308ea3d7488183c10f43b102de310e6123f24b7b7b7b61e083cce8d15.wasm", + "tx_transfer.wasm": "tx_transfer.c3808f55209395d2748e4c0076e1b92ff892acc02dba0d0d96aad9faadd58e6a.wasm", + "tx_unbond.wasm": "tx_unbond.fc6f6633c37a48e26f0298b9047c800fa1dd70795b8186df28c5813fb30b271e.wasm", + "tx_update_vp.wasm": "tx_update_vp.a18118a46a716fdb0ae2c8163cfc145bcb0750c516322fc2b6af597e9d878237.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.9b5f5dcd2bffefa5f34416f14d381a67eb09a025fb035d7c1ae44dca93d96112.wasm", + "tx_withdraw.wasm": "tx_withdraw.5ba9d243c6cd44e9e51d481af937bb77362972e5ff19e17fe5b6b28726b43ffc.wasm", + "vp_nft.wasm": "vp_nft.ac0718c8155506fadbb328a3cc8050d05d19713327c4b63f075adfa0832a9daa.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.53a22c2db6bfa23efb6b84e68c82de60617c5746be8a31bd23703b18c0567444.wasm", + "vp_token.wasm": "vp_token.625d8ad6984fa037daa830a544ad066aa91c5ce4e54e8c2e368ba9c34381ef4e.wasm", + "vp_user.wasm": "vp_user.e3c0a5e9b2423e1629800d327cafabb612df85b95120501131a4ed5caeb4c3e3.wasm" } \ No newline at end of file From c641a0a88769be654438ed3e19f4abedbe167970 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 09:44:50 +0000 Subject: [PATCH 1051/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 56914d94a2..afebf1c841 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.7917f20ba026198a26288aa42e2e7a00683feecf7aa020c84900bb009e42286f.wasm", + "tx_bond.wasm": "tx_bond.2648c533fedbb8b1ef95e1ead104cd2ac9bba366b7c49154553fbccf6da2ea9a.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.ea405bf027b05afa51765c73d324a410222f04ac03c11b23196537947523db0c.wasm", - "tx_ibc.wasm": "tx_ibc.efe82694cf1dbfac6ca295908d974411e63b4a166e616861335d8857ea923859.wasm", - "tx_init_account.wasm": "tx_init_account.fe9fd42d78aa9336d8a193d8c90342b131d684cabbc8801b290de5e3561c5a22.wasm", - "tx_init_nft.wasm": "tx_init_nft.a69e07fc4e662247491ca0829b0de4c747ebbea1b5ab392edb7495f5a8f99b46.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8cf10a8ac6e3645dd0430fab615a5251ba8c28a5fc7075d1b9da2e01f012ba32.wasm", - "tx_init_validator.wasm": "tx_init_validator.d4d6afd6d6d848f508922a10623f54dfb11e2be2c1666edbfd7321643fe9fd0f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.acd01ee6127f604ed20884bfce3186ff6d3e45947ede7c7327b5a632ab1b6a08.wasm", - "tx_transfer.wasm": "tx_transfer.52ddd66c08730453fcd4b40634cb6eaa9e0bf02e8f7b212bf4e4bea87e6c4935.wasm", - "tx_unbond.wasm": "tx_unbond.f4147b4bdc32fd01c78604223225295d09d823fff9ac96045d34d346dbf4e773.wasm", - "tx_update_vp.wasm": "tx_update_vp.597dd03b92214a9859bb9a9a3de675f20a097c30a4eddc7721fa2131ba425d20.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.380ff2c91586e43864c573c6c5a3bb2ee1469a4f0cce06fddc279d750060c7c8.wasm", - "tx_withdraw.wasm": "tx_withdraw.e549788277620412e5969ffd566cff005a2fdb2a7972fbfa7396c28db76f7751.wasm", - "vp_nft.wasm": "vp_nft.95f500766279ea27b87a33b5819f01f46036bddb58eb71e71008cf389a7735dc.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.efc950e2ee04e8b86d68d44c43c684b0fced74d215812e5ac26e9a2b1b9f5dbe.wasm", - "vp_token.wasm": "vp_token.9cdda44d45fddef739ccf41c9b88373b1504d83aa8eb9d8a5b521496254e13ea.wasm", - "vp_user.wasm": "vp_user.ec4b88f6d7986c3719a23b9536d5fcfc06f3014a4dae62c9f9e04063a096660e.wasm" + "tx_from_intent.wasm": "tx_from_intent.179a4afc1b5067f33b3639a2da41ce244df6998f7fd369c33308cadec97b2ecf.wasm", + "tx_ibc.wasm": "tx_ibc.cc2e35c727caf5b047fca56f88ddb40829e1a2e303f906746dd3e6de98fed252.wasm", + "tx_init_account.wasm": "tx_init_account.9143cedad2922b9dcd5b045ef23c921b32370561bee8c9e0048a92dd06055c55.wasm", + "tx_init_nft.wasm": "tx_init_nft.4d9f431f4e3e1b5e838b7f94f651b20da5a27a08d56c4950ccde37ee4a9e53f5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.63ee6af02e6e9a7025a83ea656430620fd705fe6b1804f5fcce75004926d258a.wasm", + "tx_init_validator.wasm": "tx_init_validator.32a932280ca262cca970bef42efbabcb5c256bcca680a076628c834c5c14fbad.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.75f2826516bc27cabdd47b705335aadbb33fd205442947e60f9aaa91cbcc3e3a.wasm", + "tx_transfer.wasm": "tx_transfer.fa515da2556b48ffd2437c59ec5a80bebb6de9b98e6045ab5412b221bd79f7f3.wasm", + "tx_unbond.wasm": "tx_unbond.64534ca72249693766dcf65f960a03cf4ea8911bb14db834474e2a51f2a9884f.wasm", + "tx_update_vp.wasm": "tx_update_vp.36667910e225cbe29871e2f0c23455572a554e09e8bcffa853979319d18d875c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.2389d7e255fe3a691cad23f2dc41979e2623bc52ba6f45fe57363f69030be915.wasm", + "tx_withdraw.wasm": "tx_withdraw.0edf5cdb211e8ad93d2f570e73dcb45f803a5ed3723fc74320c99c2c51579d60.wasm", + "vp_nft.wasm": "vp_nft.92b72ddcc21dc82803293755c0e95ea0d75cd890275fae04b35e0b6bb05de43f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.368268de473a28b4870f9085a7e25436aa5f4af56664849c2a20848441a8c2e5.wasm", + "vp_token.wasm": "vp_token.cc9b54cde5072bff9817f3954d2c05e7dd7c269c8da8494bccb94bd3120f4f44.wasm", + "vp_user.wasm": "vp_user.745fd01380c50a125c09eec9988447ca00b51887bfbb19bb6ea2ae4fe3470c5a.wasm" } \ No newline at end of file From 75c249c409498a6c08a494fd69aaf26ea8171883 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 10:15:48 +0000 Subject: [PATCH 1052/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 370f2e5aa2..f22b117b80 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.bf0cd5aedaaed86b3bd07f27e7df9eae9375e305cc7b1aa6640c87c321355aee.wasm", + "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.f73091d9d780b5213a5d035a50cb456bd7aaf4d66d82fc140a750414c7e3549d.wasm", - "tx_ibc.wasm": "tx_ibc.2b26950a5b9e5d2e910b03be82974bb29e2ae5200415ec200733e64b76554fb3.wasm", - "tx_init_account.wasm": "tx_init_account.8bd690f83ba5389cb86ea58ed686f0b3ea95d395e47273c7014a73423a9516c6.wasm", - "tx_init_nft.wasm": "tx_init_nft.34b41f8e9daab8a8371334e16c94bbe39c7310b2ebc5d87e7c3700ee387a558e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e8e519dd3c8d21216d4a7824f8f481831738fad68849a80601264818f442d200.wasm", - "tx_init_validator.wasm": "tx_init_validator.6bf715c730574108bf002ea213df28d6778eaaedfa5fdffd073e65a23de2d374.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.427d942308ea3d7488183c10f43b102de310e6123f24b7b7b7b61e083cce8d15.wasm", - "tx_transfer.wasm": "tx_transfer.c3808f55209395d2748e4c0076e1b92ff892acc02dba0d0d96aad9faadd58e6a.wasm", - "tx_unbond.wasm": "tx_unbond.fc6f6633c37a48e26f0298b9047c800fa1dd70795b8186df28c5813fb30b271e.wasm", - "tx_update_vp.wasm": "tx_update_vp.a18118a46a716fdb0ae2c8163cfc145bcb0750c516322fc2b6af597e9d878237.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9b5f5dcd2bffefa5f34416f14d381a67eb09a025fb035d7c1ae44dca93d96112.wasm", - "tx_withdraw.wasm": "tx_withdraw.5ba9d243c6cd44e9e51d481af937bb77362972e5ff19e17fe5b6b28726b43ffc.wasm", - "vp_nft.wasm": "vp_nft.ac0718c8155506fadbb328a3cc8050d05d19713327c4b63f075adfa0832a9daa.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.53a22c2db6bfa23efb6b84e68c82de60617c5746be8a31bd23703b18c0567444.wasm", - "vp_token.wasm": "vp_token.625d8ad6984fa037daa830a544ad066aa91c5ce4e54e8c2e368ba9c34381ef4e.wasm", - "vp_user.wasm": "vp_user.e3c0a5e9b2423e1629800d327cafabb612df85b95120501131a4ed5caeb4c3e3.wasm" + "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", + "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", + "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", + "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", + "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", + "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", + "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", + "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", + "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", + "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", + "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", + "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" } \ No newline at end of file From d92ccabe2665987e0bb48909596c313c614c035f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 17:21:10 +0100 Subject: [PATCH 1053/1995] Log the outcome of trying to send an abort signal --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index d26aaadea7..bd6b04aa19 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -47,7 +47,14 @@ impl Drop for Oracle { // send an abort signal to shut down the // rest of the ledger gracefully let abort = self.abort.take().unwrap(); - let _ = abort.send(()); + match abort.send(()) { + Ok(()) => tracing::debug!("Oracle sent abort signal"), + Err(()) => { + // this isn't necessarily an issue as the ledger may have shut + // down first + tracing::debug!("Oracle was unable to send an abort signal") + } + }; } } From 244ac7412173631e4eda12f52faee8d7ac0346f3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 20:48:02 +0100 Subject: [PATCH 1054/1995] Check if oracle sender is closed at await points --- .../lib/node/ledger/ethereum_node/oracle.rs | 155 +++++++++--------- 1 file changed, 74 insertions(+), 81 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index bd6b04aa19..68ec9d050b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -90,12 +90,6 @@ impl Oracle { } true } - - /// Check if the receiver in the ledger has hung up. - /// Used to help determine when to stop the oracle - fn connected(&self) -> bool { - !self.sender.is_closed() - } } /// Set up an Oracle and run the process where the Oracle @@ -128,6 +122,8 @@ pub fn run_oracle( }) } +const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); + /// Given an oracle, watch for new Ethereum events, processing /// them into Anoma native types. /// @@ -138,96 +134,93 @@ async fn run_oracle_aux(oracle: Oracle) { // the latest block height seen and a queue of events // awaiting a certain number of confirmations let mut pending: Vec = Vec::new(); - const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); loop { - tokio::time::sleep(SLEEP_DUR).await; - // update the latest block height - let latest_block = loop { - match oracle.eth_block_number().await { - Ok(height) => break height, - Err(error) => { - tracing::warn!( - ?error, - "Couldn't get the latest Ethereum block height, will \ - keep trying" - ); - tokio::time::sleep(SLEEP_DUR).await; + tokio::select! { + should_continue = run_oracle_aux_inner(&oracle, &mut pending) => { + if !should_continue { + break; } - } - if !oracle.connected() { + }, + _ = oracle.sender.closed() => { tracing::info!( "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + receiver has hung up. Shutting down" ); - return; + break } }; - tracing::debug!(?latest_block, "Got latest Ethereum block height"); - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + tokio::time::sleep(SLEEP_DUR).await; + } +} + +// returns whether to continue or not +async fn run_oracle_aux_inner( + oracle: &Oracle, + pending: &mut Vec, +) -> bool { + // update the latest block height + let latest_block = loop { + match oracle.eth_block_number().await { + Ok(height) => break height, + Err(error) => { + tracing::warn!( + ?error, + "Couldn't get the latest Ethereum block height, will keep \ + trying" ); - return; + return true; } - continue; } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); - // check for events with at least `[MIN_CONFIRMATIONS]` - // confirmations. - for sig in signatures::SIGNATURES { - let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), - }; - // fetch the events for matching the given signature - let mut events = loop { - if let Ok(pending) = oracle - .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), - vec![addr], - vec![sig], - ) - .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) - { - break pending; - } - if !oracle.connected() { - tracing::info!( - "Ethereum oracle could not send events to the ledger; \ - the receiver has hung up. Shutting down" - ); - return; - } - }; - pending.append(&mut events); - if !oracle - .send(process_queue(&latest_block, &mut pending)) + }; + tracing::debug!(?latest_block, "Got latest Ethereum block height"); + // No blocks in existence yet with enough confirmations + if Uint256::from(MIN_CONFIRMATIONS) > latest_block { + return true; + } + let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + // check for events with at least `[MIN_CONFIRMATIONS]` + // confirmations. + for sig in signatures::SIGNATURES { + let addr: Address = match signatures::SigType::from(sig) { + signatures::SigType::Bridge => MINT_CONTRACT.0.into(), + signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), + }; + // fetch the events for matching the given signature + let mut events = loop { + if let Ok(pending) = oracle + .check_for_events( + block_to_check.clone(), + Some(block_to_check.clone()), + vec![addr], + vec![sig], + ) .await + .map(|logs| { + logs.into_iter() + .filter_map(|log| { + PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) + .ok() + }) + .collect::>() + }) { - tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" - ); - return; + break pending; } + }; + pending.append(&mut events); + if !oracle.send(process_queue(&latest_block, pending)).await { + tracing::info!( + "Ethereum oracle could not send events to the ledger; the \ + receiver has hung up. Shutting down" + ); + return false; } } + true } /// Check which events in the queue have reached their From d167d4cddcff0ba869b956add9c3ee6862510715 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 8 Oct 2022 22:37:55 +0100 Subject: [PATCH 1055/1995] Check blocks starting from 0 --- .../lib/node/ledger/ethereum_node/oracle.rs | 162 +++++++++++++----- 1 file changed, 120 insertions(+), 42 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 68ec9d050b..449fc95098 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,6 +1,7 @@ use std::ops::Deref; use clarity::Address; +use eyre::{eyre, Result}; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; @@ -122,8 +123,6 @@ pub fn run_oracle( }) } -const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); - /// Given an oracle, watch for new Ethereum events, processing /// them into Anoma native types. /// @@ -134,16 +133,26 @@ async fn run_oracle_aux(oracle: Oracle) { // the latest block height seen and a queue of events // awaiting a certain number of confirmations let mut pending: Vec = Vec::new(); + + // TODO(namada#560): get the appropriate Ethereum block height to start + // checking from rather than starting from zero every time + let mut next_block_to_check: Uint256 = 0u8.into(); + loop { + tracing::info!( + ?next_block_to_check, + "Checking Ethereum block for bridge events" + ); tokio::select! { - should_continue = run_oracle_aux_inner(&oracle, &mut pending) => { - if !should_continue { - break; + result = process(&oracle, &mut pending, next_block_to_check.clone()) => { + match result { + Ok(()) => next_block_to_check += 1u8.into(), + Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), } }, _ = oracle.sender.closed() => { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ + "Ethereum oracle can not send events to the ledger; the \ receiver has hung up. Shutting down" ); break @@ -153,31 +162,46 @@ async fn run_oracle_aux(oracle: Oracle) { } } -// returns whether to continue or not -async fn run_oracle_aux_inner( +const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); + +/// Checks if the given block has any events relating to the bridge, and if so, +/// sends them to the oracle's `sender` +async fn process( oracle: &Oracle, pending: &mut Vec, -) -> bool { + block_to_check: Uint256, +) -> Result<()> { // update the latest block height let latest_block = loop { - match oracle.eth_block_number().await { - Ok(height) => break height, + let latest_block = match oracle.eth_block_number().await { + Ok(height) => height, Err(error) => { - tracing::warn!( - ?error, - "Couldn't get the latest Ethereum block height, will keep \ - trying" - ); - return true; + return Err(eyre!( + "Couldn't get the latest synced Ethereum block height \ + from the RPC endpoint: {:?}", + error + )); } + }; + let minimum_latest_block = + block_to_check.clone() + Uint256::from(MIN_CONFIRMATIONS); + if minimum_latest_block > latest_block { + tracing::debug!( + ?block_to_check, + ?latest_block, + ?minimum_latest_block, + "Waiting for enough Ethereum blocks to be synced" + ); + tokio::time::sleep(SLEEP_DUR).await; + continue; } + break latest_block; }; - tracing::debug!(?latest_block, "Got latest Ethereum block height"); - // No blocks in existence yet with enough confirmations - if Uint256::from(MIN_CONFIRMATIONS) > latest_block { - return true; - } - let block_to_check = latest_block.clone() - MIN_CONFIRMATIONS.into(); + tracing::debug!( + ?block_to_check, + ?latest_block, + "Got latest Ethereum block height" + ); // check for events with at least `[MIN_CONFIRMATIONS]` // confirmations. for sig in signatures::SIGNATURES { @@ -185,9 +209,15 @@ async fn run_oracle_aux_inner( signatures::SigType::Bridge => MINT_CONTRACT.0.into(), signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; + tracing::debug!( + ?block_to_check, + ?addr, + ?sig, + "Checking for bridge events" + ); // fetch the events for matching the given signature let mut events = loop { - if let Ok(pending) = oracle + let logs = match oracle .check_for_events( block_to_check.clone(), Some(block_to_check.clone()), @@ -195,32 +225,80 @@ async fn run_oracle_aux_inner( vec![sig], ) .await - .map(|logs| { - logs.into_iter() - .filter_map(|log| { - PendingEvent::decode( - sig, - block_to_check.clone(), - log.data.0.as_slice(), - ) - .ok() - }) - .collect::>() - }) { - break pending; + Ok(logs) => logs, + Err(error) => { + return Err(eyre!( + "Couldn't check for events ({sig} from {addr}) with \ + the RPC endpoint: {:?}", + error + )); + } + }; + if !logs.is_empty() { + tracing::info!( + ?block_to_check, + ?addr, + ?sig, + n_events = logs.len(), + "Found bridge events in Ethereum block" + ) } + break logs + .into_iter() + .filter_map(|log| { + match PendingEvent::decode( + sig, + block_to_check.clone(), + log.data.0.as_slice(), + ) { + Ok(event) => Some(event), + Err(error) => { + tracing::error!( + ?error, + ?block_to_check, + ?addr, + ?sig, + "Couldn't decode event: {:#?}", + log + ); + None + } + } + }) + .collect(); }; pending.append(&mut events); - if !oracle.send(process_queue(&latest_block, pending)).await { + if !pending.is_empty() { tracing::info!( - "Ethereum oracle could not send events to the ledger; the \ - receiver has hung up. Shutting down" + ?block_to_check, + ?addr, + ?sig, + pending = pending.len(), + "There are Ethereum events pending" ); - return false; + } + let confirmed = process_queue(&latest_block, pending); + if !confirmed.is_empty() { + tracing::info!( + ?block_to_check, + ?addr, + ?sig, + pending = pending.len(), + confirmed = confirmed.len(), + ?MIN_CONFIRMATIONS, + "Some events that have reached the minimum number of \ + confirmations and will be sent onwards" + ); + } + if !oracle.send(confirmed).await { + return Err(eyre!( + "Could not send all bridge events ({sig} from {addr}) to the \ + shell" + )); } } - true + Ok(()) } /// Check which events in the queue have reached their From 4e185bdc0778d081035352af1d2731dcecfa446b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 11:25:44 +0100 Subject: [PATCH 1056/1995] Make oracle sleep time configurable --- .../lib/node/ledger/ethereum_node/oracle.rs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 449fc95098..686f443fa2 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -1,4 +1,5 @@ use std::ops::Deref; +use std::time::Duration; use clarity::Address; use eyre::{eyre, Result}; @@ -21,6 +22,9 @@ pub(crate) const MIN_CONFIRMATIONS: u64 = 100; const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); +/// The default amount of time the oracle will wait between checking blocks +const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); + /// A client that can talk to geth and parse /// and relay events relevant to Anoma to the /// ledger process @@ -33,6 +37,8 @@ pub struct Oracle { /// A channel to signal that the ledger should shut down /// because the Oracle has stopped abort: Option>, + /// How long the oracle should wait between checking blocks + backoff: Duration, } impl Deref for Oracle { @@ -65,11 +71,13 @@ impl Oracle { url: &str, sender: BoundedSender, abort: Sender<()>, + backoff: Duration, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), sender, abort: Some(abort), + backoff, } } @@ -91,6 +99,10 @@ impl Oracle { } true } + + async fn sleep(&self) { + tokio::time::sleep(self.backoff).await; + } } /// Set up an Oracle and run the process where the Oracle @@ -110,7 +122,12 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = Oracle::new(&url, sender, abort_sender); + let oracle = Oracle::new( + &url, + sender, + abort_sender, + DEFAULT_BACKOFF, + ); run_oracle_aux(oracle).await; tracing::info!( @@ -158,12 +175,10 @@ async fn run_oracle_aux(oracle: Oracle) { break } }; - tokio::time::sleep(SLEEP_DUR).await; + oracle.sleep().await; } } -const SLEEP_DUR: std::time::Duration = std::time::Duration::from_secs(1); - /// Checks if the given block has any events relating to the bridge, and if so, /// sends them to the oracle's `sender` async fn process( @@ -192,7 +207,9 @@ async fn process( ?minimum_latest_block, "Waiting for enough Ethereum blocks to be synced" ); - tokio::time::sleep(SLEEP_DUR).await; + // this isn't an error condition, so we continue in the loop here + // with a back off + oracle.sleep().await; continue; } break latest_block; @@ -352,6 +369,8 @@ mod test_oracle { client, sender: eth_sender, abort: Some(abort), + // backoff should be short for tests so that they run faster + backoff: Duration::from_millis(5), }, admin_channel, eth_recv: eth_receiver, From 9ab00706721eb4f07c0bc7a79e786d99dab6b9ed Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 12:28:34 +0100 Subject: [PATCH 1057/1995] Add blocks_checked channel and test --- .../lib/node/ledger/ethereum_node/oracle.rs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 686f443fa2..fc4ce23ec4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -39,6 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, + /// If provided, the oracle will attempt to send blocks that it has checked + /// to this channel, but won't pause if this channel is full or + /// disconnected. + blocks_checked: Option>, } impl Deref for Oracle { @@ -78,6 +82,7 @@ impl Oracle { sender, abort: Some(abort), backoff, + blocks_checked: None, } } @@ -163,7 +168,14 @@ async fn run_oracle_aux(oracle: Oracle) { tokio::select! { result = process(&oracle, &mut pending, next_block_to_check.clone()) => { match result { - Ok(()) => next_block_to_check += 1u8.into(), + Ok(()) => { + if let Some(blocks_checked) = &oracle.blocks_checked { + if let Err(error) = blocks_checked.try_send(next_block_to_check.clone()) { + tracing::warn!(?error, block = ?next_block_to_check, "Failed to send block checked to channel"); + }; + } + next_block_to_check += 1u8.into() + }, Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), } }, @@ -342,6 +354,7 @@ fn process_queue( mod test_oracle { use namada::types::ethereum_events::TransferToEthereum; use tokio::sync::oneshot::{channel, Receiver}; + use tokio::time::timeout; use super::*; use crate::node::ledger::ethereum_node::events::{ @@ -356,6 +369,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, + blocks_checked_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -363,6 +377,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); + let (blocks_checked_sender, blocks_checked_receiver) = + tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -371,9 +387,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), + blocks_checked: Some(blocks_checked_sender), }, admin_channel, eth_recv: eth_receiver, + blocks_checked_recv: blocks_checked_receiver, abort_recv, } } @@ -645,4 +663,61 @@ mod test_oracle { drop(eth_recv); oracle.join().expect("Test failed"); } + + /// Test that Ethereum blocks are checked in sequence up to the latest block + /// that has reached the minimum number of confirmations + #[tokio::test] + async fn test_blocks_checked_sequence() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + mut blocks_checked_recv, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + + // set the height of the chain such that the first `n` blocks are deep + // enough to be considered confirmed by the oracle + let n = 10; + for height in 0..MIN_CONFIRMATIONS + n { + admin_channel + .send(TestCmd::NewHeight(Uint256::from(height))) + .expect("Test failed"); + } + // check that the oracle has indeed processed the first `n` blocks + for height in 0u64..n { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + // check that the oracle hasn't yet checked any further blocks + assert!( + timeout(Duration::from_secs(1), blocks_checked_recv.recv()) + .await + .is_err() + ); + + // increase the height of the chain by one, and check that the oracle + // has now processed the `n+1`th block + admin_channel + .send(TestCmd::NewHeight(Uint256::from(MIN_CONFIRMATIONS + n))) + .expect("Test failed"); + + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(n)); + + drop(eth_recv); + oracle.join().expect("Test failed"); + } } From ce1e2ab69c6000acb053a59da19d826ade5e94ab Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 14:43:07 +0100 Subject: [PATCH 1058/1995] Add test_all_blocks_checked --- .../lib/node/ledger/ethereum_node/oracle.rs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index fc4ce23ec4..9a0d012d9a 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -720,4 +720,61 @@ mod test_oracle { drop(eth_recv); oracle.join().expect("Test failed"); } + + /// Test that if the Ethereum RPC endpoint returns a latest block that is + /// more than one block later than the previous latest block we received, we + /// still check all the blocks inbetween + #[tokio::test] + async fn test_all_blocks_checked() { + let TestPackage { + oracle, + eth_recv, + admin_channel, + mut blocks_checked_recv, + .. + } = setup(); + let oracle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + + let initially_confirmed_blocks = 10; + admin_channel + .send(TestCmd::NewHeight(Uint256::from( + MIN_CONFIRMATIONS + initially_confirmed_blocks, + ))) + .expect("Test failed"); + + // check that the oracle has indeed processed the first `n` blocks, even + // though the first latest block that the oracle received was not 0 + for height in 0u64..initially_confirmed_blocks { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + // the next time the oracle checks, the latest block will have increased + // by more than one + let latest_block = initially_confirmed_blocks + 10; + admin_channel + .send(TestCmd::NewHeight(Uint256::from( + MIN_CONFIRMATIONS + latest_block, + ))) + .expect("Test failed"); + + // check that the oracle still checks the blocks inbetween + for height in initially_confirmed_blocks..latest_block { + let block_checked = + timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); + assert_eq!(block_checked, Uint256::from(height)); + } + + drop(eth_recv); + oracle.join().expect("Test failed"); + } } From d2a70e9d2d3458b8f207de757e5a8867db919702 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 14:49:07 +0100 Subject: [PATCH 1059/1995] Remove unnecessary `loop` --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 9a0d012d9a..e236690943 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -245,7 +245,7 @@ async fn process( "Checking for bridge events" ); // fetch the events for matching the given signature - let mut events = loop { + let mut events = { let logs = match oracle .check_for_events( block_to_check.clone(), @@ -273,8 +273,7 @@ async fn process( "Found bridge events in Ethereum block" ) } - break logs - .into_iter() + logs.into_iter() .filter_map(|log| { match PendingEvent::decode( sig, From 28ba22039c2b0615378deb41520c4c0b6a57d0ef Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 15:28:16 +0100 Subject: [PATCH 1060/1995] Clear up tests and terminology --- .../lib/node/ledger/ethereum_node/oracle.rs | 151 +++++++++--------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index e236690943..94fc91a201 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -22,7 +22,7 @@ pub(crate) const MIN_CONFIRMATIONS: u64 = 100; const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); -/// The default amount of time the oracle will wait between checking blocks +/// The default amount of time the oracle will wait between processing blocks const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); /// A client that can talk to geth and parse @@ -39,10 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will attempt to send blocks that it has checked - /// to this channel, but won't pause if this channel is full or - /// disconnected. - blocks_checked: Option>, + /// If provided, the oracle will attempt to send block heights that it has + /// processed to this channel, but won't pause if this channel is full + /// or disconnected. + blocks_processed: Option>, } impl Deref for Oracle { @@ -57,13 +57,15 @@ impl Drop for Oracle { fn drop(&mut self) { // send an abort signal to shut down the // rest of the ledger gracefully - let abort = self.abort.take().unwrap(); - match abort.send(()) { - Ok(()) => tracing::debug!("Oracle sent abort signal"), + match self.abort.take().unwrap().send(()) { + Ok(()) => tracing::info!("Oracle sent abort signal"), Err(()) => { // this isn't necessarily an issue as the ledger may have shut // down first - tracing::debug!("Oracle was unable to send an abort signal") + tracing::debug!( + "Oracle was unable to send an abort signal as the abort \ + channel was already closed" + ) } }; } @@ -82,7 +84,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - blocks_checked: None, + blocks_processed: None, } } @@ -151,32 +153,31 @@ pub fn run_oracle( /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process async fn run_oracle_aux(oracle: Oracle) { - // Initialize our local state. This includes - // the latest block height seen and a queue of events - // awaiting a certain number of confirmations + // Initialize a queue to keep events which are awaiting a certain number of + // confirmations let mut pending: Vec = Vec::new(); // TODO(namada#560): get the appropriate Ethereum block height to start // checking from rather than starting from zero every time - let mut next_block_to_check: Uint256 = 0u8.into(); + let mut next_block_to_process: Uint256 = 0u8.into(); loop { tracing::info!( - ?next_block_to_check, + ?next_block_to_process, "Checking Ethereum block for bridge events" ); tokio::select! { - result = process(&oracle, &mut pending, next_block_to_check.clone()) => { + result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_checked) = &oracle.blocks_checked { - if let Err(error) = blocks_checked.try_send(next_block_to_check.clone()) { - tracing::warn!(?error, block = ?next_block_to_check, "Failed to send block checked to channel"); + if let Some(blocks_processed) = &oracle.blocks_processed { + if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { + tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); }; } - next_block_to_check += 1u8.into() + next_block_to_process += 1u8.into() }, - Err(error) => tracing::warn!(?error, block = ?next_block_to_check, "Error while trying to check Ethereum block for bridge events"), + Err(error) => tracing::warn!(?error, block = ?next_block_to_process, "Error while trying to process Ethereum block"), } }, _ = oracle.sender.closed() => { @@ -192,11 +193,11 @@ async fn run_oracle_aux(oracle: Oracle) { } /// Checks if the given block has any events relating to the bridge, and if so, -/// sends them to the oracle's `sender` +/// sends them to the oracle's `sender` channel async fn process( oracle: &Oracle, pending: &mut Vec, - block_to_check: Uint256, + block_to_process: Uint256, ) -> Result<()> { // update the latest block height let latest_block = loop { @@ -211,10 +212,10 @@ async fn process( } }; let minimum_latest_block = - block_to_check.clone() + Uint256::from(MIN_CONFIRMATIONS); + block_to_process.clone() + Uint256::from(MIN_CONFIRMATIONS); if minimum_latest_block > latest_block { tracing::debug!( - ?block_to_check, + ?block_to_process, ?latest_block, ?minimum_latest_block, "Waiting for enough Ethereum blocks to be synced" @@ -227,7 +228,7 @@ async fn process( break latest_block; }; tracing::debug!( - ?block_to_check, + ?block_to_process, ?latest_block, "Got latest Ethereum block height" ); @@ -239,7 +240,7 @@ async fn process( signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), }; tracing::debug!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, "Checking for bridge events" @@ -248,8 +249,8 @@ async fn process( let mut events = { let logs = match oracle .check_for_events( - block_to_check.clone(), - Some(block_to_check.clone()), + block_to_process.clone(), + Some(block_to_process.clone()), vec![addr], vec![sig], ) @@ -266,7 +267,7 @@ async fn process( }; if !logs.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, n_events = logs.len(), @@ -277,14 +278,14 @@ async fn process( .filter_map(|log| { match PendingEvent::decode( sig, - block_to_check.clone(), + block_to_process.clone(), log.data.0.as_slice(), ) { Ok(event) => Some(event), Err(error) => { tracing::error!( ?error, - ?block_to_check, + ?block_to_process, ?addr, ?sig, "Couldn't decode event: {:#?}", @@ -294,12 +295,12 @@ async fn process( } } }) - .collect(); + .collect() }; pending.append(&mut events); if !pending.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, pending = pending.len(), @@ -309,7 +310,7 @@ async fn process( let confirmed = process_queue(&latest_block, pending); if !confirmed.is_empty() { tracing::info!( - ?block_to_check, + ?block_to_process, ?addr, ?sig, pending = pending.len(), @@ -368,7 +369,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - blocks_checked_recv: tokio::sync::mpsc::Receiver, + blocks_processed_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -376,7 +377,7 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (blocks_checked_sender, blocks_checked_receiver) = + let (blocks_processed, blocks_processed_recv) = tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { @@ -386,11 +387,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - blocks_checked: Some(blocks_checked_sender), + blocks_processed: Some(blocks_processed), }, admin_channel, eth_recv: eth_receiver, - blocks_checked_recv: blocks_checked_receiver, + blocks_processed_recv, abort_recv, } } @@ -663,58 +664,62 @@ mod test_oracle { oracle.join().expect("Test failed"); } - /// Test that Ethereum blocks are checked in sequence up to the latest block - /// that has reached the minimum number of confirmations + /// Test that Ethereum blocks are processed in sequence up to the latest + /// block that has reached the minimum number of confirmations #[tokio::test] async fn test_blocks_checked_sequence() { let TestPackage { oracle, eth_recv, admin_channel, - mut blocks_checked_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); - // set the height of the chain such that the first `n` blocks are deep + // set the height of the chain such that there are some blocks deep // enough to be considered confirmed by the oracle - let n = 10; - for height in 0..MIN_CONFIRMATIONS + n { + let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations + let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; + for height in 0..synced_block_height + 1 { admin_channel .send(TestCmd::NewHeight(Uint256::from(height))) .expect("Test failed"); } - // check that the oracle has indeed processed the first `n` blocks - for height in 0u64..n { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + // check that the oracle indeed processes the confirmed blocks + for height in 0u64..confirmed_block_height + 1 { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks + // TODO: check this in a deterministic way rather than just waiting a + // bit assert!( - timeout(Duration::from_secs(1), blocks_checked_recv.recv()) + timeout(Duration::from_secs(1), blocks_processed_recv.recv()) .await .is_err() ); // increase the height of the chain by one, and check that the oracle - // has now processed the `n+1`th block + // processed the next confirmed block + let synced_block_height = synced_block_height + 1; admin_channel - .send(TestCmd::NewHeight(Uint256::from(MIN_CONFIRMATIONS + n))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(n)); + assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); oracle.join().expect("Test failed"); @@ -729,48 +734,48 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_checked_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); - let initially_confirmed_blocks = 10; + let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations + let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; admin_channel - .send(TestCmd::NewHeight(Uint256::from( - MIN_CONFIRMATIONS + initially_confirmed_blocks, - ))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 - for height in 0u64..initially_confirmed_blocks { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + for height in 0u64..confirmed_block_height + 1 { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } // the next time the oracle checks, the latest block will have increased // by more than one - let latest_block = initially_confirmed_blocks + 10; + let difference = 10; + let synced_block_height = synced_block_height + difference; admin_channel - .send(TestCmd::NewHeight(Uint256::from( - MIN_CONFIRMATIONS + latest_block, - ))) + .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); // check that the oracle still checks the blocks inbetween - for height in initially_confirmed_blocks..latest_block { - let block_checked = - timeout(Duration::from_secs(3), blocks_checked_recv.recv()) + for height in (confirmed_block_height + 1) + ..(confirmed_block_height + difference + 1) + { + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) .await .expect("Timed out waiting for block to be checked") .unwrap(); - assert_eq!(block_checked, Uint256::from(height)); + assert_eq!(block_processed, Uint256::from(height)); } drop(eth_recv); From 8824714b1622e461f70cb2919a531d46d29f2fcf Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:20:38 +0100 Subject: [PATCH 1061/1995] Reformat tracing call --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 94fc91a201..df13093be5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -177,7 +177,11 @@ async fn run_oracle_aux(oracle: Oracle) { } next_block_to_process += 1u8.into() }, - Err(error) => tracing::warn!(?error, block = ?next_block_to_process, "Error while trying to process Ethereum block"), + Err(error) => tracing::warn!( + ?error, + block = ?next_block_to_process, + "Error while trying to process Ethereum block" + ), } }, _ = oracle.sender.closed() => { From 5f1fbd111ed091e920668878d4cf748957fa6389 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:23:34 +0100 Subject: [PATCH 1062/1995] Inline error variable into format string --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index df13093be5..b15643082f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -210,8 +210,7 @@ async fn process( Err(error) => { return Err(eyre!( "Couldn't get the latest synced Ethereum block height \ - from the RPC endpoint: {:?}", - error + from the RPC endpoint: {error:?}", )); } }; @@ -264,8 +263,7 @@ async fn process( Err(error) => { return Err(eyre!( "Couldn't check for events ({sig} from {addr}) with \ - the RPC endpoint: {:?}", - error + the RPC endpoint: {error:?}", )); } }; From 7535bdb2e9e44b2e9d6bed79bf33143b9bae1575 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:24:30 +0100 Subject: [PATCH 1063/1995] Fix misspelling --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index b15643082f..565a12f37b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -729,7 +729,7 @@ mod test_oracle { /// Test that if the Ethereum RPC endpoint returns a latest block that is /// more than one block later than the previous latest block we received, we - /// still check all the blocks inbetween + /// still check all the blocks in between #[tokio::test] async fn test_all_blocks_checked() { let TestPackage { From fa0494bb7318f9e85db07f027efedc8d4c47516b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 11 Oct 2022 11:03:48 +0100 Subject: [PATCH 1064/1995] Use a watch channel for making the latest block processed visible --- .../lib/node/ledger/ethereum_node/oracle.rs | 110 +++++++++++------- 1 file changed, 67 insertions(+), 43 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 565a12f37b..7795e297b1 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,6 +7,7 @@ use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; +use tokio::sync::watch; use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -39,10 +40,8 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will attempt to send block heights that it has - /// processed to this channel, but won't pause if this channel is full - /// or disconnected. - blocks_processed: Option>, + /// If provided, the oracle will put here the latest block it has processed + latest_block_processed: Option>>, } impl Deref for Oracle { @@ -84,7 +83,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - blocks_processed: None, + latest_block_processed: None, } } @@ -170,10 +169,16 @@ async fn run_oracle_aux(oracle: Oracle) { result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_processed) = &oracle.blocks_processed { - if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { - tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); - }; + if let Some(blocks_processed) = &oracle.latest_block_processed { + _ = blocks_processed.send_if_modified(|block: &mut Option| { + if let Some(current) = block { + if *current == next_block_to_process { + return false; + } + } + block.replace(next_block_to_process.clone()); + true + }); } next_block_to_process += 1u8.into() }, @@ -371,7 +376,8 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - blocks_processed_recv: tokio::sync::mpsc::Receiver, + latest_block_processed_recv: + tokio::sync::watch::Receiver>, abort_recv: Receiver<()>, } @@ -379,8 +385,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (blocks_processed, blocks_processed_recv) = - tokio::sync::mpsc::channel(1000); + let (latest_block_processed_send, latest_block_processed_recv) = + tokio::sync::watch::channel(None); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -389,11 +395,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - blocks_processed: Some(blocks_processed), + latest_block_processed: Some(latest_block_processed_send), }, admin_channel, eth_recv: eth_receiver, - blocks_processed_recv, + latest_block_processed_recv, abort_recv, } } @@ -674,7 +680,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_processed_recv, + mut latest_block_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -692,22 +698,22 @@ mod test_oracle { } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks - // TODO: check this in a deterministic way rather than just waiting a - // bit - assert!( - timeout(Duration::from_secs(1), blocks_processed_recv.recv()) - .await - .is_err() - ); + assert!(!latest_block_processed_recv.has_changed().unwrap()); // increase the height of the chain by one, and check that the oracle // processed the next confirmed block @@ -716,11 +722,17 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .unwrap(); assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); @@ -736,7 +748,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut blocks_processed_recv, + mut latest_block_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -752,11 +764,17 @@ mod test_oracle { // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 for height in 0u64..confirmed_block_height + 1 { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } @@ -772,11 +790,17 @@ mod test_oracle { for height in (confirmed_block_height + 1) ..(confirmed_block_height + difference + 1) { - let block_processed = - timeout(Duration::from_secs(3), blocks_processed_recv.recv()) - .await - .expect("Timed out waiting for block to be checked") - .unwrap(); + timeout( + Duration::from_secs(3), + latest_block_processed_recv.changed(), + ) + .await + .expect("Timed out waiting for block to be processed") + .unwrap(); + let block_processed = latest_block_processed_recv + .borrow_and_update() + .to_owned() + .expect(&format!("Test failed for height {height}")); assert_eq!(block_processed, Uint256::from(height)); } From fc27d6a1c69ab1c98821523de3b066dba1822cbb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 18:06:55 +0100 Subject: [PATCH 1065/1995] Revert "Use a watch channel for making the latest block processed visible" This reverts commit 8178fe51cdb52507117451710c031175ad1c0bc9. --- .../lib/node/ledger/ethereum_node/oracle.rs | 110 +++++++----------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 7795e297b1..565a12f37b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -7,7 +7,6 @@ use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; -use tokio::sync::watch; use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -40,8 +39,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will put here the latest block it has processed - latest_block_processed: Option>>, + /// If provided, the oracle will attempt to send block heights that it has + /// processed to this channel, but won't pause if this channel is full + /// or disconnected. + blocks_processed: Option>, } impl Deref for Oracle { @@ -83,7 +84,7 @@ impl Oracle { sender, abort: Some(abort), backoff, - latest_block_processed: None, + blocks_processed: None, } } @@ -169,16 +170,10 @@ async fn run_oracle_aux(oracle: Oracle) { result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => { - if let Some(blocks_processed) = &oracle.latest_block_processed { - _ = blocks_processed.send_if_modified(|block: &mut Option| { - if let Some(current) = block { - if *current == next_block_to_process { - return false; - } - } - block.replace(next_block_to_process.clone()); - true - }); + if let Some(blocks_processed) = &oracle.blocks_processed { + if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { + tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); + }; } next_block_to_process += 1u8.into() }, @@ -376,8 +371,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - latest_block_processed_recv: - tokio::sync::watch::Receiver>, + blocks_processed_recv: tokio::sync::mpsc::Receiver, abort_recv: Receiver<()>, } @@ -385,8 +379,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (latest_block_processed_send, latest_block_processed_recv) = - tokio::sync::watch::channel(None); + let (blocks_processed, blocks_processed_recv) = + tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -395,11 +389,11 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - latest_block_processed: Some(latest_block_processed_send), + blocks_processed: Some(blocks_processed), }, admin_channel, eth_recv: eth_receiver, - latest_block_processed_recv, + blocks_processed_recv, abort_recv, } } @@ -680,7 +674,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut latest_block_processed_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -698,22 +692,22 @@ mod test_oracle { } // check that the oracle indeed processes the confirmed blocks for height in 0u64..confirmed_block_height + 1 { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } // check that the oracle hasn't yet checked any further blocks - assert!(!latest_block_processed_recv.has_changed().unwrap()); + // TODO: check this in a deterministic way rather than just waiting a + // bit + assert!( + timeout(Duration::from_secs(1), blocks_processed_recv.recv()) + .await + .is_err() + ); // increase the height of the chain by one, and check that the oracle // processed the next confirmed block @@ -722,17 +716,11 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .unwrap(); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); @@ -748,7 +736,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, - mut latest_block_processed_recv, + mut blocks_processed_recv, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -764,17 +752,11 @@ mod test_oracle { // check that the oracle has indeed processed the first `n` blocks, even // though the first latest block that the oracle received was not 0 for height in 0u64..confirmed_block_height + 1 { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } @@ -790,17 +772,11 @@ mod test_oracle { for height in (confirmed_block_height + 1) ..(confirmed_block_height + difference + 1) { - timeout( - Duration::from_secs(3), - latest_block_processed_recv.changed(), - ) - .await - .expect("Timed out waiting for block to be processed") - .unwrap(); - let block_processed = latest_block_processed_recv - .borrow_and_update() - .to_owned() - .expect(&format!("Test failed for height {height}")); + let block_processed = + timeout(Duration::from_secs(3), blocks_processed_recv.recv()) + .await + .expect("Timed out waiting for block to be checked") + .unwrap(); assert_eq!(block_processed, Uint256::from(height)); } From c18e8ba4e2db3c6b5ad6d67dcc30946e49702c56 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 21 Oct 2022 11:12:44 +0100 Subject: [PATCH 1066/1995] Update apps/src/lib/node/ledger/ethereum_node/oracle.rs Co-authored-by: Jacob Turner --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 565a12f37b..fb6c490a79 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -318,7 +318,7 @@ async fn process( pending = pending.len(), confirmed = confirmed.len(), ?MIN_CONFIRMATIONS, - "Some events that have reached the minimum number of \ + "Some events have reached the minimum number of \ confirmations and will be sent onwards" ); } From 45c70eaf6be426526ae90405e624f8a0c5d123e1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 11:45:31 +0100 Subject: [PATCH 1067/1995] Fix formatting --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index fb6c490a79..f54db835f5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -318,8 +318,8 @@ async fn process( pending = pending.len(), confirmed = confirmed.len(), ?MIN_CONFIRMATIONS, - "Some events have reached the minimum number of \ - confirmations and will be sent onwards" + "Some events have reached the minimum number of confirmations \ + and will be sent onwards" ); } if !oracle.send(confirmed).await { From ec082b1bbaebb2da1ea004bf31e0b70bc452c368 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 19:43:01 +0100 Subject: [PATCH 1068/1995] Split up start_ethereum_node and remove mock components --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 35 +------ .../ledger/ethereum_node/test_tools/mod.rs | 52 ----------- apps/src/lib/node/ledger/mod.rs | 91 +++++++++++-------- 3 files changed, 54 insertions(+), 124 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 4556f58eb3..064c7151d8 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -29,37 +29,6 @@ pub enum Error { pub type Result = std::result::Result; -/// Represents a subprocess running an Ethereum full node -pub enum Subprocess { - Mock(test_tools::mock_eth_fullnode::EthereumNode), - Geth(eth_fullnode::EthereumNode), -} - -/// Starts an Ethereum fullnode in a subprocess and returns a handle for -/// monitoring it using [`monitor`], as well as a channel for halting it. -pub async fn start(url: &str, real: bool) -> Result<(Subprocess, Sender<()>)> { - if real { - let (node, sender) = eth_fullnode::EthereumNode::new(url).await?; - Ok((Subprocess::Geth(node), sender)) - } else { - let (node, sender) = - test_tools::mock_eth_fullnode::EthereumNode::new().await?; - Ok((Subprocess::Mock(node), sender)) - } -} - -/// Monitor the Ethereum fullnode. If it stops or an abort -/// signal is sent, the subprocess is halted. -pub async fn monitor( - ethereum_node: Subprocess, - abort_recv: Receiver>, -) -> Result<()> { - match ethereum_node { - Subprocess::Mock(node) => monitor_node(node, abort_recv).await, - Subprocess::Geth(node) => monitor_node(node, abort_recv).await, - } -} - /// A handle on an Ethereum full node subprocess for monitoring it #[async_trait] pub trait Monitorable { @@ -67,7 +36,9 @@ pub trait Monitorable { async fn kill(&mut self); } -async fn monitor_node( +/// Monitor the Ethereum fullnode. If it stops or an abort +/// signal is sent, the subprocess is halted. +pub async fn monitor( mut node: impl Monitorable, abort_recv: Receiver>, ) -> Result<()> { diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs index 1788bc3982..072fabfed8 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs @@ -1,57 +1,5 @@ pub mod events_endpoint; -/// tools for running a mock ethereum fullnode process -pub mod mock_eth_fullnode { - use async_trait::async_trait; - use tokio::sync::oneshot::{channel, Receiver, Sender}; - - use super::super::Result; - use crate::node::ledger::ethereum_node::Monitorable; - - pub struct EthereumNode { - #[allow(dead_code)] - receiver: Receiver<()>, - } - - impl EthereumNode { - pub async fn new() -> Result<(EthereumNode, Sender<()>)> { - let (abort_sender, receiver) = channel(); - Ok((Self { receiver }, abort_sender)) - } - } - - #[async_trait] - impl Monitorable for EthereumNode { - async fn wait(&mut self) -> Result<()> { - std::future::pending().await - } - - async fn kill(&mut self) {} - } -} - -pub mod mock_oracle { - - use namada::types::ethereum_events::EthereumEvent; - use tokio::macros::support::poll_fn; - use tokio::sync::mpsc::Sender as BoundedSender; - use tokio::sync::oneshot::Sender; - - pub fn run_oracle( - _: impl AsRef, - _: BoundedSender, - mut abort: Sender<()>, - ) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { - tracing::info!("Mock Ethereum event oracle is starting"); - - poll_fn(|cx| abort.poll_closed(cx)).await; - - tracing::info!("Mock Ethereum event oracle is no longer running"); - }) - } -} - #[cfg(test)] pub mod mock_web3_client { use std::cell::RefCell; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 80fc9ecdb0..5f9b1096cc 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -22,11 +22,12 @@ use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; use tokio::task; use tower::ServiceBuilder; use self::abortable::AbortableSpawner; +use self::ethereum_node::eth_fullnode; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::{ethereum, TendermintMode}; @@ -229,9 +230,13 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start Tendermint node let tendermint_node = start_tendermint(&mut spawner, &config); - // Start Ethereum full node and its oracle - let (eth_node, eth_receiver, oracle) = - start_ethereum_node(&mut spawner, &config).await; + // Start managed Ethereum node if necessary + let (eth_node, abort_sender) = + maybe_start_geth(&mut spawner, &config).await; + + // Start oracle if necessary + let (eth_receiver, oracle) = + maybe_start_ethereum_oracle(&config, abort_sender).await; // Start ABCI server and broadcaster (the latter only if we are a validator // node) @@ -609,27 +614,59 @@ fn start_tendermint( /// /// An oracle is also returned, along with its associated channel, /// for receiving Ethereum events from `geth`. -async fn start_ethereum_node( +async fn maybe_start_ethereum_oracle( + config: &config::Ledger, + abort_sender: oneshot::Sender<()>, +) -> (Option>, task::JoinHandle<()>) { + let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); + + // Start the oracle for listening to Ethereum events + let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); + let oracle = match config.ethereum.mode { + ethereum::Mode::Managed | ethereum::Mode::Remote => { + ethereum_node::oracle::run_oracle( + ethereum_url, + eth_sender, + abort_sender, + ) + } + ethereum::Mode::EventsEndpoint => { + ethereum_node::test_tools::events_endpoint::serve( + eth_sender, + abort_sender, + ) + } + ethereum::Mode::Off => spawn_dummy_task(()), + }; + + (Some(eth_receiver), oracle) +} + +/// Launches a new task managing a `geth` process into the asynchronous +/// runtime, and returns its [`task::JoinHandle`]. +/// +/// An oracle is also returned, along with its associated channel, +/// for receiving Ethereum events from `geth`. +async fn maybe_start_geth( spawner: &mut AbortableSpawner, config: &config::Ledger, ) -> ( task::JoinHandle>, - Option>, - task::JoinHandle<()>, + tokio::sync::oneshot::Sender<()>, ) { - if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { + if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) + || !matches!(config.ethereum.mode, ethereum::Mode::Managed) + { let eth_node = spawn_dummy_task(Ok(())); - let oracle = spawn_dummy_task(()); - return (eth_node, None, oracle); + let (abort_sender, _) = tokio::sync::oneshot::channel::<()>(); + return (eth_node, abort_sender); } let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); // Boot up geth and wait for it to finish syncing - let start_managed_eth_node = - matches!(config.ethereum.mode, ethereum::Mode::Managed); let (eth_node, abort_sender) = - ethereum_node::start(ðereum_url, start_managed_eth_node) + eth_fullnode::EthereumNode::new(ðereum_url) .await .expect("Unable to start the Ethereum fullnode"); @@ -662,33 +699,7 @@ async fn start_ethereum_node( } } }); - - // Start the oracle for listening to Ethereum events - let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); - let oracle = match config.ethereum.mode { - ethereum::Mode::Managed | ethereum::Mode::Remote => { - ethereum_node::oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ) - } - ethereum::Mode::EventsEndpoint => { - ethereum_node::test_tools::events_endpoint::serve( - eth_sender, - abort_sender, - ) - } - ethereum::Mode::Off => { - ethereum_node::test_tools::mock_oracle::run_oracle( - ethereum_url, - eth_sender, - abort_sender, - ) - } - }; - - (eth_node, Some(eth_receiver), oracle) + (eth_node, abort_sender) } /// Spawn a dummy asynchronous task into the runtime, From 8fa4e00066a25076104ecbcf9ca8ca37e87d6825 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 20:36:29 +0100 Subject: [PATCH 1069/1995] Only validators should run Ethereum oracles --- apps/src/lib/node/ledger/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 5f9b1096cc..e77783f54c 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -618,6 +618,10 @@ async fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, ) -> (Option>, task::JoinHandle<()>) { + if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { + return (None, spawn_dummy_task(())); + } + let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); // Start the oracle for listening to Ethereum events From 0adac7a73a38381289bc848289cfe8ac52f2ac25 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 20:37:54 +0100 Subject: [PATCH 1070/1995] Fix EthereumNode docstring --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 064c7151d8..c8671cb2d4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -108,7 +108,7 @@ pub mod eth_fullnode { impl EthereumNode { /// Starts the geth process and returns a handle to it as well - /// as an oracle that can relay data from geth to the ledger. + /// as a channel on which an abort signal can be sent to shut it down. /// /// First looks up which network to connect to from an env var. /// It then starts the process and waits for it to finish From c063b1bc84a02d19377af8ec6589487a71ee8a72 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 09:01:18 +0100 Subject: [PATCH 1071/1995] Remove Monitorable trait --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index c8671cb2d4..37669f195f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -3,7 +3,6 @@ pub mod oracle; pub mod test_tools; use std::ffi::OsString; -use async_trait::async_trait; use thiserror::Error; use tokio::sync::oneshot::{Receiver, Sender}; @@ -29,17 +28,10 @@ pub enum Error { pub type Result = std::result::Result; -/// A handle on an Ethereum full node subprocess for monitoring it -#[async_trait] -pub trait Monitorable { - async fn wait(&mut self) -> Result<()>; - async fn kill(&mut self); -} - /// Monitor the Ethereum fullnode. If it stops or an abort /// signal is sent, the subprocess is halted. pub async fn monitor( - mut node: impl Monitorable, + mut node: eth_fullnode::EthereumNode, abort_recv: Receiver>, ) -> Result<()> { tokio::select! { @@ -68,13 +60,12 @@ pub async fn monitor( pub mod eth_fullnode { use std::time::Duration; - use async_trait::async_trait; use tokio::process::{Child, Command}; use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::task::LocalSet; use web30::client::Web3; - use super::{Error, Monitorable, Result}; + use super::{Error, Result}; /// A handle to a running geth process and a channel /// that indicates it should shut down if the oracle @@ -171,14 +162,11 @@ pub mod eth_fullnode { }) .await } - } - #[async_trait] - impl Monitorable for EthereumNode { /// Wait for the process to finish or an abort message was /// received from the Oracle process. If either, return the /// status. - async fn wait(&mut self) -> Result<()> { + pub async fn wait(&mut self) -> Result<()> { use futures::future::{self, Either}; let child_proc = self.process.wait(); @@ -197,7 +185,7 @@ pub mod eth_fullnode { } /// Stop the geth process - async fn kill(&mut self) { + pub async fn kill(&mut self) { self.process.kill().await.unwrap(); } } From 839793f26f5b453cf80e370d0349ecb12ca284ce Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 21 Oct 2022 15:05:00 +0100 Subject: [PATCH 1072/1995] Release lock on wallet in e2e tests --- tests/src/e2e/setup.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1ca7a2f41c..1a0ce85a4b 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -189,6 +189,10 @@ pub fn network( println!("'init-network' output: {}", unread); let net = Network { chain_id }; + // release lock on wallet by dropping the + // child process + drop(init_network); + // Move the "others" accounts wallet in the main base dir, so that we can // use them with `Who::NonValidator` let chain_dir = test_dir.path().join(net.chain_id.as_str()); From 12d6e9ac185dc9476b2a758975d9b6a84d1891ad Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 16:55:33 +0200 Subject: [PATCH 1073/1995] oops --- .../lib/node/ledger/ethereum_node/oracle.rs | 25 ++++++------------- .../ledger/ethereum_node/test_tools/mod.rs | 18 ++++++++++++- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 565a12f37b..0d06607e92 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -39,10 +39,6 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, - /// If provided, the oracle will attempt to send block heights that it has - /// processed to this channel, but won't pause if this channel is full - /// or disconnected. - blocks_processed: Option>, } impl Deref for Oracle { @@ -84,7 +80,6 @@ impl Oracle { sender, abort: Some(abort), backoff, - blocks_processed: None, } } @@ -169,14 +164,7 @@ async fn run_oracle_aux(oracle: Oracle) { tokio::select! { result = process(&oracle, &mut pending, next_block_to_process.clone()) => { match result { - Ok(()) => { - if let Some(blocks_processed) = &oracle.blocks_processed { - if let Err(error) = blocks_processed.try_send(next_block_to_process.clone()) { - tracing::warn!(?error, block = ?next_block_to_process, "Failed to send processed block height to `blocks_processed` channel"); - }; - } - next_block_to_process += 1u8.into() - }, + Ok(()) => next_block_to_process += 1u8.into(), Err(error) => tracing::warn!( ?error, block = ?next_block_to_process, @@ -371,16 +359,14 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - blocks_processed_recv: tokio::sync::mpsc::Receiver, + blocks_processed_recv: tokio::sync::mpsc::UnboundedReceiver, abort_recv: Receiver<()>, } /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { - let (admin_channel, client) = Web3::setup(); + let (admin_channel, blocks_processed_recv, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (blocks_processed, blocks_processed_recv) = - tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -389,7 +375,6 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), - blocks_processed: Some(blocks_processed), }, admin_channel, eth_recv: eth_receiver, @@ -439,6 +424,7 @@ mod test_oracle { oracle, mut eth_recv, admin_channel, + blocks_processed_recv: _processed, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -466,6 +452,7 @@ mod test_oracle { oracle, mut eth_recv, admin_channel, + blocks_processed_recv: _processed, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -508,6 +495,7 @@ mod test_oracle { oracle, eth_recv, admin_channel, + blocks_processed_recv: _processed, .. } = setup(); let oracle = std::thread::spawn(move || { @@ -564,6 +552,7 @@ mod test_oracle { oracle, mut eth_recv, admin_channel, + blocks_processed_recv: _processed, .. } = setup(); let oracle = std::thread::spawn(move || { diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs index 1788bc3982..cbc3a045e4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs @@ -105,6 +105,8 @@ pub mod mock_web3_client { active: bool, latest_block_height: Uint256, events: Vec<(MockEventType, Vec, u32, Sender<()>)>, + blocks_processed: UnboundedSender, + last_block_processed: Option, } impl Web3 { @@ -120,16 +122,23 @@ pub mod mock_web3_client { /// Return a new client and a separate sender /// to send in admin commands - pub fn setup() -> (UnboundedSender, Self) { + pub fn setup() + -> (UnboundedSender, UnboundedReceiver, Self) + { // we can only send one command at a time. let (cmd_sender, cmd_channel) = unbounded_channel(); + let (block_processed_send, block_processed_recv) = + unbounded_channel(); ( cmd_sender, + block_processed_recv, Self(RefCell::new(Web3Client { cmd_channel, active: true, latest_block_height: Default::default(), events: vec![], + blocks_processed: block_processed_send, + last_block_processed: None, })), ) } @@ -206,6 +215,13 @@ pub mod mock_web3_client { client.events.push((event_ty, data, height, seen)); } } + if client.last_block_processed < Some(block_to_check.clone()) { + client + .blocks_processed + .send(block_to_check.clone()) + .unwrap(); + client.last_block_processed = Some(block_to_check); + } Ok(logs) } else { Err(Error::Runtime("Uh oh, I'm not responding".into())) From 691fa56d5c9dc5dc0205cbe268024545392daa1e Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 24 Oct 2022 10:20:35 +0200 Subject: [PATCH 1074/1995] Update apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs index cbc3a045e4..7b3d3618e4 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs @@ -215,7 +215,7 @@ pub mod mock_web3_client { client.events.push((event_ty, data, height, seen)); } } - if client.last_block_processed < Some(block_to_check.clone()) { + if client.last_block_processed.as_ref() < Some(&block_to_check) { client .blocks_processed .send(block_to_check.clone()) From 14210a8bc1f6acdfce7bf9d51f6c241c2fdce7da Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 10 Oct 2022 16:44:17 +0100 Subject: [PATCH 1075/1995] Add Ethereum bridge chain parameters + refactor runtime config --- .../ledger.rs} | 5 +++-- apps/src/lib/config/ethereum_bridge/mod.rs | 2 ++ apps/src/lib/config/ethereum_bridge/params.rs | 22 +++++++++++++++++++ apps/src/lib/config/genesis.rs | 3 +++ apps/src/lib/config/mod.rs | 6 ++--- apps/src/lib/node/ledger/mod.rs | 20 ++++++++++------- tests/src/e2e/ledger_tests.rs | 4 ++-- tests/src/e2e/setup.rs | 4 ++-- 8 files changed, 49 insertions(+), 17 deletions(-) rename apps/src/lib/config/{ethereum.rs => ethereum_bridge/ledger.rs} (92%) create mode 100644 apps/src/lib/config/ethereum_bridge/mod.rs create mode 100644 apps/src/lib/config/ethereum_bridge/params.rs diff --git a/apps/src/lib/config/ethereum.rs b/apps/src/lib/config/ethereum_bridge/ledger.rs similarity index 92% rename from apps/src/lib/config/ethereum.rs rename to apps/src/lib/config/ethereum_bridge/ledger.rs index d02df90184..6cdb7e17e8 100644 --- a/apps/src/lib/config/ethereum.rs +++ b/apps/src/lib/config/ethereum_bridge/ledger.rs @@ -1,4 +1,4 @@ -//! Configuration settings to do with the Ethereum bridge. +//! Runtime configuration for a validator node #[allow(unused_imports)] use namada::types::ethereum_events::EthereumEvent; use serde::{Deserialize, Serialize}; @@ -28,7 +28,8 @@ pub enum Mode { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { - /// The mode in which to run the Ethereum bridge + /// The mode in which to run the Ethereum node and oracle setup of this + /// validator pub mode: Mode, /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use /// to listen for events from the Ethereum bridge smart contracts diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs new file mode 100644 index 0000000000..ab72d58b42 --- /dev/null +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -0,0 +1,2 @@ +pub mod ledger; +pub mod params; diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs new file mode 100644 index 0000000000..c36ed9773c --- /dev/null +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -0,0 +1,22 @@ +//! Blockchain-level parameters for the configuration of the Ethereum bridge. +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Config { + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: u64, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators + pub contract_addresses: Addresses, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Addresses { + /// The Ethereum address of the proxy contract e.g. + /// 0x6B175474E89094C44Da98b954EedeAC495271d0F + pub proxy: String, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F + pub native_erc20: String, +} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5441ce524f..debf2d9f9c 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -44,6 +44,7 @@ pub mod genesis_config { EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; + use crate::config::ethereum_bridge; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct HexString(pub String); @@ -121,6 +122,8 @@ pub mod genesis_config { pub gov_params: GovernanceParamsConfig, // Treasury parameters pub treasury_params: TreasuryParamasConfig, + // Ethereum bridge config + pub ethereum_bridge_params: Option, // Wasm definitions pub wasm: HashMap, } diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 6d3797800e..bd9484e141 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -1,6 +1,6 @@ //! Node and client configuration -pub mod ethereum; +pub mod ethereum_bridge; pub mod genesis; pub mod global; pub mod utils; @@ -83,7 +83,7 @@ pub struct Ledger { pub chain_id: ChainId, pub shell: Shell, pub tendermint: Tendermint, - pub ethereum: ethereum::Config, + pub ethereum_bridge: ethereum_bridge::ledger::Config, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -197,7 +197,7 @@ impl Ledger { ), instrumentation_namespace: "anoman_tm".to_string(), }, - ethereum: ethereum::Config::default(), + ethereum_bridge: ethereum_bridge::ledger::Config::default(), } } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index e77783f54c..0a1695de33 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -30,7 +30,7 @@ use self::abortable::AbortableSpawner; use self::ethereum_node::eth_fullnode; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; -use crate::config::{ethereum, TendermintMode}; +use crate::config::{ethereum_bridge, TendermintMode}; use crate::facade::tendermint_proto::abci::CheckTxType; use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; @@ -622,25 +622,26 @@ async fn maybe_start_ethereum_oracle( return (None, spawn_dummy_task(())); } - let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); + let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); // Start the oracle for listening to Ethereum events let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); - let oracle = match config.ethereum.mode { - ethereum::Mode::Managed | ethereum::Mode::Remote => { + let oracle = match config.ethereum_bridge.mode { + ethereum_bridge::ledger::Mode::Managed + | ethereum_bridge::ledger::Mode::Remote => { ethereum_node::oracle::run_oracle( ethereum_url, eth_sender, abort_sender, ) } - ethereum::Mode::EventsEndpoint => { + ethereum_bridge::ledger::Mode::EventsEndpoint => { ethereum_node::test_tools::events_endpoint::serve( eth_sender, abort_sender, ) } - ethereum::Mode::Off => spawn_dummy_task(()), + ethereum_bridge::ledger::Mode::Off => spawn_dummy_task(()), }; (Some(eth_receiver), oracle) @@ -659,14 +660,17 @@ async fn maybe_start_geth( tokio::sync::oneshot::Sender<()>, ) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) - || !matches!(config.ethereum.mode, ethereum::Mode::Managed) + || !matches!( + config.ethereum_bridge.mode, + ethereum_bridge::ledger::Mode::Managed + ) { let eth_node = spawn_dummy_task(Ok(())); let (abort_sender, _) = tokio::sync::oneshot::channel::<()>(); return (eth_node, abort_sender); } - let ethereum_url = config.ethereum.oracle_rpc_endpoint.clone(); + let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); // Boot up geth and wait for it to finish syncing let (eth_node, abort_sender) = diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index fd42df7e72..b4dca67fbe 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -18,7 +18,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use namada::types::token; -use namada_apps::config::ethereum; +use namada_apps::config::ethereum_bridge; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; @@ -1814,7 +1814,7 @@ fn test_genesis_validators() -> Result<()> { .set_port(first_port + 1); config.ledger.shell.ledger_address.set_port(first_port + 2); // disable eth full node - config.ledger.ethereum.mode = ethereum::Mode::Off; + config.ledger.ethereum_bridge.mode = ethereum_bridge::ledger::Mode::Off; config }; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1a0ce85a4b..35c578f2f6 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -21,7 +21,7 @@ use itertools::{Either, Itertools}; use namada::types::chain::ChainId; use namada_apps::client::utils; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; -use namada_apps::config::{ethereum, Config}; +use namada_apps::config::{ethereum_bridge, Config}; use namada_apps::{config, wallet}; use rand::Rng; use tempfile::{tempdir, TempDir}; @@ -77,7 +77,7 @@ pub fn update_actor_config( /// Disable the Ethereum fullnode of `who`. pub fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { update_actor_config(test, chain_id, who, |config| { - config.ledger.ethereum.mode = ethereum::Mode::Off; + config.ledger.ethereum_bridge.mode = ethereum_bridge::ledger::Mode::Off; }); } From 7d03d211d43dff21ad85c1861413a4afb8a55b76 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 14 Oct 2022 14:10:30 +0100 Subject: [PATCH 1076/1995] Update params (WIP) --- apps/src/lib/config/ethereum_bridge/params.rs | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index c36ed9773c..12fd50d28a 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,11 +1,20 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use serde::{Deserialize, Serialize}; +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct MinimumConfirmations(u64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + Self(1) + } +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Config { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. - pub min_confirmations: u64, + pub min_confirmations: MinimumConfirmations, /// The addresses of the Ethereum contracts that need to be directly known /// by validators pub contract_addresses: Addresses, @@ -13,10 +22,31 @@ pub struct Config { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Addresses { - /// The Ethereum address of the proxy contract e.g. + /// The Ethereum address of the bridge contract e.g. /// 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub proxy: String, + pub bridge: EthereumContract, + /// The Ethereum address of the governance contract e.g. + /// 0x6B175474E89094C44Da98b954EedeAC495271d0F + pub governance: EthereumContract, /// The Ethereum address of the ERC20 contract that represents this chain's /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F pub native_erc20: String, } + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct ContractVersion(u64); + +impl Default for ContractVersion { + fn default() -> Self { + Self(1) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct EthereumContract { + /// The Ethereum address of the contract e.g. + /// 0x6B175474E89094C44Da98b954EedeAC495271d0F + pub address: String, + /// The version of the contract e.g. 1 + pub version: ContractVersion, +} From 4a9164278f091486a6063206afa43b2d92999818 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 17:09:01 +0100 Subject: [PATCH 1077/1995] Order fields appropriately for toml serialization, and use transparent fields --- apps/src/lib/config/ethereum_bridge/params.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 12fd50d28a..901dd610fd 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[repr(transparent)] pub struct MinimumConfirmations(u64); impl Default for MinimumConfirmations { @@ -22,18 +23,19 @@ pub struct Config { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Addresses { + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F + pub native_erc20: String, /// The Ethereum address of the bridge contract e.g. /// 0x6B175474E89094C44Da98b954EedeAC495271d0F pub bridge: EthereumContract, /// The Ethereum address of the governance contract e.g. /// 0x6B175474E89094C44Da98b954EedeAC495271d0F pub governance: EthereumContract, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub native_erc20: String, } #[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[repr(transparent)] pub struct ContractVersion(u64); impl Default for ContractVersion { From 54e0c828b89a4bedb77829ecd68ed921dea17d5c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 17:32:36 +0100 Subject: [PATCH 1078/1995] Add a test for TOML serialization --- apps/src/lib/config/ethereum_bridge/params.rs | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 901dd610fd..a3825198c4 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,17 +1,17 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct MinimumConfirmations(u64); impl Default for MinimumConfirmations { fn default() -> Self { - Self(1) + Self(100) } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct Config { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. @@ -21,7 +21,7 @@ pub struct Config { pub contract_addresses: Addresses, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct Addresses { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F @@ -34,7 +34,7 @@ pub struct Addresses { pub governance: EthereumContract, } -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct ContractVersion(u64); @@ -44,7 +44,7 @@ impl Default for ContractVersion { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct EthereumContract { /// The Ethereum address of the contract e.g. /// 0x6B175474E89094C44Da98b954EedeAC495271d0F @@ -52,3 +52,39 @@ pub struct EthereumContract { /// The version of the contract e.g. 1 pub version: ContractVersion, } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use super::*; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = Config { + min_confirmations: MinimumConfirmations::default(), + contract_addresses: Addresses { + native_erc20: "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872" + .to_string(), + bridge: EthereumContract { + address: "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" + .to_string(), + version: ContractVersion::default(), + }, + governance: EthereumContract { + address: "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" + .to_string(), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: Config = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} From 29175fe677f32d2f19856a45720cb68738effd1f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 17:44:31 +0100 Subject: [PATCH 1079/1995] Add more types and docstrings --- apps/src/lib/config/ethereum_bridge/params.rs | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index a3825198c4..6e91794a7b 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,14 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use serde::{Deserialize, Serialize}; +/// Represents a configuration value for an Ethereum address e.g. +/// 0x6B175474E89094C44Da98b954EedeAC495271d0F +#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[repr(transparent)] +pub struct Address(String); + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct MinimumConfirmations(u64); @@ -11,29 +19,32 @@ impl Default for MinimumConfirmations { } } +/// Represents chain parameters for the Ethereum bridge. #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct Config { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. pub min_confirmations: MinimumConfirmations, /// The addresses of the Ethereum contracts that need to be directly known - /// by validators - pub contract_addresses: Addresses, + /// by validators. + pub contracts: Contracts, } +/// Represents all the Ethereum contracts that need to be directly know about by +/// validators. #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct Addresses { +pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token e.g. 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub native_erc20: String, - /// The Ethereum address of the bridge contract e.g. - /// 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub bridge: EthereumContract, - /// The Ethereum address of the governance contract e.g. - /// 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub governance: EthereumContract, + /// native token. + pub native_erc20: Address, + /// The Ethereum address of the bridge contract. + pub bridge: UpgradeableContract, + /// The Ethereum address of the governance contract. + pub governance: UpgradeableContract, } +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct ContractVersion(u64); @@ -44,12 +55,12 @@ impl Default for ContractVersion { } } +/// Represents an Ethereum contract that may be upgraded. #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct EthereumContract { - /// The Ethereum address of the contract e.g. - /// 0x6B175474E89094C44Da98b954EedeAC495271d0F - pub address: String, - /// The version of the contract e.g. 1 +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: Address, + /// The version of the contract. Starts from 1. pub version: ContractVersion, } @@ -66,17 +77,22 @@ mod tests { fn test_round_trip_toml_serde() -> Result<()> { let config = Config { min_confirmations: MinimumConfirmations::default(), - contract_addresses: Addresses { - native_erc20: "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872" - .to_string(), - bridge: EthereumContract { - address: "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" - .to_string(), + contracts: Contracts { + native_erc20: Address( + "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), + ), + bridge: UpgradeableContract { + address: Address( + "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" + .to_string(), + ), version: ContractVersion::default(), }, - governance: EthereumContract { - address: "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" - .to_string(), + governance: UpgradeableContract { + address: Address( + "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" + .to_string(), + ), version: ContractVersion::default(), }, }, From 3227ffa898a97b5b17921bd8db7ebb456115c074 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 10:58:02 +0100 Subject: [PATCH 1080/1995] Use NonZeroU64 type --- apps/src/lib/config/ethereum_bridge/params.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 6e91794a7b..e6877d909b 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,4 +1,6 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. +use std::num::NonZeroU64; + use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address e.g. @@ -11,11 +13,11 @@ pub struct Address(String); /// confirmations an Ethereum event must reach before it can be acted on. #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] -pub struct MinimumConfirmations(u64); +pub struct MinimumConfirmations(NonZeroU64); impl Default for MinimumConfirmations { fn default() -> Self { - Self(100) + Self(unsafe { NonZeroU64::new_unchecked(100) }) } } @@ -47,11 +49,11 @@ pub struct Contracts { /// upgraded. Starts from 1. #[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] -pub struct ContractVersion(u64); +pub struct ContractVersion(NonZeroU64); impl Default for ContractVersion { fn default() -> Self { - Self(1) + Self(unsafe { NonZeroU64::new_unchecked(1) }) } } From c82402c9e51d3bbb2018167282eac5c911716439 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 21 Oct 2022 10:58:51 +0100 Subject: [PATCH 1081/1995] Update apps/src/lib/config/ethereum_bridge/ledger.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/config/ethereum_bridge/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/config/ethereum_bridge/ledger.rs b/apps/src/lib/config/ethereum_bridge/ledger.rs index 6cdb7e17e8..c1c1e02d11 100644 --- a/apps/src/lib/config/ethereum_bridge/ledger.rs +++ b/apps/src/lib/config/ethereum_bridge/ledger.rs @@ -29,7 +29,7 @@ pub enum Mode { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { /// The mode in which to run the Ethereum node and oracle setup of this - /// validator + /// validator. pub mode: Mode, /// The Ethereum JSON-RPC endpoint that the Ethereum event oracle will use /// to listen for events from the Ethereum bridge smart contracts From 7565a5226b3df9904706ac5e5d9deeb8eabd48af Mon Sep 17 00:00:00 2001 From: James Date: Fri, 21 Oct 2022 10:58:57 +0100 Subject: [PATCH 1082/1995] Update apps/src/lib/config/ethereum_bridge/ledger.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/config/ethereum_bridge/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/config/ethereum_bridge/ledger.rs b/apps/src/lib/config/ethereum_bridge/ledger.rs index c1c1e02d11..924546e0e2 100644 --- a/apps/src/lib/config/ethereum_bridge/ledger.rs +++ b/apps/src/lib/config/ethereum_bridge/ledger.rs @@ -1,4 +1,4 @@ -//! Runtime configuration for a validator node +//! Runtime configuration for a validator node. #[allow(unused_imports)] use namada::types::ethereum_events::EthereumEvent; use serde::{Deserialize, Serialize}; From dd78b996a4be099c3bde9708bd29001cb83d8394 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 21 Oct 2022 10:59:14 +0100 Subject: [PATCH 1083/1995] Update apps/src/lib/config/ethereum_bridge/params.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/config/ethereum_bridge/params.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index e6877d909b..e5312716a9 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -3,8 +3,10 @@ use std::num::NonZeroU64; use serde::{Deserialize, Serialize}; -/// Represents a configuration value for an Ethereum address e.g. -/// 0x6B175474E89094C44Da98b954EedeAC495271d0F +/// Represents a configuration value for an Ethereum address. +/// +/// For instance: +/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] #[repr(transparent)] pub struct Address(String); From 27307130d1198a8471588b9b3a4bcba5314e5909 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 13:12:34 +0100 Subject: [PATCH 1084/1995] Add SAFETY comments --- apps/src/lib/config/ethereum_bridge/params.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index e5312716a9..3ba5be7d22 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -19,6 +19,8 @@ pub struct MinimumConfirmations(NonZeroU64); impl Default for MinimumConfirmations { fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64`can be violated + // is if we construct values of this type using 0 as argument. Self(unsafe { NonZeroU64::new_unchecked(100) }) } } @@ -55,6 +57,8 @@ pub struct ContractVersion(NonZeroU64); impl Default for ContractVersion { fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64`can be violated + // is if we construct values of this type using 0 as argument. Self(unsafe { NonZeroU64::new_unchecked(1) }) } } From 11972f6b3bc9e045798ab22c046f4a6ae7246278 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 13:14:18 +0100 Subject: [PATCH 1085/1995] Fix formatting --- apps/src/lib/config/ethereum_bridge/params.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 3ba5be7d22..810941623c 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -19,7 +19,7 @@ pub struct MinimumConfirmations(NonZeroU64); impl Default for MinimumConfirmations { fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64`can be violated + // SAFETY: The only way the API contract of `NonZeroU64` can be violated // is if we construct values of this type using 0 as argument. Self(unsafe { NonZeroU64::new_unchecked(100) }) } @@ -57,8 +57,9 @@ pub struct ContractVersion(NonZeroU64); impl Default for ContractVersion { fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64`can be violated - // is if we construct values of this type using 0 as argument. + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. Self(unsafe { NonZeroU64::new_unchecked(1) }) } } From 65c80a6fff5809aa91225c35a93f0478dbf43c9b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 13:59:48 +0100 Subject: [PATCH 1086/1995] Rename Config -> GenesisConfig --- apps/src/lib/config/ethereum_bridge/params.rs | 6 +++--- apps/src/lib/config/genesis.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 810941623c..857230a6c4 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -27,7 +27,7 @@ impl Default for MinimumConfirmations { /// Represents chain parameters for the Ethereum bridge. #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct Config { +pub struct GenesisConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. pub min_confirmations: MinimumConfirmations, @@ -84,7 +84,7 @@ mod tests { /// in any of the config structs. #[test] fn test_round_trip_toml_serde() -> Result<()> { - let config = Config { + let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { native_erc20: Address( @@ -107,7 +107,7 @@ mod tests { }, }; let serialized = toml::to_string(&config)?; - let deserialized: Config = toml::from_str(&serialized)?; + let deserialized: GenesisConfig = toml::from_str(&serialized)?; assert_eq!(config, deserialized); Ok(()) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index debf2d9f9c..288f0efbd4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -123,7 +123,8 @@ pub mod genesis_config { // Treasury parameters pub treasury_params: TreasuryParamasConfig, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: + Option, // Wasm definitions pub wasm: HashMap, } From 2373f32ae84122ec517ad4dd4d8d1abb99255ab8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 24 Oct 2022 13:09:40 +0000 Subject: [PATCH 1087/1995] [ci] wasm checksums update --- wasm/checksums.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f22b117b80..fd02651d91 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", + "tx_bond.wasm": "tx_bond.2df13db8c3627972f331e4324ad3d2ce193fcd8d3a3908a81401e5ffb2c063bc.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", - "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", - "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", - "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", - "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", - "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", - "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", + "tx_from_intent.wasm": "tx_from_intent.5069eecf4a38b825cf254b415647b52e15dddd4e76c47e1973d29eba7512ab72.wasm", + "tx_ibc.wasm": "tx_ibc.28bd6a18fd516a206609d7a0b99a46b35c7ae5a021d1300db9e206f0091e0152.wasm", + "tx_init_account.wasm": "tx_init_account.53ddf6e67ed21df2b9581735a4eae99d70859b136396da75372a62b96c96bc0c.wasm", + "tx_init_nft.wasm": "tx_init_nft.8710f24dd510d1adb3524263cfbcdc3ea148a3e52dc623469f30fcf1146b9d3d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2f60e58e688948e07ae94532ffbf6ae4e805bcbb0de937232f759069a49841e2.wasm", + "tx_init_validator.wasm": "tx_init_validator.a61c090ddbe1c17e43468bf979b8e183ca115b51b2dd41b907575f83b419ff9b.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.d0b3084f16f0180a17fd2451a4c923b35289eab04259d0e8cc4ca73c8ff4da91.wasm", + "tx_transfer.wasm": "tx_transfer.9ff30ec40ec063307ac2f34fa8754f0c1708e2449977d21b58966fa5da6272b9.wasm", + "tx_unbond.wasm": "tx_unbond.f6e5962bf262dde8e853bc7e9b805c6007b4d4ac6e8015e50746c5971d09cb6e.wasm", "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", - "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.72e987ee3e6ff3e73ee5f7fb73159d1c0326784f7a87942fa0fa6400c6155dcc.wasm", + "tx_withdraw.wasm": "tx_withdraw.5f69e193e458163d8a6adacc71e78598b3472dd0993ee448bc6d382ec16e23ca.wasm", + "vp_nft.wasm": "vp_nft.54473e621cce7870b3680204a3801968985680e4b0ef8195226556487e93fae0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c52e6411032c0b54a71b9dcf92471d291298b7bc18843e3f4e05007d3bfa0023.wasm", "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", - "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" + "vp_user.wasm": "vp_user.564f91ef8d0d76e695dc09976efa312434c6f952dcf82ca12fd52fc25f7015be.wasm" } \ No newline at end of file From 572eeed3a2d4d010623fa277aac582dded140018 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 24 Oct 2022 13:09:42 +0000 Subject: [PATCH 1088/1995] [ci] wasm checksums update --- wasm/checksums.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index fd02651d91..f22b117b80 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.2df13db8c3627972f331e4324ad3d2ce193fcd8d3a3908a81401e5ffb2c063bc.wasm", + "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.5069eecf4a38b825cf254b415647b52e15dddd4e76c47e1973d29eba7512ab72.wasm", - "tx_ibc.wasm": "tx_ibc.28bd6a18fd516a206609d7a0b99a46b35c7ae5a021d1300db9e206f0091e0152.wasm", - "tx_init_account.wasm": "tx_init_account.53ddf6e67ed21df2b9581735a4eae99d70859b136396da75372a62b96c96bc0c.wasm", - "tx_init_nft.wasm": "tx_init_nft.8710f24dd510d1adb3524263cfbcdc3ea148a3e52dc623469f30fcf1146b9d3d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2f60e58e688948e07ae94532ffbf6ae4e805bcbb0de937232f759069a49841e2.wasm", - "tx_init_validator.wasm": "tx_init_validator.a61c090ddbe1c17e43468bf979b8e183ca115b51b2dd41b907575f83b419ff9b.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d0b3084f16f0180a17fd2451a4c923b35289eab04259d0e8cc4ca73c8ff4da91.wasm", - "tx_transfer.wasm": "tx_transfer.9ff30ec40ec063307ac2f34fa8754f0c1708e2449977d21b58966fa5da6272b9.wasm", - "tx_unbond.wasm": "tx_unbond.f6e5962bf262dde8e853bc7e9b805c6007b4d4ac6e8015e50746c5971d09cb6e.wasm", + "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", + "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", + "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", + "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", + "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", + "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", + "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.72e987ee3e6ff3e73ee5f7fb73159d1c0326784f7a87942fa0fa6400c6155dcc.wasm", - "tx_withdraw.wasm": "tx_withdraw.5f69e193e458163d8a6adacc71e78598b3472dd0993ee448bc6d382ec16e23ca.wasm", - "vp_nft.wasm": "vp_nft.54473e621cce7870b3680204a3801968985680e4b0ef8195226556487e93fae0.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c52e6411032c0b54a71b9dcf92471d291298b7bc18843e3f4e05007d3bfa0023.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", + "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", + "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", - "vp_user.wasm": "vp_user.564f91ef8d0d76e695dc09976efa312434c6f952dcf82ca12fd52fc25f7015be.wasm" + "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" } \ No newline at end of file From 4e208f968eb58b2ab56d9dbf05dae90f0a7b99dd Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Oct 2022 16:46:22 +0200 Subject: [PATCH 1089/1995] [fix]: Some code review changes --- .../ledger/eth_bridge/storage/bridge_pool.rs | 108 ++++++------------ shared/src/ledger/storage/traits.rs | 4 +- 2 files changed, 37 insertions(+), 75 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 0a3afe7eda..e03d499af3 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -60,6 +60,8 @@ pub fn is_protected_storage(key: &Key) -> bool { } /// A simple Merkle tree for the Ethereum bridge pool +/// +/// Note that an empty tree has root [0u8; 20] by definition. #[derive( Debug, Default, Clone, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -67,13 +69,16 @@ pub struct BridgePoolTree { /// Root of the tree root: KeccakHash, /// The underlying storage, containing hashes of [`PendingTransfer`]s. - store: BTreeSet, + leaves: BTreeSet, } impl BridgePoolTree { /// Create a new merkle tree for the Ethereum bridge pool pub fn new(root: KeccakHash, store: BTreeSet) -> Self { - Self { root, store } + Self { + root, + leaves: store, + } } /// Parse the key to ensure it is of the correct type. @@ -81,7 +86,7 @@ impl BridgePoolTree { /// If it is, it can be converted to a hash. /// Checks if the hash is in the tree. pub fn contains_key(&self, key: &Key) -> Result { - Ok(self.store.contains(&Self::parse_key(key)?)) + Ok(self.leaves.contains(&Self::parse_key(key)?)) } /// Update the tree with a new value. @@ -90,7 +95,7 @@ impl BridgePoolTree { /// return an error if the key is malformed. pub fn insert_key(&mut self, key: &Key) -> Result { let hash = Self::parse_key(key)?; - _ = self.store.insert(hash); + _ = self.leaves.insert(hash); self.root = self.compute_root(); Ok(self.root()) } @@ -98,14 +103,14 @@ impl BridgePoolTree { /// Delete a key from storage and update the root pub fn delete_key(&mut self, key: &Key) -> Result<(), Error> { let hash = Self::parse_key(key)?; - _ = self.store.remove(&hash); + _ = self.leaves.remove(&hash); self.root = self.compute_root(); Ok(()) } /// Compute the root of the merkle tree fn compute_root(&self) -> KeccakHash { - let mut hashes: Vec = self.store.iter().cloned().collect(); + let mut hashes: Vec = self.leaves.iter().cloned().collect(); while hashes.len() > 1 { let mut next_hashes = vec![]; for pair in hashes.chunks(2) { @@ -130,48 +135,32 @@ impl BridgePoolTree { /// Get a reference to the backing store pub fn store(&self) -> &BTreeSet { - &self.store + &self.leaves } /// Create a batched membership proof for the provided keys pub fn get_membership_proof( &self, - keys: &[Key], mut values: Vec, ) -> Result { - if values.len() != keys.len() { - return Err(eyre!( - "The number of leaves and leaf hashes must be equal." - ) - .into()); - } // sort the values according to their hash values values.sort_by_key(|transfer| transfer.keccak256()); // get the leaf hashes - let mut leaves: BTreeSet = Default::default(); - for (key, value) in keys.iter().zip(values.iter()) { - let hash = Self::parse_key(key)?; - if hash != value.keccak256() { - return Err(eyre!( - "Hashes of keys did not match hashes of values." - ) - .into()); - } - leaves.insert(hash); - } + let leaves: BTreeSet = + values.iter().map(|v| v.keccak256()).collect(); let mut proof_hashes = vec![]; let mut flags = vec![]; let mut hashes: Vec<_> = self - .store + .leaves .iter() .cloned() .map(|hash| { if leaves.contains(&hash) { Node::OnPath(hash) } else { - Node::Sibling(hash) + Node::OffPath(hash) } }) .collect(); @@ -188,20 +177,20 @@ impl BridgePoolTree { next_hashes .push(Node::OnPath(hash_pair(left.clone(), right))); } - (Node::OnPath(hash), Node::Sibling(sib)) => { + (Node::OnPath(hash), Node::OffPath(sib)) => { flags.push(false); proof_hashes.push(sib.clone()); next_hashes .push(Node::OnPath(hash_pair(hash.clone(), sib))); } - (Node::Sibling(sib), Node::OnPath(hash)) => { + (Node::OffPath(sib), Node::OnPath(hash)) => { flags.push(false); proof_hashes.push(sib.clone()); next_hashes .push(Node::OnPath(hash_pair(hash, sib.clone()))); } - (Node::Sibling(left), Node::Sibling(right)) => { - next_hashes.push(Node::Sibling(hash_pair( + (Node::OffPath(left), Node::OffPath(right)) => { + next_hashes.push(Node::OffPath(hash_pair( left.clone(), right, ))); @@ -264,12 +253,12 @@ enum Node { /// Node is on a path from root to leaf in proof OnPath(KeccakHash), /// Node is not on a path from root to leaf in proof - Sibling(KeccakHash), + OffPath(KeccakHash), } impl Default for Node { fn default() -> Self { - Self::Sibling(Default::default()) + Self::OffPath(Default::default()) } } @@ -279,7 +268,9 @@ pub struct BridgePoolProof { pub proof: Vec, /// The leaves; must be sorted pub leaves: Vec, - /// flags to indicate how to combine hashes + /// Flags to indicate how to combine hashes. + /// Flags are used to indicate which consecutive + /// pairs of leaves in `leaves` are siblings. pub flags: Vec, } @@ -345,7 +336,6 @@ impl BridgePoolProof { #[cfg(test)] mod test_bridge_pool_tree { - use std::array; use itertools::Itertools; use proptest::prelude::*; @@ -436,7 +426,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let hashes: BTreeSet = transfers.iter().map(|t| t.keccak256()).collect(); - assert_eq!(hashes, tree.store); + assert_eq!(hashes, tree.leaves); let left_hash = hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); @@ -605,11 +595,8 @@ mod test_bridge_pool_tree { #[test] fn test_empty_proof() { let tree = BridgePoolTree::default(); - let keys = vec![]; let values = vec![]; - let proof = tree - .get_membership_proof(&keys, values) - .expect("Test failed"); + let proof = tree.get_membership_proof(values).expect("Test failed"); assert!(proof.verify(Default::default())); } @@ -632,7 +619,7 @@ mod test_bridge_pool_tree { let key = Key::from(&transfer); let _ = tree.insert_key(&key).expect("Test failed"); let proof = tree - .get_membership_proof(array::from_ref(&key), vec![transfer]) + .get_membership_proof(vec![transfer]) .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -661,12 +648,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let key = Key::from(&transfers[0]); let proof = tree - .get_membership_proof( - array::from_ref(&key), - vec![transfers.remove(0)], - ) + .get_membership_proof(vec![transfers.remove(0)]) .expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -695,11 +678,8 @@ mod test_bridge_pool_tree { let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys = vec![Key::from(&transfers[0]), Key::from(&transfers[1])]; let values = vec![transfers[0].clone(), transfers[1].clone()]; - let proof = tree - .get_membership_proof(&keys, values) - .expect("Test failed"); + let proof = tree.get_membership_proof(values).expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -725,11 +705,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let keys = vec![]; let values = vec![]; - let proof = tree - .get_membership_proof(&keys, values) - .expect("Test failed"); + let proof = tree.get_membership_proof(values).expect("Test failed"); assert!(proof.verify(tree.root().into())) } @@ -756,10 +733,7 @@ mod test_bridge_pool_tree { let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree - .get_membership_proof(&keys, transfers) - .expect("Test failed"); + let proof = tree.get_membership_proof(transfers).expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -786,10 +760,7 @@ mod test_bridge_pool_tree { let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys: Vec<_> = transfers.iter().map(Key::from).collect(); - let proof = tree - .get_membership_proof(&keys, transfers) - .expect("Test failed"); + let proof = tree.get_membership_proof(transfers).expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -816,11 +787,8 @@ mod test_bridge_pool_tree { let _ = tree.insert_key(&key).expect("Test failed"); } transfers.sort_by_key(|t| t.keccak256()); - let keys: Vec<_> = transfers.iter().step_by(2).map(Key::from).collect(); let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); - let proof = tree - .get_membership_proof(&keys, values) - .expect("Test failed"); + let proof = tree.get_membership_proof(values).expect("Test failed"); assert!(proof.verify(tree.root().into())); } @@ -881,13 +849,7 @@ mod test_bridge_pool_tree { } to_prove.sort_by_key(|t| t.keccak256()); - let mut keys = vec![]; - let mut values = vec![]; - for transfer in to_prove.into_iter() { - keys.push(Key::from(&transfer)); - values.push(transfer); - } - let proof = tree.get_membership_proof(&keys, values).expect("Test failed"); + let proof = tree.get_membership_proof(to_prove).expect("Test failed"); assert!(proof.verify(tree.root().into())); } } diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 67d77ac991..ee9805a8ee 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -173,7 +173,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { fn subtree_membership_proof( &self, - keys: &[Key], + _: &[Key], values: Vec, ) -> Result { let values = values @@ -183,7 +183,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { _ => None, }) .collect(); - self.get_membership_proof(keys, values) + self.get_membership_proof(values) .map(Into::into) .map_err(|err| Error::MerkleTree(err.to_string())) } From 2bdc99af57a52432b0c0313ed87af5ab30f5b057 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1090/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 0624a83ae1..f558c2d2dc 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -232,8 +215,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -244,9 +227,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -256,9 +237,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index e03d499af3..24e31a2791 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool /// /// Note that an empty tree has root [0u8; 20] by definition. diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index be15012cfa..78e54a154a 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 4c3e0bfc43b95d355660c8988cbc68d028a24d90 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1091/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 +++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - 2 files changed, 124 insertions(+), 88 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index f558c2d2dc..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -272,19 +271,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -340,10 +341,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -360,10 +358,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -372,13 +372,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -389,13 +391,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -406,13 +410,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -423,13 +429,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -440,58 +448,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -502,13 +535,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -539,11 +577,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 24e31a2791..0ec98b31b6 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; From 3a97f866c334aadea2aac79c19b4b74fb35b63b5 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 1092/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From ae75f07eb6a2d687b674bfcbd6f2cebd63608b8a Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1093/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 +++++++++------ shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..97aa273088 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -116,11 +117,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { - if *key != pending_key { + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." ); return Ok(false); } @@ -131,7 +132,9 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool." + ); return Ok(false); } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 78e54a154a..4cb6d91f69 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -657,6 +657,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 54df9ce5c98cd717fc9005351d52cf9f19f3465c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1094/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 97aa273088..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -117,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -132,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } From e45b6cf28fcf1632a96efe40365d5b1d6c10cf16 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 16:06:12 +0200 Subject: [PATCH 1095/1995] [fix]: Added some more logging --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -120,7 +120,10 @@ where if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + incorrect key in the pending transaction pool: {}.\n \ + Expected key: {}", + key, + pending_key ); return Ok(false); } @@ -131,7 +134,12 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool: {:?}.\n \ + Expected: {:?}", + transfer, + pending + ); return Ok(false); } From 34aa825f8594e94aaa8cf8151df17bf80c692fc6 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1096/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..117c9e61ef 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 4cb6d91f69..655c7be0ed 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -673,6 +673,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From f7c97441bf176784a0617a5b4f7ec4ccdcff128d Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1097/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 117c9e61ef..47aa3b7968 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -466,6 +465,15 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } From 8156bf06412a11c80102be9ee0eff74b6a288f8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Oct 2022 15:33:09 +0000 Subject: [PATCH 1098/1995] [ci] wasm checksums update --- wasm/checksums.json | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7a6840ef5f..850b41f1b8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.870a6cd86c7725df65306a4ef49ae05f39ac5d2f7c39bf12c901def38587c016.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.f14bd1cf1bc9adce29ff5161d36cade38f75db58daaab0ac4446aeb3752ca491.wasm", - "tx_ibc.wasm": "tx_ibc.d88b6ea37611c9e82d012b468475a2dfe125d5f0ecf8162ee361a2e07ac48a7c.wasm", - "tx_init_account.wasm": "tx_init_account.1424aad69e094e6ddbbcc41c28484675d2ce181699d5820b365cade406a33e3a.wasm", - "tx_init_nft.wasm": "tx_init_nft.b6d193bf41c5b105de075ed832ed9ac5bb0ee03943c0f70ace1888474f50ece7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.951e3667382e0d81de17ebda9248046a8f1cc2d7e714b0cfa7ae58920c0d02d6.wasm", - "tx_init_validator.wasm": "tx_init_validator.d9ec814be773d6bfd6c36ddb37ace2463effef02d7e089495c3f74de43de556f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.90b8cf9d1f52b758ae419cefaaa51b270d7492877348ea02357d589f83695099.wasm", - "tx_transfer.wasm": "tx_transfer.ad9707d3e02d01b261675fdb09eab6aedeebea0b8035726df76f68c7279b864b.wasm", - "tx_unbond.wasm": "tx_unbond.f73b997890f6ad61289fdfa01770940bc26e28bf06a8a2e856d2615acdcbe6ae.wasm", + "tx_from_intent.wasm": "tx_from_intent.7e9893f8e2affcfbb4e04fe936bb18571645353da08d8e08d880971f2cfca053.wasm", + "tx_ibc.wasm": "tx_ibc.29d0881d94f45a2fd67bb0f8cbc432a4c2c18ce96c1e17712b721a3ae04788d2.wasm", + "tx_init_account.wasm": "tx_init_account.fff73dad57c9f1f670593939b4a0a895ed538324a9a39958e3724a51b1fc1524.wasm", + "tx_init_nft.wasm": "tx_init_nft.378007b60d8324df95f0db09b568b8924b411d6402a76e0280ce5bb95835dff2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.59c9755b31a9727c7b5908c2c404f193b9de7ffac919833c342e06e4bca31953.wasm", + "tx_init_validator.wasm": "tx_init_validator.34b7afb1af8f7b23c00812e969f89058754c8447704a9cd1acd595281c58d16a.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.7962f00c4735a88cae4363ca172e6bbe1cd0b75c7271b5203b141e29a2d039ff.wasm", + "tx_transfer.wasm": "tx_transfer.e818885f13cbe1349c8702fa848991263b4032cae6c2f68df75cf9e4a7d4b4db.wasm", + "tx_unbond.wasm": "tx_unbond.1e3a5e7e5e85cf6222af2255e8bea9658693758473aba1022304c4b5af4d7f78.wasm", "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.25bb52d647106263537467e229ddbc1ef57970c222d49830d1bc182727afe4f4.wasm", - "tx_withdraw.wasm": "tx_withdraw.ebe91941b7f8559e651ceeba786d5cf08fba7ee17d2991ef460cbf65e6501664.wasm", - "vp_nft.wasm": "vp_nft.e8bcdf806148418e765aad6fa509993c490a7c2fb7cbc9abe8e469babd371e05.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d210846a57a8290b726689c50d8002ce374e54c5af08ca7b4edc852832cd42d8.wasm", - "vp_token.wasm": "vp_token.e2d4cebc4c592ec68a452e1ef55a6bb6b16bd0cf7d77ec3527b4cde82d243251.wasm", - "vp_user.wasm": "vp_user.856857cea31ce03e076dcb1a9e5d2a9cf30d3c883365e7230f0c8fa1d4c1dc8a.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.d61eb971b9e8eab8d8d2ddf5ff238f2de769bc227cbcb14beb6e36462102ff36.wasm", + "tx_withdraw.wasm": "tx_withdraw.76c8c25e235a9e351796caef61a783e15e289f0a8b8ebdfba6ffefb027ec4f68.wasm", + "vp_nft.wasm": "vp_nft.acd5da5193e1d02caf3a278ea88eaa22943cd280e9c84a8d381217399c8bdabf.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.cc2633219e7fe4a807fb6e25ae8c7248b35dcea239dd6b108293bfee26d3a512.wasm", + "vp_token.wasm": "vp_token.cee6c283ec93ecf78d473cf382b2ec78a891d705fa48ca5c7393c61b74bb2aa7.wasm", + "vp_user.wasm": "vp_user.62b8491c45cb9fb1b2b4f4d4590a119ba34fcca8d63539ed2717c3bef365aff1.wasm" } \ No newline at end of file From e5246eb492eede797b67ed675738bba4d35ca575 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1099/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 + shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 47aa3b7968..c3b05e4e6f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,6 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 655c7be0ed..146d5cfb04 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -689,6 +689,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 87dd95fcf2944f435c5fa54de9b92e53dcffa5e8 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1100/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c3b05e4e6f..2fbce42f60 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -466,15 +466,6 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, - |transfer, log| { - log.write( - &get_pending_key(&transfer), - transfer.try_to_vec().unwrap(), - ) - .unwrap(); - BTreeSet::from([get_pending_key(&transfer)]) - }, - Expect::False, ); } From db35c60005b2c16f3773c03e3feef7bd66a27d9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:32:58 +0000 Subject: [PATCH 1101/1995] [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 850b41f1b8..73038833d5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7e9893f8e2affcfbb4e04fe936bb18571645353da08d8e08d880971f2cfca053.wasm", - "tx_ibc.wasm": "tx_ibc.29d0881d94f45a2fd67bb0f8cbc432a4c2c18ce96c1e17712b721a3ae04788d2.wasm", - "tx_init_account.wasm": "tx_init_account.fff73dad57c9f1f670593939b4a0a895ed538324a9a39958e3724a51b1fc1524.wasm", - "tx_init_nft.wasm": "tx_init_nft.378007b60d8324df95f0db09b568b8924b411d6402a76e0280ce5bb95835dff2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.59c9755b31a9727c7b5908c2c404f193b9de7ffac919833c342e06e4bca31953.wasm", - "tx_init_validator.wasm": "tx_init_validator.34b7afb1af8f7b23c00812e969f89058754c8447704a9cd1acd595281c58d16a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.7962f00c4735a88cae4363ca172e6bbe1cd0b75c7271b5203b141e29a2d039ff.wasm", - "tx_transfer.wasm": "tx_transfer.e818885f13cbe1349c8702fa848991263b4032cae6c2f68df75cf9e4a7d4b4db.wasm", - "tx_unbond.wasm": "tx_unbond.1e3a5e7e5e85cf6222af2255e8bea9658693758473aba1022304c4b5af4d7f78.wasm", - "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d61eb971b9e8eab8d8d2ddf5ff238f2de769bc227cbcb14beb6e36462102ff36.wasm", - "tx_withdraw.wasm": "tx_withdraw.76c8c25e235a9e351796caef61a783e15e289f0a8b8ebdfba6ffefb027ec4f68.wasm", - "vp_nft.wasm": "vp_nft.acd5da5193e1d02caf3a278ea88eaa22943cd280e9c84a8d381217399c8bdabf.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.cc2633219e7fe4a807fb6e25ae8c7248b35dcea239dd6b108293bfee26d3a512.wasm", - "vp_token.wasm": "vp_token.cee6c283ec93ecf78d473cf382b2ec78a891d705fa48ca5c7393c61b74bb2aa7.wasm", - "vp_user.wasm": "vp_user.62b8491c45cb9fb1b2b4f4d4590a119ba34fcca8d63539ed2717c3bef365aff1.wasm" + "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", + "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", + "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", + "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", + "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", + "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", + "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", + "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", + "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", + "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" } \ No newline at end of file From 297ae10b63d69f80beecfe2338127c24d96517f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Oct 2022 08:33:01 +0000 Subject: [PATCH 1102/1995] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 73038833d5..4754de3ff7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.d5fc1b1c43e88ebdb60da385d2c4d0561f01cd40f4dacdc7c87ed7c8028679cf.wasm", - "tx_ibc.wasm": "tx_ibc.e97671a3584072c854d0a3638adcd87370b3a15e0084f1721babe6169a325499.wasm", - "tx_init_account.wasm": "tx_init_account.cc60e72a9f3e010793f2255320559f8508a8f41f699012a15e7e2ad7b9b91e8d.wasm", - "tx_init_nft.wasm": "tx_init_nft.fc4846b333cde985a0435ece4c67cf4720fc690f0b691d7c47750164dc25b7f7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.5e440b0dd0087fd8a0ffa41725038e66ecf5f6932d013faf73550ae95d598db4.wasm", - "tx_init_validator.wasm": "tx_init_validator.6088e7415f6d346ef15533ef2b9116141707e63a08dc734a22847b972fa61d10.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.0ec77f549ed90b9e5c1b03e6697835250ff426c20c09904c28e37b27930cc9f9.wasm", - "tx_transfer.wasm": "tx_transfer.855a118f90703815f72425f162c0beba543870db2768a2e2855ce29b508c6594.wasm", - "tx_unbond.wasm": "tx_unbond.8c24da4d52ae3bb513ac08889b1b7f10fa3a26271713cca4f758a5a72f0f7fa9.wasm", + "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", + "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", + "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", + "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", + "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", + "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", + "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3d0433598d600227b936caadd1e2e9c7cf7e1989225cc5d2660a1f8bc0d00e09.wasm", - "tx_withdraw.wasm": "tx_withdraw.dfdee0959a74df3335c2e262fd18030e4ce5dff862fc98ab4c9a461ccb2a4dd3.wasm", - "vp_nft.wasm": "vp_nft.aea9aa6952d86799992c8374f568b589a48750f32ce3f19c8807281307204a6d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.64bc3bc9a5f8d03204aa834d2105b4a91fee65e685a5672ff4a203df5eff44ad.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", + "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", + "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.b7daaa56f48b385f936b2ff907b85298ee1aa8d422ef6fd9df3c044ee7fdadbf.wasm" + "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" } \ No newline at end of file From caf09061813fa52473f643c93ac613edb6507455 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 15:40:29 +0200 Subject: [PATCH 1103/1995] [fix]: Fixed some garbage created by rebasing --- shared/src/types/storage.rs | 39 +++---------------------------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 146d5cfb04..9fa5b65128 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,7 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::keccak::KeccakHash; +use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -643,41 +643,8 @@ impl KeySeg for Hash { impl KeySeg for KeccakHash { fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) + seg.try_into() + .map_err(|e: TryFromError| Error::ParseError(e.to_string())) } fn raw(&self) -> String { From 6c87b7e84a1b11445786b54b57762daca0708bb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Oct 2022 08:39:54 +0000 Subject: [PATCH 1104/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 4754de3ff7..5f5b21643b 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,20 @@ { + "tx_bond.wasm": "tx_bond.730e12ff5cebf2e8bdb23882a0e3ab9ed20df6bfe20e2d6b3578bb474c97a04d.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.658fa18092d794db18c0084779568411c4c1b7c8d163b78c5338d6016a75d6c6.wasm", - "tx_ibc.wasm": "tx_ibc.5188c4ff27f8823b672e7e85844208893332a9f47adbbff08a51bc7694e9bab0.wasm", - "tx_init_account.wasm": "tx_init_account.159d8afce1e760d1e4bbb0a699dfe1e8543074238523964c69ac9ea6b9ac8d9a.wasm", - "tx_init_nft.wasm": "tx_init_nft.6ee23f0cff039a81a2fdbb5ca4bdc332aee2bc96f205bf55f35e3d04f16bffd5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.cc839bb461efe6d0ea9337d6e2d8698d829c2c6b828d46dbb9e9deed1977337d.wasm", - "tx_init_validator.wasm": "tx_init_validator.7b230affa897b6dca91915d8d9486204edb8c3b2d0a9fa72277d06d3085887e7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.87767ccb2483d75f195470dbd3b961132e85a1aeb9bce55a10d24e4d11dfbfba.wasm", - "tx_transfer.wasm": "tx_transfer.d7cb47c03c3036f053c99c9cbb467e3e4e3ca80c9ffa5f893e0737174423bdb8.wasm", - "tx_unbond.wasm": "tx_unbond.f5bc85a36a9b26a0290404362997428de321f34098e42b1e37876b87d2bed965.wasm", - "tx_update_vp.wasm": "tx_update_vp.a7b0c9370d60d4168a198af66ed5e3ce094e250e566bdff23ec0fbb2236d576e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8d4e8653c40e703c2c4ad50eb1d3e0e5d40a5ed8e4ec83d37ff13b2b102f0390.wasm", - "tx_withdraw.wasm": "tx_withdraw.3abc393edcd9ac5b7ea0a4f086d339adf31c0a16bd8c267a0bce7dd66a976d59.wasm", - "vp_nft.wasm": "vp_nft.c1c9e2e806ec23da24b404d5124dd70e94a4eb9d7176d0893a6c06b564817b12.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a4c1c679ea003bd2b39293cddfc3db58b0d68ed3609aed4b5b0c7010cb6d235d.wasm", - "vp_token.wasm": "vp_token.a08656587dcaa572872f3757d05f30494b4add02d821c0101b4244a1068bfae2.wasm", - "vp_user.wasm": "vp_user.cbcfe75a037fe36e192f841a8b65f8f81dab2fac61d57f08d0a6bafbc0e2b016.wasm" + "tx_from_intent.wasm": "tx_from_intent.f3add97540ff97ee0e7af21c8b628f8cd6221d2c7150354f9a33fbda4f7da85a.wasm", + "tx_ibc.wasm": "tx_ibc.49f1e97533207af795362dadfc5be7ce6c119a51830707ee12ab14e3eb5e60ba.wasm", + "tx_init_account.wasm": "tx_init_account.5eedb88e7fa75066fbaeaf79a7b63f78346ce5c55ad76325ca7b2ee786cb124d.wasm", + "tx_init_nft.wasm": "tx_init_nft.f4b14f460478f6ab5268a431948da24add566188129b0297a39230e86a945b38.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ed94170605b1b1b6e0440d2235d25598f1c6976a4a0dbf3d90a1e0154f815740.wasm", + "tx_init_validator.wasm": "tx_init_validator.8db2f2f7b180310591b8163b5db9494e80b59b66bcea72f71d8af09ec8c6da0d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.deca7087433d476e273094c5017e3b83534d7c8f576c35561f05ab48aeffdea8.wasm", + "tx_transfer.wasm": "tx_transfer.a7f182f0b1398a1da79e8a74d41b70d2b0e39329b1598f9774fb99e46791c895.wasm", + "tx_unbond.wasm": "tx_unbond.7fedc39de1b6f197de757563b142ad43dfe280234df7f0f0434f083419fd7476.wasm", + "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.af655290e235244a73d7fe16211d790bb45c08c903be40726bb806f84ae2bd2f.wasm", + "tx_withdraw.wasm": "tx_withdraw.9ecd4a4b8b037b79900605f946791677236b7544ea0fa41890d0a3f9bc264a68.wasm", + "vp_nft.wasm": "vp_nft.a7a93adb21605e532bef10274c580d5b8d11c8f49298f6c08ad2e1cd22db5b89.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8c7c23b7dc72c9b4ecaf1602989431d6338111f730ad85f93b80dfd6a7806417.wasm", + "vp_token.wasm": "vp_token.e2d4cebc4c592ec68a452e1ef55a6bb6b16bd0cf7d77ec3527b4cde82d243251.wasm", + "vp_user.wasm": "vp_user.0e094c576229c2f72d39cdf1ec4a65d35c76dc5823ff4ea2537d22f8c7856977.wasm" } \ No newline at end of file From c9ebd1efbec278235b4d63cf09021aea0732a89a Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Oct 2022 17:12:09 +0200 Subject: [PATCH 1105/1995] [chore]: Rebased onto PR #573 --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 - shared/src/types/storage.rs | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 2fbce42f60..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,6 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 9fa5b65128..eee9b5b847 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,22 +656,6 @@ impl KeySeg for KeccakHash { } } -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 3c0be07ba1fc5a18f00792691cb7c08a900c365d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 24 Oct 2022 15:17:09 +0000 Subject: [PATCH 1106/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7a6840ef5f..a320c8c877 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.870a6cd86c7725df65306a4ef49ae05f39ac5d2f7c39bf12c901def38587c016.wasm", + "tx_bond.wasm": "tx_bond.921a2553de5b15305df8708f7bfba49d8174d0a1ff5eaded66fc9e47da8e16ea.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.f14bd1cf1bc9adce29ff5161d36cade38f75db58daaab0ac4446aeb3752ca491.wasm", - "tx_ibc.wasm": "tx_ibc.d88b6ea37611c9e82d012b468475a2dfe125d5f0ecf8162ee361a2e07ac48a7c.wasm", - "tx_init_account.wasm": "tx_init_account.1424aad69e094e6ddbbcc41c28484675d2ce181699d5820b365cade406a33e3a.wasm", - "tx_init_nft.wasm": "tx_init_nft.b6d193bf41c5b105de075ed832ed9ac5bb0ee03943c0f70ace1888474f50ece7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.951e3667382e0d81de17ebda9248046a8f1cc2d7e714b0cfa7ae58920c0d02d6.wasm", - "tx_init_validator.wasm": "tx_init_validator.d9ec814be773d6bfd6c36ddb37ace2463effef02d7e089495c3f74de43de556f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.90b8cf9d1f52b758ae419cefaaa51b270d7492877348ea02357d589f83695099.wasm", - "tx_transfer.wasm": "tx_transfer.ad9707d3e02d01b261675fdb09eab6aedeebea0b8035726df76f68c7279b864b.wasm", - "tx_unbond.wasm": "tx_unbond.f73b997890f6ad61289fdfa01770940bc26e28bf06a8a2e856d2615acdcbe6ae.wasm", - "tx_update_vp.wasm": "tx_update_vp.4e5d802da0ef98daa6c904522c6fd4964020cc9ce948f16579a6cec1793337b8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.25bb52d647106263537467e229ddbc1ef57970c222d49830d1bc182727afe4f4.wasm", - "tx_withdraw.wasm": "tx_withdraw.ebe91941b7f8559e651ceeba786d5cf08fba7ee17d2991ef460cbf65e6501664.wasm", - "vp_nft.wasm": "vp_nft.e8bcdf806148418e765aad6fa509993c490a7c2fb7cbc9abe8e469babd371e05.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d210846a57a8290b726689c50d8002ce374e54c5af08ca7b4edc852832cd42d8.wasm", - "vp_token.wasm": "vp_token.e2d4cebc4c592ec68a452e1ef55a6bb6b16bd0cf7d77ec3527b4cde82d243251.wasm", - "vp_user.wasm": "vp_user.856857cea31ce03e076dcb1a9e5d2a9cf30d3c883365e7230f0c8fa1d4c1dc8a.wasm" + "tx_from_intent.wasm": "tx_from_intent.f3dc53d17cf0e698fcaab3c07039d90bf5dec9afc097ee3bc409427162ade54e.wasm", + "tx_ibc.wasm": "tx_ibc.1137cd5dfb5f7b1d2101f36d69c73f42c36d463683eb3f6db05293a78a05d31c.wasm", + "tx_init_account.wasm": "tx_init_account.184180f1b0f798b7929873165e9aaa0b9027bc122d3d7f66b8a5b9663eb886d5.wasm", + "tx_init_nft.wasm": "tx_init_nft.6fe64086099e6175df889e5c068bd5038d6f666288f6e97af0009aa11e4270b0.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.48c78758fdcb20b20405be265ec0a18224b48e99ffb08651543db1f972761416.wasm", + "tx_init_validator.wasm": "tx_init_validator.610a72775cbdd0b5e34e71eca29545acf5dcf6b16ab887e778d75d8fc5ab086b.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.3e67ffb6b3c974df2c74479ad49d97b6e8301ef7b369dceeb02223c771a4bf65.wasm", + "tx_transfer.wasm": "tx_transfer.c31c6f1b912a5cf161c2b2623044a27b2b6ee37ef226a0ae3c9b6ec75e95d9a9.wasm", + "tx_unbond.wasm": "tx_unbond.247505c2aee9e7fabf928f436f9220f6a09220d0276c6cdab30eae10baededbb.wasm", + "tx_update_vp.wasm": "tx_update_vp.308c3066cc935b84ddb6e884008acacedaca6cb2fd302530fde0faa712672f73.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8e28f8c76eaebc1302247e34bc9c7f2744495dc802934cfb309b825c8e5d31d4.wasm", + "tx_withdraw.wasm": "tx_withdraw.8ce1e6063155e2876f1c2044c9f09d643b56a55a2b9be95cf41452b4ebdfb20c.wasm", + "vp_nft.wasm": "vp_nft.957258593d899af1d6368afdefb6f158a65da28c6656a2b901c5cf97dd1f23e4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.eaad3bffc9b20a831db37d18c82930e037ea079e7152b7e683eff8b98dacd7bf.wasm", + "vp_token.wasm": "vp_token.a4cd361ad211e05c691eab70127813e348216f6a793c2566a1f5ee2f88256596.wasm", + "vp_user.wasm": "vp_user.76d5d421abf1a78c55045ba905c8330cee12cbddf03c01f41bb46b7b24bbb1a3.wasm" } \ No newline at end of file From 28b0a4fdbf9f56af948f31de2487e0744756ad6e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1107/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 31 +++- apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 62 +++++--- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 54 ++++++- 7 files changed, 287 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 4cbedca8bc..25816cc426 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -17,7 +17,10 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block. Epoch, - /// Read a storage value with exact storage key. + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), + /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix. Prefix(storage::Key), @@ -29,6 +32,16 @@ pub enum Path { Applied { tx_hash: Hash }, } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -39,6 +52,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -59,6 +75,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } Path::Accepted { tx_hash } => { write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } @@ -77,6 +100,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9203d4f3e8..9208f32f2d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,22 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -21,6 +30,7 @@ use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::events::log::dumb_queries; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -87,6 +97,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, Path::Accepted { tx_hash } => { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) @@ -337,6 +355,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 72c20f2046..b3c1543b93 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -88,7 +89,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.leaves.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -119,9 +120,9 @@ impl BridgePoolTree { } } - /// Return the root as a [struct@`Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + /// Return the root as a [`struct@Hash`] type. + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store @@ -325,6 +326,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { @@ -386,9 +412,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -423,7 +448,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -479,9 +504,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -612,7 +636,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -642,7 +666,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfers.remove(0)]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -671,7 +695,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values = vec![transfers[0].clone(), transfers[1].clone()]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -698,7 +722,7 @@ mod test_bridge_pool_tree { } let values = vec![]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -725,7 +749,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -752,7 +776,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -780,7 +804,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -841,7 +865,7 @@ mod test_bridge_pool_tree { to_prove.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(to_prove).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 3add9dd7f0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index c2464c52d1..12ccd198a5 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -21,7 +21,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index ee9805a8ee..5ff9f7bb59 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 904c2c7eec..4c5c5496a3 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,10 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -56,13 +58,15 @@ pub struct PendingTransfer { impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, fee_from, nonce] + vec![version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -96,3 +100,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From 8dfae96b615b84860a34d47133627e8cc7755351 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1108/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 7 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 9 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 8 files changed, 235 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f18113ce..f038bdb2e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4328,6 +4328,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9208f32f2d..d53ca1fd55 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,13 +5,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -20,7 +20,8 @@ use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -364,13 +365,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -420,12 +433,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -438,22 +446,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -468,6 +468,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -869,10 +870,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1047,4 +1058,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..7c2cfbec2b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,6 +104,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index b3c1543b93..eb90967ed8 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 12ccd198a5..8cc6fe6dcf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -430,15 +430,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl Into, ) -> Result<(u64, i64)> { + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); self.block.tree.update(key, value.clone())?; - let len = value.as_ref().len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4c5c5496a3..ebdce1d15f 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -43,6 +44,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -89,6 +91,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index eee9b5b847..50df74286c 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -245,6 +245,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -267,6 +268,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From e07024d55a130701f392932bad5578cfe0019e82 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1109/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/mod.rs | 13 ++++- apps/src/lib/node/ledger/shell/queries.rs | 18 +++---- shared/Cargo.toml | 1 - shared/src/types/key/secp256k1.rs | 60 ++++++++++++++++------- 5 files changed, 61 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f038bdb2e6..c2f18113ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4328,7 +4328,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4f6bdeedbe..f926d940b8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -874,10 +874,19 @@ mod test_utils { sig_bytes[0] = sig_bytes[0].wrapping_add(1); common::Signature::Ed25519(ed25519::Signature(sig_bytes.into())) } - common::Signature::Secp256k1(secp256k1::Signature(ref sig)) => { + common::Signature::Secp256k1(secp256k1::Signature( + ref sig, + ref recovery_id, + )) => { let mut sig_bytes = sig.serialize(); + let recovery_id_bytes = recovery_id.serialize(); sig_bytes[0] = sig_bytes[0].wrapping_add(1); - common::Signature::Secp256k1((&sig_bytes).try_into().unwrap()) + let bytes: [u8; 65] = + [sig_bytes.as_slice(), [recovery_id_bytes].as_slice()] + .concat() + .try_into() + .unwrap(); + common::Signature::Secp256k1((&bytes).try_into().unwrap()) } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d53ca1fd55..0d384d50cd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,6 +16,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; @@ -1083,7 +1084,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1120,7 +1121,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1128,7 +1129,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1136,7 +1137,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1180,7 +1181,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1194,14 +1195,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 7c2cfbec2b..ccb4a3752d 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,7 +104,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 96b892fdbf..7ab6172774 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -266,7 +267,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(pub libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature, pub RecoveryId); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; @@ -304,6 +305,7 @@ impl Serialize for Signature { for elem in &arr[..] { seq.serialize_element(elem)?; } + seq.serialize_element(&self.1.serialize())?; seq.end() } } @@ -316,7 +318,7 @@ impl<'de> Deserialize<'de> for Signature { struct ByteArrayVisitor; impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( @@ -325,13 +327,13 @@ impl<'de> Deserialize<'de> for Signature { )) } - fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + fn visit_seq(self, mut seq: A) -> Result<[u8; 65], A::Error> where A: SeqAccess<'de>, { - let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE + 1]; #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + for i in 0..libsecp256k1::util::SIGNATURE_SIZE + 1 { arr[i] = seq .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; @@ -341,23 +343,34 @@ impl<'de> Deserialize<'de> for Signature { } let arr_res = deserializer.deserialize_tuple( - libsecp256k1::util::SIGNATURE_SIZE, + libsecp256k1::util::SIGNATURE_SIZE + 1, ByteArrayVisitor, )?; - let sig = libsecp256k1::Signature::parse_standard(&arr_res) + let sig_array: [u8; 64] = arr_res[..64].try_into().unwrap(); + let sig = libsecp256k1::Signature::parse_standard(&sig_array) .map_err(D::Error::custom); - Ok(Signature(sig.unwrap())) + Ok(Signature( + sig.unwrap(), + RecoveryId::parse(arr_res[64]).map_err(Error::custom)?, + )) } } impl BorshDeserialize for Signature { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first + let (sig_bytes, recovery_id) = BorshDeserialize::deserialize(buf)?; + Ok(Signature( - libsecp256k1::Signature::parse_standard( - &(BorshDeserialize::deserialize(buf)?), - ) - .map_err(|e| { + libsecp256k1::Signature::parse_standard(&sig_bytes).map_err( + |e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + }, + )?, + RecoveryId::parse(recovery_id).map_err(|e| { std::io::Error::new( ErrorKind::InvalidInput, format!("Error decoding secp256k1 signature: {}", e), @@ -369,7 +382,10 @@ impl BorshDeserialize for Signature { impl BorshSerialize for Signature { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - BorshSerialize::serialize(&self.0.serialize(), writer) + BorshSerialize::serialize( + &(self.0.serialize(), self.1.serialize()), + writer, + ) } } @@ -405,12 +421,19 @@ impl PartialOrd for Signature { } } -impl TryFrom<&[u8; 64]> for Signature { +impl TryFrom<&[u8; 65]> for Signature { type Error = ParseSignatureError; - fn try_from(sig: &[u8; 64]) -> Result { - libsecp256k1::Signature::parse_standard(sig) - .map(Self) + fn try_from(sig: &[u8; 65]) -> Result { + let sig_bytes = sig[..64].try_into().unwrap(); + let recovery_id = RecoveryId::parse(sig[64]).map_err(|err| { + ParseSignatureError::InvalidEncoding(std::io::Error::new( + ErrorKind::Other, + err, + )) + })?; + libsecp256k1::Signature::parse_standard(&sig_bytes) + .map(|sig| Self(sig, recovery_id)) .map_err(|err| { ParseSignatureError::InvalidEncoding(std::io::Error::new( ErrorKind::Other, @@ -467,7 +490,8 @@ impl super::SigScheme for SigScheme { 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) + let (sig, recovery_id) = libsecp256k1::sign(&message, &keypair.0); + Signature(sig, recovery_id) } } From c7b8782a39bfb8c22ca8610cb529391a321d744b Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 15:20:59 +0200 Subject: [PATCH 1110/1995] [feat]: Fixed secp256k key signatures --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 13 ++++++++---- shared/src/types/key/dkg_session_keys.rs | 7 ++++-- shared/src/types/key/ed25519.rs | 13 ++++++++---- shared/src/types/key/mod.rs | 27 +++++++++++++++++++++--- shared/src/types/key/secp256k1.rs | 27 +++++++++++++++++------- wasm/tx_template/Cargo.lock | 7 ++++++ wasm/vp_template/Cargo.lock | 7 ++++++ wasm/wasm_source/Cargo.lock | 7 ++++++ wasm_for_tests/wasm_source/Cargo.lock | 7 ++++++ 11 files changed, 96 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2f18113ce..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4301,6 +4301,7 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index ccb4a3752d..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -76,6 +76,7 @@ borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 59196e1ff0..633367053c 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -5,6 +5,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -70,7 +71,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -78,7 +79,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -196,7 +199,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -204,7 +207,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index dbcf9fe04c..052461de9a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -106,7 +107,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -114,7 +115,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -203,7 +206,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -211,7 +214,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1c5ac85558..5a16838455 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -7,6 +7,7 @@ use std::hash::Hash; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -85,7 +86,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -96,7 +97,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -107,7 +108,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] @@ -356,6 +357,26 @@ 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 + } + common::PublicKey::Secp256k1(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + +/// 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 { + HEXUPPER.encode(raw_hash.as_ref()) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 7ab6172774..9c8825dd98 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -115,7 +116,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -123,7 +124,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -245,7 +248,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -253,7 +256,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } @@ -396,10 +401,16 @@ impl BorshSchema for Signature { 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 }; + // Encoded as `([u8; SIGNATURE_SIZE], u8)` + let signature = + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::declaration(); + <[u8; libsecp256k1::util::SIGNATURE_SIZE]>::add_definitions_recursively( + definitions, + ); + let recovery = "u8".into(); + let definition = borsh::schema::Definition::Tuple { + elements: vec![signature, recovery], + }; definitions.insert(Self::declaration(), definition); } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 64bad284a2..b59950f447 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3a3058df34..392c010281 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1393,6 +1399,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 2b891bf4e1..4905239cb0 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -619,6 +619,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1402,6 +1408,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index f0cabb9569..56e8a94bfb 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -620,6 +620,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1403,6 +1409,7 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ethabi", From 3562f968c6afadf562929c36e95c5245748d4e9a Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 1111/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0d384d50cd..e097e244ec 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -401,7 +401,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1225,4 +1225,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } From 86ff5026d4a6cd2176ce997e6b3e37bd18158513 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1112/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 14 +++++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e097e244ec..3f9026c5fc 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -459,6 +459,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1221,6 +1223,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1263,7 +1267,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index eb90967ed8..25df913dbb 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -146,7 +146,11 @@ impl BridgePoolTree { // get the leaf hashes let leaves: BTreeSet = values.iter().map(|v| v.keccak256()).collect(); - + if !leaves.is_subset(&self.leaves) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; let mut hashes: Vec<_> = self @@ -331,8 +335,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -347,12 +351,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index ebdce1d15f..f0a2d5f093 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -58,8 +58,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); @@ -68,7 +68,7 @@ impl Encode for PendingTransfer { let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, fee_from, nonce] + [version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -117,17 +117,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -139,13 +137,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 9c8825dd98..89016530b0 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -419,6 +421,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From 3c0c2a20ca6c1333e5a6482ef1aa29f6e8af9fcf Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 1113/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3f9026c5fc..d942a573be 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -457,6 +457,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1221,6 +1223,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index f0a2d5f093..4cfc557987 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -133,6 +134,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -141,11 +144,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..24e0145ac5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -301,6 +301,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 6dea9275777a72526d195d2e93cf2ebfbdbc3711 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1114/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d942a573be..b1951eb800 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -18,7 +18,6 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 0bcf7da126cbae1201984a9e45801bf17780e7bd Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1115/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- shared/src/types/eth_bridge_pool.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4cfc557987..33bf927823 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -59,8 +59,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode<7> for PendingTransfer { - fn tokenize(&self) -> [Token; 7] { +impl Encode<8> for PendingTransfer { + fn tokenize(&self) -> [Token; 8] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); From 4cc874d411efe9cf0c26f468d908a98d353c6c9b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1116/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 50df74286c..d077c3cab2 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -278,6 +278,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 93a10fbc117380baa6966c73574b265f8c447809 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1117/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From bef9fc2ea6c307242c3770059b1dd89b1e0c62cc Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:44:52 +0200 Subject: [PATCH 1118/1995] [chore]: rebasing on changes from previous feature prs --- shared/src/types/storage.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index d077c3cab2..6e7eb4e284 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -273,17 +273,9 @@ impl MerkleValue { pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + Self::BridgePoolTransfer(transfer) => { + transfer.try_to_vec().unwrap() + } } } } From 8379f2f6218b6fbe2ed4bead7c82667169f11095 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1119/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From c13730ed41ab7930a37c35a14464d8c077f654c9 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1120/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 6e7eb4e284..ffe2f905e8 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -280,6 +280,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From f9edb5fef3775869d9fecd31a18337aad45ff837 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1121/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 43e7be9ccc2f46caa36fabde77459abda6c14a6a Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1122/1995] [feat]: Corrected the abi encodings of bridge proofs --- shared/src/ledger/eth_bridge/storage/bridge_pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 25df913dbb..4271f91407 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -149,7 +149,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.leaves) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; let mut flags = vec![]; From 14f8272fee41e30754715f87472824f478056e70 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 17:05:19 +0100 Subject: [PATCH 1123/1995] Start recording the block heights at which validators vote --- .../transactions/ethereum_events/mod.rs | 34 +++++++++++++++--- .../ledger/protocol/transactions/votes.rs | 35 ++++++++++--------- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index f6038589b3..1b75532465 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -4,7 +4,7 @@ mod eth_msgs; mod events; -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; @@ -157,9 +157,17 @@ where // is a less arbitrary way to do this let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; + let mut seen_by = BTreeMap::default(); + for (address, block_height) in update.seen_by.into_iter() { + // TODO: more deterministic deduplication + if let Some(present) = seen_by.insert(address, block_height) { + tracing::warn!(?present, "Duplicate vote in digest"); + } + } + let (vote_tracking, changed, confirmed) = if !exists_in_storage { tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); - let vote_tracking = calculate_new(&update.seen_by, voting_powers)?; + let vote_tracking = calculate_new(seen_by, voting_powers)?; let changed = eth_msg_keys.into_iter().collect(); let confirmed = vote_tracking.seen; (vote_tracking, changed, confirmed) @@ -168,9 +176,25 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let vote_tracking = - calculate_updated(storage, ð_msg_keys, voting_powers)?; - let changed = BTreeSet::default(); // TODO(namada#515): calculate changed keys + // TODO: move construction of votes map up the call path + let mut votes = HashMap::default(); + seen_by.iter().for_each(|(address, block_height)| { + let fvp = voting_powers + .get(&(address.to_owned(), block_height.to_owned())) + .unwrap(); + if let Some(already_present_fvp) = + votes.insert(address.to_owned(), fvp.to_owned()) + { + tracing::warn!( + ?address, + ?already_present_fvp, + new_fvp = ?fvp, + "Validator voted more than once, arbitrarily using later value", + ) + } + }); + let (vote_tracking, changed) = + calculate_updated(storage, ð_msg_keys, &votes)?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index d597d9e3b2..8c1dcac9c5 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -1,7 +1,7 @@ //! Logic and data types relating to tallying validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -9,11 +9,14 @@ use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; -use namada::types::storage::BlockHeight; +use namada::types::storage::{self, BlockHeight}; use namada::types::voting_power::FractionalVotingPower; use crate::node::ledger::protocol::transactions::read; +/// The keys changed while applying a protocol transaction +type ChangedKeys = BTreeSet; + #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -22,11 +25,11 @@ use crate::node::ledger::protocol::transactions::read; pub struct Tally { /// The total voting power that's voted for this event across all epochs pub voting_power: FractionalVotingPower, - /// The addresses of validators that voted for this event. We use a - /// set type as validators should only be able to vote at most once, - /// and [`BTreeSet`] specifically as we want this field to be - /// deterministically ordered for storage. - pub seen_by: BTreeSet
, + /// The addresses of validators that voted for this event and the block + /// heights at which they voted. We use a map type as validators should + /// only be able to vote at most once, and [`BTreeMap`] specifically as + /// we want this field to be deterministically ordered for storage. + pub seen_by: BTreeMap, /// Whether this event has been acted on or not - this should only ever /// transition from `false` to `true`, once there is enough voting power pub seen: bool, @@ -35,11 +38,11 @@ pub struct Tally { /// Calculate a new [`Tally`] based on some validators' fractional voting powers /// as specific block heights pub fn calculate_new( - seen_by: &BTreeSet<(Address, BlockHeight)>, + seen_by: BTreeMap, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result { let mut seen_by_voting_power = FractionalVotingPower::default(); - for (validator, block_height) in seen_by { + for (validator, block_height) in seen_by.iter() { match voting_powers .get(&(validator.to_owned(), block_height.to_owned())) { @@ -57,10 +60,7 @@ pub fn calculate_new( seen_by_voting_power > FractionalVotingPower::TWO_THIRDS; Ok(Tally { voting_power: seen_by_voting_power, - seen_by: seen_by - .iter() - .map(|(validator, _)| validator.to_owned()) - .collect(), + seen_by, seen: newly_confirmed, }) } @@ -70,8 +70,8 @@ pub fn calculate_new( pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - _voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> Result + _voting_powers: &HashMap, +) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -80,7 +80,8 @@ where // TODO(namada#515): implement this let _body: T = read::value(store, &keys.body())?; let seen: bool = read::value(store, &keys.seen())?; - let seen_by: BTreeSet
= read::value(store, &keys.seen_by())?; + let seen_by: BTreeMap = + read::value(store, &keys.seen_by())?; let voting_power: FractionalVotingPower = read::value(store, &keys.voting_power())?; @@ -95,7 +96,7 @@ where "Updating events is not implemented yet, so the returned vote tally \ will be identical to the one in storage", ); - Ok(tally) + Ok((tally, ChangedKeys::default())) } pub fn write( From d453d1caf3f6867779040496fe41b33b426bc290 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 17:16:41 +0100 Subject: [PATCH 1124/1995] Consolidate ChangedKeys type --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 7 ++----- apps/src/lib/node/ledger/protocol/transactions/mod.rs | 7 +++++++ apps/src/lib/node/ledger/protocol/transactions/votes.rs | 8 +++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 1b75532465..fe3b4161a5 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -12,11 +12,12 @@ use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; -use namada::types::storage::{self, BlockHeight}; +use namada::types::storage::BlockHeight; use namada::types::transaction::TxResult; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; +use super::ChangedKeys; use crate::node::ledger::protocol::transactions::utils::{ self, get_active_validators, }; @@ -24,9 +25,6 @@ use crate::node::ledger::protocol::transactions::votes::{ calculate_new, calculate_updated, write, }; -/// The keys changed while applying a protocol transaction -type ChangedKeys = BTreeSet; - /// Applies derived state changes to storage, based on Ethereum `events` which /// were newly seen by some active validator(s) in the last epoch. For `events` /// which have been seen by enough voting power, extra state changes may take @@ -232,7 +230,6 @@ mod tests { }; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::token::Amount; - use storage::BlockHeight; use super::*; diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 79c3cb990d..046e3f2c8f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -4,6 +4,10 @@ //! to update their blockchain state in a deterministic way. This can be done //! natively rather than via the wasm environment as happens with regular //! transactions. + +use std::collections::BTreeSet; + +use namada::types::storage; #[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; #[cfg(not(feature = "abcipp"))] @@ -17,3 +21,6 @@ mod update; #[cfg(not(feature = "abcipp"))] mod utils; + +/// The keys changed while applying a protocol transaction. +pub type ChangedKeys = BTreeSet; diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index 8c1dcac9c5..e697a812be 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -1,7 +1,7 @@ //! Logic and data types relating to tallying validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::{BTreeMap, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -9,14 +9,12 @@ use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; -use namada::types::storage::{self, BlockHeight}; +use namada::types::storage::BlockHeight; use namada::types::voting_power::FractionalVotingPower; +use super::ChangedKeys; use crate::node::ledger::protocol::transactions::read; -/// The keys changed while applying a protocol transaction -type ChangedKeys = BTreeSet; - #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] From c2d944f50c0b3090eb99800a2ec30812081dfb84 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 17:31:12 +0100 Subject: [PATCH 1125/1995] Update EthMsgUpdate to use BTreeMap --- .../protocol/transactions/ethereum_events/eth_msgs.rs | 10 +++++----- .../protocol/transactions/ethereum_events/mod.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 47e0caf111..2539a387d8 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::BTreeMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada::types::address::Address; @@ -24,11 +24,11 @@ pub struct EthMsgUpdate { /// The event being seen pub body: EthereumEvent, /// Addresses of the validators who have just seen this event. We use - /// [`BTreeSet`] even though ordering is not important here, so that we + /// [`BTreeMap`] even though ordering is not important here, so that we /// can derive [`Hash`] for [`EthMsgUpdate`]. // NOTE(feature = "abcipp"): This can just become BTreeSet
because // BlockHeight will always be the previous block - pub seen_by: BTreeSet<(Address, BlockHeight)>, + pub seen_by: BTreeMap, } impl From for EthMsgUpdate { @@ -80,8 +80,8 @@ mod tests { }; let expected = EthMsgUpdate { body: event, - seen_by: BTreeSet::from_iter(vec![( - sole_validator, + seen_by: BTreeMap::from([( + sole_validator.clone(), BlockHeight(100), )]), }; diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index fe3b4161a5..8fdd87e522 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -251,7 +251,7 @@ mod tests { }; let update = EthMsgUpdate { body: body.clone(), - seen_by: BTreeSet::from_iter(vec![( + seen_by: BTreeMap::from([( sole_validator.clone(), BlockHeight(100), )]), @@ -290,8 +290,8 @@ mod tests { let (seen_by_bytes, _) = storage.read(ð_msg_keys.seen_by())?; let seen_by_bytes = seen_by_bytes.unwrap(); assert_eq!( - Vec::
::try_from_slice(&seen_by_bytes)?, - vec![sole_validator] + BTreeMap::::try_from_slice(&seen_by_bytes)?, + BTreeMap::from([(sole_validator.clone(), BlockHeight(100))]) ); let (voting_power_bytes, _) = From dafedd50f31d85347afbfa12b59dd36647efd21d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 17:51:38 +0100 Subject: [PATCH 1126/1995] Declare a `Votes` type alias and update docstrings --- .../transactions/ethereum_events/eth_msgs.rs | 19 ++++----------- .../transactions/ethereum_events/mod.rs | 15 +++++------- .../node/ledger/protocol/transactions/mod.rs | 2 ++ .../ledger/protocol/transactions/votes.rs | 23 ++++++++++++------- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 2539a387d8..4452e2c8b6 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,12 +1,8 @@ -use std::collections::BTreeMap; - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use namada::types::address::Address; use namada::types::ethereum_events::EthereumEvent; -use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::node::ledger::protocol::transactions::votes::Tally; +use crate::node::ledger::protocol::transactions::votes::{Tally, Votes}; /// Represents an Ethereum event being seen by some validators #[derive( @@ -21,14 +17,12 @@ use crate::node::ledger::protocol::transactions::votes::Tally; BorshDeserialize, )] pub struct EthMsgUpdate { - /// The event being seen + /// The event being seen. pub body: EthereumEvent, - /// Addresses of the validators who have just seen this event. We use - /// [`BTreeMap`] even though ordering is not important here, so that we - /// can derive [`Hash`] for [`EthMsgUpdate`]. + /// New votes for this event. // NOTE(feature = "abcipp"): This can just become BTreeSet
because // BlockHeight will always be the previous block - pub seen_by: BTreeMap, + pub seen_by: Votes, } impl From for EthMsgUpdate { @@ -80,10 +74,7 @@ mod tests { }; let expected = EthMsgUpdate { body: event, - seen_by: BTreeMap::from([( - sole_validator.clone(), - BlockHeight(100), - )]), + seen_by: Votes::from([(sole_validator.clone(), BlockHeight(100))]), }; let update: EthMsgUpdate = with_signers.into(); diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 8fdd87e522..6bce7c4bd2 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -4,7 +4,7 @@ mod eth_msgs; mod events; -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; @@ -22,7 +22,7 @@ use crate::node::ledger::protocol::transactions::utils::{ self, get_active_validators, }; use crate::node::ledger::protocol::transactions::votes::{ - calculate_new, calculate_updated, write, + calculate_new, calculate_updated, write, Votes, }; /// Applies derived state changes to storage, based on Ethereum `events` which @@ -155,7 +155,7 @@ where // is a less arbitrary way to do this let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; - let mut seen_by = BTreeMap::default(); + let mut seen_by = Votes::default(); for (address, block_height) in update.seen_by.into_iter() { // TODO: more deterministic deduplication if let Some(present) = seen_by.insert(address, block_height) { @@ -251,10 +251,7 @@ mod tests { }; let update = EthMsgUpdate { body: body.clone(), - seen_by: BTreeMap::from([( - sole_validator.clone(), - BlockHeight(100), - )]), + seen_by: Votes::from([(sole_validator.clone(), BlockHeight(100))]), }; let updates = HashSet::from_iter(vec![update]); let voting_powers = HashMap::from_iter(vec![( @@ -290,8 +287,8 @@ mod tests { let (seen_by_bytes, _) = storage.read(ð_msg_keys.seen_by())?; let seen_by_bytes = seen_by_bytes.unwrap(); assert_eq!( - BTreeMap::::try_from_slice(&seen_by_bytes)?, - BTreeMap::from([(sole_validator.clone(), BlockHeight(100))]) + Votes::try_from_slice(&seen_by_bytes)?, + Votes::from([(sole_validator.clone(), BlockHeight(100))]) ); let (voting_power_bytes, _) = diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 046e3f2c8f..64d2ab220f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -8,8 +8,10 @@ use std::collections::BTreeSet; use namada::types::storage; + #[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; + #[cfg(not(feature = "abcipp"))] mod votes; diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index e697a812be..8bada865bd 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -15,6 +15,14 @@ use namada::types::voting_power::FractionalVotingPower; use super::ChangedKeys; use crate::node::ledger::protocol::transactions::read; +/// The addresses of validators that voted for something, and the block +/// heights at which they voted. We use a [`BTreeMap`] to enforce that a +/// validator (as uniquely identified by an [`Address`]) may vote at most once, +/// and their vote must be associated with a specific [`BlockHeight`]. Their +/// voting power at that block height is what is used when calculating whether +/// something has enough voting power behind it or not. +pub type Votes = BTreeMap; + #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] @@ -23,11 +31,11 @@ use crate::node::ledger::protocol::transactions::read; pub struct Tally { /// The total voting power that's voted for this event across all epochs pub voting_power: FractionalVotingPower, - /// The addresses of validators that voted for this event and the block - /// heights at which they voted. We use a map type as validators should - /// only be able to vote at most once, and [`BTreeMap`] specifically as - /// we want this field to be deterministically ordered for storage. - pub seen_by: BTreeMap, + /// The votes which have been counted towards `voting_power`. Note that + /// validators may submit multiple votes at different block heights for + /// the same thing, but ultimately only one vote per validator will be + /// used when tallying voting power. + pub seen_by: Votes, /// Whether this event has been acted on or not - this should only ever /// transition from `false` to `true`, once there is enough voting power pub seen: bool, @@ -36,7 +44,7 @@ pub struct Tally { /// Calculate a new [`Tally`] based on some validators' fractional voting powers /// as specific block heights pub fn calculate_new( - seen_by: BTreeMap, + seen_by: Votes, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result { let mut seen_by_voting_power = FractionalVotingPower::default(); @@ -78,8 +86,7 @@ where // TODO(namada#515): implement this let _body: T = read::value(store, &keys.body())?; let seen: bool = read::value(store, &keys.seen())?; - let seen_by: BTreeMap = - read::value(store, &keys.seen_by())?; + let seen_by: Votes = read::value(store, &keys.seen_by())?; let voting_power: FractionalVotingPower = read::value(store, &keys.voting_power())?; From 28d96b4c4814ce88e21182a3601f426ffdd136d7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 18:20:37 +0100 Subject: [PATCH 1127/1995] Update docstring --- apps/src/lib/node/ledger/protocol/transactions/votes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/votes.rs b/apps/src/lib/node/ledger/protocol/transactions/votes.rs index 8bada865bd..6738a05c70 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/votes.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/votes.rs @@ -72,11 +72,11 @@ pub fn calculate_new( } /// Calculate an updated [`Tally`] based on one that is in storage under `keys`, -/// and some new votes +/// with some new `voters`. pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - _voting_powers: &HashMap, + _voters: &HashMap, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, From 8c135253bd30746874246436a28ca8ce4867ca6f Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1128/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 0b21f7776c7ae8e6efa4000a9948c51f9a46c950 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1129/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index ffe2f905e8..3294257034 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -290,6 +290,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 5a8e624dc27f4681f2729ebc1677ca4e14051310 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1130/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From c58b4dc4af32796a73b8e09ab0e2b307b334cfed Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:54:52 +0200 Subject: [PATCH 1131/1995] [fix]: Removed duplicated code block --- shared/src/types/storage.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 3294257034..5e91c068eb 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -269,7 +269,7 @@ impl From for MerkleValue { } impl MerkleValue { - /// Get the natural byte repesentation of the value + /// Get the natural byte representation of the value pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, @@ -280,26 +280,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From d96fb48191ac3eb4bfb71eedc479556020ea147b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 10:20:38 +0200 Subject: [PATCH 1132/1995] [chore]: Rebase on previous branches --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b1951eb800..207eb44da5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1216,7 +1216,6 @@ mod test_queries { ); let proof = tree .get_membership_proof( - std::array::from_ref(&Key::from(&transfer)), vec![transfer], ) .expect("Test failed"); From cd55e32e535fd8e8a4ca6cfc383f0f77264cb06f Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 10:29:43 +0200 Subject: [PATCH 1133/1995] [fix]: Formatting --- apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs index 7b3d3618e4..05ac9f02dc 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/mod.rs @@ -215,7 +215,8 @@ pub mod mock_web3_client { client.events.push((event_ty, data, height, seen)); } } - if client.last_block_processed.as_ref() < Some(&block_to_check) { + if client.last_block_processed.as_ref() < Some(&block_to_check) + { client .blocks_processed .send(block_to_check.clone()) From c6452baf7757c8be5646148e1ed6d58cb4de1023 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 25 Oct 2022 10:43:34 +0200 Subject: [PATCH 1134/1995] Update shared/src/ledger/eth_bridge/storage/bridge_pool.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/storage/bridge_pool.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index e03d499af3..0e7d67eae6 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -268,7 +268,6 @@ pub struct BridgePoolProof { pub proof: Vec, /// The leaves; must be sorted pub leaves: Vec, - /// Flags to indicate how to combine hashes. /// Flags are used to indicate which consecutive /// pairs of leaves in `leaves` are siblings. pub flags: Vec, From 65445493a1415de11f2b294c5cb9f43450da220c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1135/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 0624a83ae1..f558c2d2dc 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -232,8 +215,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -244,9 +227,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -256,9 +237,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 0e7d67eae6..7ce5584c94 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool /// /// Note that an empty tree has root [0u8; 20] by definition. diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index be15012cfa..78e54a154a 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 251dc1589e69f936da34bfe5cb42ced1aa509908 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1136/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 +++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - 2 files changed, 124 insertions(+), 88 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index f558c2d2dc..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -272,19 +271,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -340,10 +341,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -360,10 +358,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -372,13 +372,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -389,13 +391,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -406,13 +410,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -423,13 +429,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -440,58 +448,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -502,13 +535,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -539,11 +577,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7ce5584c94..19426af21c 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; From ec16f3accdd444fb0c556cf829f0d5e9b1779e18 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 1137/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From 16c937d23d4c62591dbb1547e3912c071b873b90 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1138/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 +++++++++------ shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..97aa273088 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -116,11 +117,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { - if *key != pending_key { + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." ); return Ok(false); } @@ -131,7 +132,9 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool." + ); return Ok(false); } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 78e54a154a..4cb6d91f69 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -657,6 +657,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 97973deac96373419699ada242484dfeed5bee75 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1139/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 97aa273088..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -117,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -132,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } From e0ab8ea49a6220bd7cc73fe7f55bc0fb199b7c2b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 16:06:12 +0200 Subject: [PATCH 1140/1995] [fix]: Added some more logging --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -120,7 +120,10 @@ where if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + incorrect key in the pending transaction pool: {}.\n \ + Expected key: {}", + key, + pending_key ); return Ok(false); } @@ -131,7 +134,12 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool: {:?}.\n \ + Expected: {:?}", + transfer, + pending + ); return Ok(false); } From a5b10ce50c38ae62825b6fd721f26092a1fd67ac Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1141/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..117c9e61ef 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 4cb6d91f69..655c7be0ed 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -673,6 +673,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 9f48e7de264dd23c6928496a277f7feafb9eccdd Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1142/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 117c9e61ef..47aa3b7968 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -466,6 +465,15 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } From 796cb8096fc9b80ccfcec2ea3bfd6cd93f0562eb Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1143/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 + shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 47aa3b7968..c3b05e4e6f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,6 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 655c7be0ed..146d5cfb04 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -689,6 +689,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From d4be3851438ba02bc09413896f7543f9b0efedbd Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1144/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c3b05e4e6f..2fbce42f60 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -466,15 +466,6 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, - |transfer, log| { - log.write( - &get_pending_key(&transfer), - transfer.try_to_vec().unwrap(), - ) - .unwrap(); - BTreeSet::from([get_pending_key(&transfer)]) - }, - Expect::False, ); } From 34b40e88518768c64a0d9db5f6c4371d1211f8fc Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 15:40:29 +0200 Subject: [PATCH 1145/1995] [fix]: Fixed some garbage created by rebasing --- shared/src/types/storage.rs | 39 +++---------------------------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 146d5cfb04..9fa5b65128 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,7 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::keccak::KeccakHash; +use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -643,41 +643,8 @@ impl KeySeg for Hash { impl KeySeg for KeccakHash { fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) + seg.try_into() + .map_err(|e: TryFromError| Error::ParseError(e.to_string())) } fn raw(&self) -> String { From 8f6036bf95660a6ac84a5a8794351d43374d19bf Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Oct 2022 17:12:09 +0200 Subject: [PATCH 1146/1995] [chore]: Rebased onto PR #573 --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 - shared/src/types/storage.rs | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 2fbce42f60..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,6 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 9fa5b65128..eee9b5b847 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,22 +656,6 @@ impl KeySeg for KeccakHash { } } -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From caebf4c083a4a8abf5626697038cac0069178c5e Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1147/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..117c9e61ef 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; From 0c9d93fbb040ad98ddea364b5a734ea12f34d9fe Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1148/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/types/storage.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index eee9b5b847..9fa5b65128 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,6 +656,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 632a6cd191060aa9a97f54d4471a541607ec8c10 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1149/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 31 +++- apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 56 +++++-- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 54 ++++++- 7 files changed, 284 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 4cbedca8bc..25816cc426 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -17,7 +17,10 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block. Epoch, - /// Read a storage value with exact storage key. + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), + /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix. Prefix(storage::Key), @@ -29,6 +32,16 @@ pub enum Path { Applied { tx_hash: Hash }, } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -39,6 +52,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -59,6 +75,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } Path::Accepted { tx_hash } => { write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } @@ -77,6 +100,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9203d4f3e8..9208f32f2d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,22 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -21,6 +30,7 @@ use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::events::log::dumb_queries; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -87,6 +97,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, Path::Accepted { tx_hash } => { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) @@ -337,6 +355,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 19426af21c..da024ace6f 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -88,7 +89,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.leaves.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -324,6 +325,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { @@ -385,9 +411,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -422,7 +447,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -478,9 +503,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -611,7 +635,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -641,7 +665,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfers.remove(0)]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -670,7 +694,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values = vec![transfers[0].clone(), transfers[1].clone()]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -697,7 +721,7 @@ mod test_bridge_pool_tree { } let values = vec![]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -724,7 +748,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -751,7 +775,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -779,7 +803,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -840,7 +864,7 @@ mod test_bridge_pool_tree { to_prove.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(to_prove).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 3add9dd7f0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index c2464c52d1..12ccd198a5 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -21,7 +21,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index ee9805a8ee..5ff9f7bb59 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 904c2c7eec..4c5c5496a3 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,10 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -56,13 +58,15 @@ pub struct PendingTransfer { impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, fee_from, nonce] + vec![version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -96,3 +100,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From 600e482cdd061e1ead713d9a1f952d4004161824 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1150/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 7 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 9 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 8 files changed, 235 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9208f32f2d..d53ca1fd55 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,13 +5,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -20,7 +20,8 @@ use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -364,13 +365,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -420,12 +433,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -438,22 +446,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -468,6 +468,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -869,10 +870,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1047,4 +1058,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index da024ace6f..d92d2f7e94 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 12ccd198a5..8cc6fe6dcf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -430,15 +430,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl Into, ) -> Result<(u64, i64)> { + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); self.block.tree.update(key, value.clone())?; - let len = value.as_ref().len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4c5c5496a3..ebdce1d15f 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -43,6 +44,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -89,6 +91,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 9fa5b65128..80dcbab993 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -245,6 +245,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -267,6 +268,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From abe4e19e6343b199015f09bd579c224f9cedbecf Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1151/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/queries.rs | 18 ++++++++---------- shared/Cargo.toml | 1 - 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d53ca1fd55..0d384d50cd 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,6 +16,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; @@ -1083,7 +1084,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1120,7 +1121,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1128,7 +1129,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1136,7 +1137,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1180,7 +1181,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1194,14 +1195,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From f4c7a0100c3b64c376c131e03ac32f4561c03192 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 1152/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0d384d50cd..e097e244ec 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -401,7 +401,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1225,4 +1225,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } From e25b77213989acad21bdbdf1252de60b04f36408 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1153/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 14 +++++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e097e244ec..3f9026c5fc 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -459,6 +459,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1221,6 +1223,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1263,7 +1267,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index d92d2f7e94..a0ff7b91b1 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -146,7 +146,11 @@ impl BridgePoolTree { // get the leaf hashes let leaves: BTreeSet = values.iter().map(|v| v.keccak256()).collect(); - + if !leaves.is_subset(&self.leaves) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; let mut hashes: Vec<_> = self @@ -330,8 +334,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -346,12 +350,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index ebdce1d15f..f0a2d5f093 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -58,8 +58,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); @@ -68,7 +68,7 @@ impl Encode for PendingTransfer { let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, fee_from, nonce] + [version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -117,17 +117,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -139,13 +137,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index d83f2ea953..5e5278b06e 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// The provided constant is for a traditional /// signature on this curve. For Ethereum, an extra byte is included @@ -422,6 +424,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From 98cb92e49dee3fa4d5113173e83038c608f21501 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 1154/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3f9026c5fc..d942a573be 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -457,6 +457,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1221,6 +1223,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index f0a2d5f093..4cfc557987 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -133,6 +134,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -141,11 +144,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..24e0145ac5 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -301,6 +301,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 7ee06ff88d04c0db91838727133fc96560e1d3e2 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1155/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d942a573be..b1951eb800 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -18,7 +18,6 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 34fa94d697bea78a19e8da843e73b5d77bdf879e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1156/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- shared/src/types/eth_bridge_pool.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 4cfc557987..33bf927823 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -59,8 +59,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode<7> for PendingTransfer { - fn tokenize(&self) -> [Token; 7] { +impl Encode<8> for PendingTransfer { + fn tokenize(&self) -> [Token; 8] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); From d535faed901798830a64ada068e0cf99ddb5062f Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1157/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 80dcbab993..fd184151e6 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -278,6 +278,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 9a094b21b383fa59b71e9ad684438330a17ae137 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1158/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 4c20c675f1a58396f82419a7e5f09dc4474155b2 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:44:52 +0200 Subject: [PATCH 1159/1995] [chore]: rebasing on changes from previous feature prs --- shared/src/types/storage.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fd184151e6..e5b8f70eed 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -273,17 +273,9 @@ impl MerkleValue { pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + Self::BridgePoolTransfer(transfer) => { + transfer.try_to_vec().unwrap() + } } } } From 2060a9df60abc39dd6eadd93f106d14f26df90f1 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1160/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 5591e810be406698bb031cbee874bdc51a471494 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1161/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index e5b8f70eed..2eb5e99072 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -280,6 +280,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 7a18a8a9c677e77f75f91a9feb7f5b3f6c10aa93 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1162/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 0781fab85b7a30b483be2b5270e74b44bcceb35d Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1163/1995] [feat]: Corrected the abi encodings of bridge proofs --- shared/src/ledger/eth_bridge/storage/bridge_pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index a0ff7b91b1..92ee41777f 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -149,7 +149,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.leaves) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; let mut flags = vec![]; From 44f9e0c460fedd25c0070539b7881e29e7093d2c Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1164/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 3ea900fada6aa6a0cf86264e9bf4faa37cf73a7f Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1165/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 2eb5e99072..dd798b50ec 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -290,6 +290,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 045717839d0978630a9c30975c7354ee56641649 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1166/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 472210639f5b9b2d487e282e2cbfa536d0b7bd04 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:54:52 +0200 Subject: [PATCH 1167/1995] [fix]: Removed duplicated code block --- shared/src/types/storage.rs | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index dd798b50ec..adee37d2fd 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -269,7 +269,7 @@ impl From for MerkleValue { } impl MerkleValue { - /// Get the natural byte repesentation of the value + /// Get the natural byte representation of the value pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, @@ -280,26 +280,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 6f0dbc5cde5f76df7ec8e432032480b4bc3758c6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 09:57:29 +0100 Subject: [PATCH 1168/1995] Remove redundant clones --- .../ledger/protocol/transactions/ethereum_events/eth_msgs.rs | 2 +- .../node/ledger/protocol/transactions/ethereum_events/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 4452e2c8b6..c2b7746913 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -74,7 +74,7 @@ mod tests { }; let expected = EthMsgUpdate { body: event, - seen_by: Votes::from([(sole_validator.clone(), BlockHeight(100))]), + seen_by: Votes::from([(sole_validator, BlockHeight(100))]), }; let update: EthMsgUpdate = with_signers.into(); diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 6bce7c4bd2..78febb850a 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -288,7 +288,7 @@ mod tests { let seen_by_bytes = seen_by_bytes.unwrap(); assert_eq!( Votes::try_from_slice(&seen_by_bytes)?, - Votes::from([(sole_validator.clone(), BlockHeight(100))]) + Votes::from([(sole_validator, BlockHeight(100))]) ); let (voting_power_bytes, _) = From 931fdae793fc6c6f088bb164360d62fa0a15d9a9 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1169/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From da1206f31a6ac81a2463075578225f511ea1d0a1 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1170/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 41ecd5b216a5c60a8e091f154e6f1a23f9f6479a Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1171/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/queries.rs | 2 +- shared/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b1951eb800..75a427dff5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,8 +16,8 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; -use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; +use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 7b76346b3cef12d85b9e9b8f69d5cc387f3b54db Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1172/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 75a427dff5..fb417e8c2d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -17,7 +17,6 @@ use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 574508386efb261a9e2623ef272510bf8abaafc1 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1173/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 2f589c97f9944e1f9dbaa10710cc41f198752a65 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1174/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index adee37d2fd..e33fb57fe6 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -280,6 +280,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 29f9a197ded5621532a6d40d52330574373ae339 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1175/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 69ee903a3bad3752c6c4474bcff67347625975d7 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1176/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From b9255c057c58712352f3f9ad892d72a78b7f0984 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1177/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index e33fb57fe6..b99a9b70ba 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -290,6 +290,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 52c7311cc1e5094882ebd718b80dfac7f6762fa7 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1178/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/queries.rs | 1 + shared/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index fb417e8c2d..b1951eb800 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -16,6 +16,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 15c9036a5a09741312b5b5f2beeb8ff0a6576d41 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1179/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 9d7e0f1a78c610e811fb1e212954168910ce1801 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1180/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..db34e1c8d7 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,6 +105,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b99a9b70ba..67c3559f9d 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -300,6 +300,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 8aa745e44920f2eb20fad226fe63bd89879b491b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1181/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index db34e1c8d7..c16713d30c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -105,7 +105,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 8be8b1b7ccf1240c35a1099335c875ab40c64468 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 10:20:38 +0200 Subject: [PATCH 1182/1995] [chore]: Rebase on previous branches --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b1951eb800..207eb44da5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1216,7 +1216,6 @@ mod test_queries { ); let proof = tree .get_membership_proof( - std::array::from_ref(&Key::from(&transfer)), vec![transfer], ) .expect("Test failed"); From aac7af6196bb3fdea38eee62edcf8bd26a92a043 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 10:08:24 +0100 Subject: [PATCH 1183/1995] Remove TODO item --- .../lib/node/ledger/protocol/transactions/ethereum_events/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 78febb850a..4503e70fb1 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -174,7 +174,6 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - // TODO: move construction of votes map up the call path let mut votes = HashMap::default(); seen_by.iter().for_each(|(address, block_height)| { let fvp = voting_powers From ec8b0e4fa45fb7b91786ba4f867a77adec4d4cbc Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 11:11:28 +0200 Subject: [PATCH 1184/1995] [chore]: rebasing on eth-bridge-integration --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 3 +- .../ledger/eth_bridge/storage/bridge_pool.rs | 4 +- shared/src/types/storage.rs | 46 ------------------- 3 files changed, 3 insertions(+), 50 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 117c9e61ef..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 92ee41777f..362feed8b2 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -126,8 +126,8 @@ impl BridgePoolTree { } /// Return the root as a [`struct@Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 67c3559f9d..5e91c068eb 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -280,36 +280,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { @@ -699,22 +669,6 @@ impl KeySeg for KeccakHash { } } -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 749888fb84af047f22a1f1bf4fd4ce32e3ae7198 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 11:22:47 +0200 Subject: [PATCH 1185/1995] [chore]: Formatting --- apps/src/lib/node/ledger/shell/queries.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 207eb44da5..ae505f903b 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1215,9 +1215,7 @@ mod test_queries { BTreeSet::from([transfer.keccak256()]), ); let proof = tree - .get_membership_proof( - vec![transfer], - ) + .get_membership_proof(vec![transfer]) .expect("Test failed"); let proof = RelayProof { From 04df62f6c2d9d17e464614af7f8e4dc159c076db Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1186/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 63 +++++++------------ .../ledger/eth_bridge/storage/bridge_pool.rs | 13 +--- shared/src/types/storage.rs | 17 +++++ 3 files changed, 42 insertions(+), 51 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 0624a83ae1..f558c2d2dc 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,16 +14,15 @@ use std::collections::{BTreeSet, HashSet}; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, -}; +use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,43 +114,27 @@ where } }; - // check that only the pending_key value is changed - if keys_changed.iter().any(is_protected_storage) { - tracing::debug!( - "Rejecting transaction as it is attempting to change the \ - bridge pool storage other than the pending transaction pool" - ); - return Ok(false); + let pending_key = get_pending_key(&transfer); + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { + tracing::debug!( + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." + ); + return Ok(false); + } } - // check that the pending transfer (and only that) was added to the pool - // TODO: This will change slightly when we merkelize the pool, - // but that will be a separate PR. - let pending_key = get_pending_key(); - let pending_pre: HashSet = - (&self.ctx).read_pre_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - let pending_post: HashSet = + let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( - "The bridge pool transfers are missing from storage" - ))?; - if !pending_post.contains(&transfer) { - tracing::debug!( "Rejecting transaction as the transfer wasn't added to the \ pending transfers" + ))?; + if pending != transfer { + tracing::debug!( + "An incorrect transfer was added to the pool." ); return Ok(false); } - for item in pending_pre.symmetric_difference(&pending_post) { - if item != &transfer { - tracing::debug!( - ?item, - "Rejecting transaction as an unrecognized item was added \ - to the pending transfers" - ); - return Ok(false); - } - } // check that gas fees were put into escrow @@ -232,8 +215,8 @@ mod test_bridge_pool_vp { } /// The bridge pool at the beginning of all tests - fn initial_pool() -> HashSet { - let transfer = PendingTransfer { + fn initial_pool() -> PendingTransfer { + PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), @@ -244,9 +227,7 @@ mod test_bridge_pool_vp { amount: 0.into(), payer: bertha_address(), }, - }; - - HashSet::::from([transfer]) + } } /// Create a new storage @@ -256,9 +237,9 @@ mod test_bridge_pool_vp { writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) .unwrap(); - + let transfer = initial_pool(); writelog - .write(&get_pending_key(), initial_pool().try_to_vec().unwrap()) + .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) .unwrap(); let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); let amount: Amount = ESCROWED_AMOUNT.into(); diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 0e7d67eae6..7ce5584c94 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -11,7 +11,7 @@ use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = @@ -27,11 +27,11 @@ const SIGNED_ROOT_SEG: &str = "signed_root"; pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool -pub fn get_pending_key() -> Key { +pub fn get_pending_key(transfer: &PendingTransfer) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - DbKeySeg::StringSeg(PENDING_TRANSFERS_SEG.into()), + transfer.keccak256().to_db_key(), ], } } @@ -52,13 +52,6 @@ pub fn is_bridge_pool_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &BRIDGE_POOL_ADDRESS) } -/// Check if a key belongs to the bridge pool but is not -/// the key for the pending transaction pool. Such keys -/// may not be modified via transactions. -pub fn is_protected_storage(key: &Key) -> bool { - is_bridge_pool_key(key) && *key != get_pending_key() -} - /// A simple Merkle tree for the Ethereum bridge pool /// /// Note that an empty tree has root [0u8; 20] by definition. diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index be15012cfa..78e54a154a 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,6 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; +use crate::types::keccak::KeccakHash; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -640,6 +641,22 @@ impl KeySeg for Hash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 48c9b0f3a130644b2c2ed3145f5207d41b8bbf3c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1187/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 210 +++++++++++------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - 2 files changed, 124 insertions(+), 88 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index f558c2d2dc..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -9,20 +9,21 @@ //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is //! added to the pool and gas fees are submitted appropriately. -use std::collections::{BTreeSet, HashSet}; +use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use crate::ledger::eth_bridge::storage::bridge_pool::{get_pending_key, is_protected_storage, BRIDGE_POOL_ADDRESS, is_bridge_pool_key}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, +}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -115,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -130,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } @@ -272,19 +271,21 @@ mod test_bridge_pool_vp { ) } + enum Expect { + True, + False, + Error, + } + /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, - keys_changed: BTreeSet, - expect: bool, + expect: Expect, ) where - F: FnOnce( - PendingTransfer, - HashSet, - ) -> HashSet, + F: FnOnce(PendingTransfer, &mut WriteLog) -> BTreeSet, { // setup let mut write_log = new_writelog(); @@ -340,10 +341,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool - let pool = insert_transfer(transfer.clone(), initial_pool()); - write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) - .expect("Test failed"); + let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { @@ -360,10 +358,12 @@ mod test_bridge_pool_vp { .expect("Test failed"); let verifiers = BTreeSet::default(); - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert_eq!(res, expect); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + match expect { + Expect::True => assert!(res.expect("Test failed")), + Expect::False => assert!(!res.expect("Test failed")), + Expect::Error => assert!(res.is_err()), + } } /// Test adding a transfer to the pool and escrowing gas passes vp @@ -372,13 +372,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - true, + Expect::True, ); } @@ -389,13 +391,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -406,13 +410,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -423,13 +429,15 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } @@ -440,58 +448,83 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::False, ); } - /// Test that if a transaction is removed from - /// the pool, the tx is rejected. + /// Test that if the transfer was not added to the + /// pool, the vp rejects #[test] - fn test_remove_transfer_rejected() { + fn test_not_adding_transfer_rejected() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, _pool| HashSet::from([transfer]), - BTreeSet::default(), - false, + |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), + Expect::Error, ); } - /// Test that if the transfer was not added to the - /// pool, the vp rejects + /// Test that if the wrong transaction was added + /// to the pool, it is rejected. #[test] - fn test_not_adding_transfer_rejected() { + fn test_add_wrong_transfer() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| pool, - BTreeSet::default(), - false, + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&transfer), t.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } /// Test that if the wrong transaction was added /// to the pool, it is rejected. #[test] - fn test_add_wrong_transfer() { + fn test_add_wrong_key() { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |_transfer, pool| { - let mut pool = pool; - let wrong_transfer = - initial_pool().into_iter().next().expect("Test failed"); - pool.insert(wrong_transfer); - pool + |transfer, log| { + let t = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 10u64.into(), + }, + gas_fee: GasFee { + amount: GAS_FEE.into(), + payer: bertha_address(), + }, + }; + log.write(&get_pending_key(&t), transfer.try_to_vec().unwrap()) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) }, - BTreeSet::default(), - false, + Expect::Error, ); } @@ -502,13 +535,18 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), - |transfer, pool| { - let mut pool = pool; - pool.insert(transfer); - pool + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([ + get_pending_key(&transfer), + get_signed_root_key(), + ]) }, - BTreeSet::from([get_signed_root_key()]), - false, + Expect::False, ); } @@ -539,11 +577,11 @@ mod test_bridge_pool_vp { }, }; - // add transfer to pool - let mut pool = initial_pool(); - pool.insert(transfer.clone()); write_log - .write(&get_pending_key(), pool.try_to_vec().expect("Test failed")) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create the data to be given to the vp diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 7ce5584c94..19426af21c 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -16,8 +16,6 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// The main address of the Ethereum bridge pool pub const BRIDGE_POOL_ADDRESS: Address = Address::Internal(InternalAddress::EthBridgePool); -/// Sub-segmnet for getting the contents of the pool -const PENDING_TRANSFERS_SEG: &str = "pending_transfers"; /// Sub-segment for getting the latest signed const SIGNED_ROOT_SEG: &str = "signed_root"; From 029dcb1cfbee7a1c4293ed6e483f9c08d2b584c7 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:30:31 +0200 Subject: [PATCH 1188/1995] [feat]: Fixed the wasm blob for adding transfers for the bridge pool --- wasm/wasm_source/src/tx_bridge_pool.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index bed8e3820e..0c945d6925 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -20,8 +20,6 @@ fn apply_tx(tx_data: Vec) { } = transfer.gas_fees; token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); // add transfer into the pool - let pending_key = bridge_pool::get_pending_key(); - let mut pending: HashSet = read(&pending_key).unwrap(); - pending.insert(transfer); - write(pending_key, pending.try_to_vec().unwrap()); + let pending_key = bridge_pool::get_pending_key(&transfer); + write(pending_key, transfer.try_to_vec().unwrap()); } \ No newline at end of file From 76726a5391ca5f9c37a2cf1b01486ae4acf9fe1a Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1189/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 +++++++++------ shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..97aa273088 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -116,11 +117,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { - if *key != pending_key { + for key in keys_changed.iter().filter(is_bridge_pool_key) { + if key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an incorrect \ + key in the pending transaction pool." ); return Ok(false); } @@ -131,7 +132,9 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool." + ); return Ok(false); } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 78e54a154a..4cb6d91f69 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -657,6 +657,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From b8adf6254eba821f442583d1ce03f67750103a27 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1190/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 97aa273088..774b2d8b39 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -117,11 +116,11 @@ where }; let pending_key = get_pending_key(&transfer); - for key in keys_changed.iter().filter(is_bridge_pool_key) { - if key != pending_key { + for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { + if *key != pending_key { tracing::debug!( - "Rejecting transaction as it is attempting to change an incorrect \ - key in the pending transaction pool." + "Rejecting transaction as it is attempting to change an \ + incorrect key in the pending transaction pool." ); return Ok(false); } @@ -132,9 +131,7 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!( - "An incorrect transfer was added to the pool." - ); + tracing::debug!("An incorrect transfer was added to the pool."); return Ok(false); } From 49f73e7e7f39f7865e22314ba7bc1c49d2fe277a Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 16:06:12 +0200 Subject: [PATCH 1191/1995] [fix]: Added some more logging --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 774b2d8b39..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -120,7 +120,10 @@ where if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool." + incorrect key in the pending transaction pool: {}.\n \ + Expected key: {}", + key, + pending_key ); return Ok(false); } @@ -131,7 +134,12 @@ where pending transfers" ))?; if pending != transfer { - tracing::debug!("An incorrect transfer was added to the pool."); + tracing::debug!( + "An incorrect transfer was added to the pool: {:?}.\n \ + Expected: {:?}", + transfer, + pending + ); return Ok(false); } From 2c365a666351c5b84941f805adcba44fee9631db Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1192/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..117c9e61ef 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 4cb6d91f69..655c7be0ed 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -673,6 +673,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From a7a04bfb52010b60312e358f1cd659f327ffaa76 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1193/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 117c9e61ef..47aa3b7968 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,8 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -466,6 +465,15 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } From 115dac9416a76da8aa9a48d70704fbb0f65f35b1 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1194/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 + shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 47aa3b7968..c3b05e4e6f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,6 +23,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 655c7be0ed..146d5cfb04 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -689,6 +689,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 9f472c72a241392683d8ea4e677ef1c9de4ccc7a Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1195/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c3b05e4e6f..2fbce42f60 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -466,15 +466,6 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, - |transfer, log| { - log.write( - &get_pending_key(&transfer), - transfer.try_to_vec().unwrap(), - ) - .unwrap(); - BTreeSet::from([get_pending_key(&transfer)]) - }, - Expect::False, ); } From a12a68cfbc3284b0cfaa643c8cb509cf9ef8fb9b Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 20 Oct 2022 15:40:29 +0200 Subject: [PATCH 1196/1995] [fix]: Fixed some garbage created by rebasing --- shared/src/types/storage.rs | 39 +++---------------------------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 146d5cfb04..9fa5b65128 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,7 +20,7 @@ use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::keccak::KeccakHash; +use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; #[allow(missing_docs)] @@ -643,41 +643,8 @@ impl KeySeg for Hash { impl KeySeg for KeccakHash { fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) + seg.try_into() + .map_err(|e: TryFromError| Error::ParseError(e.to_string())) } fn raw(&self) -> String { From ef7c7cac2750036828cd6c3ebf4a042b430eec59 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Oct 2022 17:12:09 +0200 Subject: [PATCH 1197/1995] [chore]: Rebased onto PR #573 --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 - shared/src/types/storage.rs | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 2fbce42f60..3588d76734 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -23,7 +23,6 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 9fa5b65128..eee9b5b847 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,22 +656,6 @@ impl KeySeg for KeccakHash { } } -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 19650ab09e73c4007602aaf8eefdfc935b843d93 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1198/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/types/storage.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index eee9b5b847..9fa5b65128 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,6 +656,22 @@ impl KeySeg for KeccakHash { } } +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 9f151a5eaeb7c23d784e70eb82384a1f83d32c68 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1199/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/types/storage.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 9fa5b65128..eee9b5b847 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -656,22 +656,6 @@ impl KeySeg for KeccakHash { } } -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 83371de35d9d6bc1f819642d328b85d543d9b4e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 13:34:48 +0100 Subject: [PATCH 1200/1995] Derive BorshSchema on vote extension types --- .../types/vote_extensions/ethereum_events.rs | 61 +++++-------------- .../vote_extensions/validator_set_update.rs | 61 +++++-------------- 2 files changed, 28 insertions(+), 94 deletions(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index da48c5146e..ac0993a3b7 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -11,14 +11,19 @@ use crate::types::ethereum_events::EthereumEvent; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; +/// Type alias for an [`EthereumEventsVext`]. +pub type Vext = EthereumEventsVext; + /// Represents a set of [`EthereumEvent`] instances /// seen by some validator. /// /// This struct will be created and signed over by each /// active validator, to be included as a vote extension at the end of a /// Tendermint PreCommit phase. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct Vext { +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthereumEventsVext { /// The block height for which this [`Vext`] was made. pub block_height: BlockHeight, /// TODO: the validator's address is temporarily being included @@ -47,28 +52,6 @@ impl Vext { } } -impl BorshSchema for Vext { - fn add_definitions_recursively( - definitions: &mut HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - BlockHeight::declaration(), - Address::declaration(), - Vec::::declaration(), - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "ethereum_events::Vext".into() - } -} - /// Aggregates an Ethereum event with the corresponding /// validators who saw this event. #[derive( @@ -86,10 +69,15 @@ pub struct MultiSignedEthEvent { pub signers: BTreeSet<(Address, BlockHeight)>, } +/// Type alias for an [`EthereumEventsVextDigest`]. +pub type VextDigest = EthereumEventsVextDigest; + /// Compresses a set of signed [`Vext`] instances, to save /// space on a block. -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct VextDigest { +#[derive( + Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct EthereumEventsVextDigest { /// The signatures and signing address of each [`Vext`] #[cfg(feature = "abcipp")] pub signatures: HashMap, @@ -101,27 +89,6 @@ pub struct VextDigest { pub events: Vec, } -impl BorshSchema for VextDigest { - fn add_definitions_recursively( - definitions: &mut HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - HashMap::::declaration(), - Vec::::declaration() - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "ethereum_events::VextDigest".into() - } -} - impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, last_height: BlockHeight) -> Vec> { diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 096092f916..4c3c75fab1 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -21,10 +21,15 @@ use crate::types::storage::Epoch; const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; +/// Type alias for a [`ValidatorSetUpdateVextDigest`]. +pub type VextDigest = ValidatorSetUpdateVextDigest; + /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct VextDigest { +#[derive( + Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct ValidatorSetUpdateVextDigest { #[cfg(feature = "abcipp")] /// A mapping from a validator address to a [`Signature`]. pub signatures: HashMap, @@ -39,27 +44,6 @@ pub struct VextDigest { pub voting_powers: VotingPowersMap, } -impl BorshSchema for VextDigest { - fn add_definitions_recursively( - definitions: &mut HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - HashMap::::declaration(), - VotingPowersMap::declaration() - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "validator_set_update::VextDigest".into() - } -} - impl VextDigest { /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, block_height: BlockHeight) -> Vec { @@ -101,9 +85,14 @@ impl VextDigest { /// an Ethereum key. pub type SignedVext = Signed; +/// Type alias for a [`ValidatorSetUpdateVext`]. +pub type Vext = ValidatorSetUpdateVext; + /// Represents a validator set update, for some new [`Epoch`]. -#[derive(Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize)] -pub struct Vext { +#[derive( + Eq, PartialEq, Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, +)] +pub struct ValidatorSetUpdateVext { /// The addresses of the validators in the new [`Epoch`], /// and their respective voting power. /// @@ -142,28 +131,6 @@ impl Vext { } } -impl BorshSchema for Vext { - fn add_definitions_recursively( - definitions: &mut HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - let fields = - borsh::schema::Fields::UnnamedFields(borsh::maybestd::vec![ - VotingPowersMap::declaration(), - Address::declaration(), - BlockHeight::declaration(), - ]); - let definition = borsh::schema::Definition::Struct { fields }; - Self::add_definition(Self::declaration(), definition, definitions); - } - - fn declaration() -> borsh::schema::Declaration { - "validator_set_update::Vext".into() - } -} - /// Container type for both kinds of Ethereum bridge addresses: /// /// - An address derived from a hot key. From 650088c99197bf3cd7498873ce5ff3c8334435fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Oct 2022 13:02:28 +0000 Subject: [PATCH 1201/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index a320c8c877..022eae49c9 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.921a2553de5b15305df8708f7bfba49d8174d0a1ff5eaded66fc9e47da8e16ea.wasm", + "tx_bond.wasm": "tx_bond.0cbb07bd84e1060bd53cd5910c650ea5bbc991f019f046797bacac8f19a73aaf.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.f3dc53d17cf0e698fcaab3c07039d90bf5dec9afc097ee3bc409427162ade54e.wasm", - "tx_ibc.wasm": "tx_ibc.1137cd5dfb5f7b1d2101f36d69c73f42c36d463683eb3f6db05293a78a05d31c.wasm", - "tx_init_account.wasm": "tx_init_account.184180f1b0f798b7929873165e9aaa0b9027bc122d3d7f66b8a5b9663eb886d5.wasm", - "tx_init_nft.wasm": "tx_init_nft.6fe64086099e6175df889e5c068bd5038d6f666288f6e97af0009aa11e4270b0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.48c78758fdcb20b20405be265ec0a18224b48e99ffb08651543db1f972761416.wasm", - "tx_init_validator.wasm": "tx_init_validator.610a72775cbdd0b5e34e71eca29545acf5dcf6b16ab887e778d75d8fc5ab086b.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.3e67ffb6b3c974df2c74479ad49d97b6e8301ef7b369dceeb02223c771a4bf65.wasm", - "tx_transfer.wasm": "tx_transfer.c31c6f1b912a5cf161c2b2623044a27b2b6ee37ef226a0ae3c9b6ec75e95d9a9.wasm", - "tx_unbond.wasm": "tx_unbond.247505c2aee9e7fabf928f436f9220f6a09220d0276c6cdab30eae10baededbb.wasm", - "tx_update_vp.wasm": "tx_update_vp.308c3066cc935b84ddb6e884008acacedaca6cb2fd302530fde0faa712672f73.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8e28f8c76eaebc1302247e34bc9c7f2744495dc802934cfb309b825c8e5d31d4.wasm", - "tx_withdraw.wasm": "tx_withdraw.8ce1e6063155e2876f1c2044c9f09d643b56a55a2b9be95cf41452b4ebdfb20c.wasm", - "vp_nft.wasm": "vp_nft.957258593d899af1d6368afdefb6f158a65da28c6656a2b901c5cf97dd1f23e4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.eaad3bffc9b20a831db37d18c82930e037ea079e7152b7e683eff8b98dacd7bf.wasm", - "vp_token.wasm": "vp_token.a4cd361ad211e05c691eab70127813e348216f6a793c2566a1f5ee2f88256596.wasm", - "vp_user.wasm": "vp_user.76d5d421abf1a78c55045ba905c8330cee12cbddf03c01f41bb46b7b24bbb1a3.wasm" + "tx_from_intent.wasm": "tx_from_intent.d6a4040e4871f51caa2c7252fd1c81e798f6f1df1bf508d67e732cac75a6c7d5.wasm", + "tx_ibc.wasm": "tx_ibc.3b870866443b6d95078f1ae7c1922dcdde2c3c4afaea04797e978579bac98492.wasm", + "tx_init_account.wasm": "tx_init_account.8264bea21394afae8ee2e124885d4b5221b61bf9ad0e36d5bfa61511a8037ed1.wasm", + "tx_init_nft.wasm": "tx_init_nft.d5caba1abbc2cf010f55fb2a9acdb3547421282307b912738426ee46ccf61f65.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.fd9d9fa15a472ce43991b5d34f47423e7731b82ff66780f277577e3d89049595.wasm", + "tx_init_validator.wasm": "tx_init_validator.b54c7f0dc0bcbb696f8f6c9ee931e96292dbcf2ad1eb21ebc09f9542dbe1509c.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.81ebf2117957adfdc26c55c31e5e2f5a75f7ed3446121cddb05fa21727df1a1c.wasm", + "tx_transfer.wasm": "tx_transfer.44b0dfe3984aaa9a8596fda1ba7f58c9338b269a5e73276113034eb53f0a2b5a.wasm", + "tx_unbond.wasm": "tx_unbond.6168c3c53f29856526d7d46526f8f6acf59d9b336d7fc328b6d65cb529043e88.wasm", + "tx_update_vp.wasm": "tx_update_vp.f01e33f9de91690ab4a8a70369bf0b7fd97ad72bdc601d0f5b7521893e2ca3f1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6cd376f913771ed75da9e50cad2f1412c85318b48fc54fd6d7c2cbb1f71b201c.wasm", + "tx_withdraw.wasm": "tx_withdraw.172af39fd0ff8ca7b3930bf98425336dfac1158ad2d47df5a140c529877f89af.wasm", + "vp_nft.wasm": "vp_nft.de30d66996183e2458e18b35a1b245d1a6c894dbc6f8dcf913832144d51c331a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4475fd9ad16ddcd39e39a6317e6c450e2a14055799472b2a0a3068576ac653f6.wasm", + "vp_token.wasm": "vp_token.27dda715efb921bf2fe232159782eeea00a81deb450bdf79e4e1d9b3e181bd47.wasm", + "vp_user.wasm": "vp_user.98a2c2d23c00ee72e5bc8c2b426ed5372195bcf4ed7c14b2a507d9fa2434824e.wasm" } \ No newline at end of file From 63c3d40d94487671c1fc20c68ad4ad1c76f3d779 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Oct 2022 13:02:29 +0000 Subject: [PATCH 1202/1995] [ci] wasm checksums update --- wasm/checksums.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 022eae49c9..e6c03957eb 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.0cbb07bd84e1060bd53cd5910c650ea5bbc991f019f046797bacac8f19a73aaf.wasm", + "tx_bond.wasm": "tx_bond.bff6b2424d05de6dd951fd2d1660119768ca6896556e6f54d106b6aeec61f4ae.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.d6a4040e4871f51caa2c7252fd1c81e798f6f1df1bf508d67e732cac75a6c7d5.wasm", - "tx_ibc.wasm": "tx_ibc.3b870866443b6d95078f1ae7c1922dcdde2c3c4afaea04797e978579bac98492.wasm", - "tx_init_account.wasm": "tx_init_account.8264bea21394afae8ee2e124885d4b5221b61bf9ad0e36d5bfa61511a8037ed1.wasm", - "tx_init_nft.wasm": "tx_init_nft.d5caba1abbc2cf010f55fb2a9acdb3547421282307b912738426ee46ccf61f65.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.fd9d9fa15a472ce43991b5d34f47423e7731b82ff66780f277577e3d89049595.wasm", - "tx_init_validator.wasm": "tx_init_validator.b54c7f0dc0bcbb696f8f6c9ee931e96292dbcf2ad1eb21ebc09f9542dbe1509c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.81ebf2117957adfdc26c55c31e5e2f5a75f7ed3446121cddb05fa21727df1a1c.wasm", - "tx_transfer.wasm": "tx_transfer.44b0dfe3984aaa9a8596fda1ba7f58c9338b269a5e73276113034eb53f0a2b5a.wasm", - "tx_unbond.wasm": "tx_unbond.6168c3c53f29856526d7d46526f8f6acf59d9b336d7fc328b6d65cb529043e88.wasm", + "tx_from_intent.wasm": "tx_from_intent.802709899f297a6e7574e61cee92a9b73d4f1d78db8b0fecd6f71aee10a62c1b.wasm", + "tx_ibc.wasm": "tx_ibc.3df9ad5e43985b6a0d73429534b5bbacc8f4c5b067b65cc6c9517ad76d575d73.wasm", + "tx_init_account.wasm": "tx_init_account.814d24ec3e149c141cb90293f32b7d9229cf2adcc21eb64763cebefa30651cb0.wasm", + "tx_init_nft.wasm": "tx_init_nft.ca7a4085bfa8935329e9c72814c9f468499f0b4bdc32722aef69b31701f554bb.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4201a913c38f90d9c7ed4a780f77eff359e0f3b8d20f0220237f72c06461e4a5.wasm", + "tx_init_validator.wasm": "tx_init_validator.2966562a461dd8bccceb23277f96f04c00eb7fccb6e122f31b070a2ae5170340.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.8c344161a360b130a94ba502f4e4fe03bc9b30b5835db23b1920043eb743c641.wasm", + "tx_transfer.wasm": "tx_transfer.a5026af845ccaa564a7a45b83362f1ef527254f66228217811819dd2786c3172.wasm", + "tx_unbond.wasm": "tx_unbond.c3af1a051ec4f06a719c654af4aa483934e114af0dc3618a6d3f80f443f2aac3.wasm", "tx_update_vp.wasm": "tx_update_vp.f01e33f9de91690ab4a8a70369bf0b7fd97ad72bdc601d0f5b7521893e2ca3f1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.6cd376f913771ed75da9e50cad2f1412c85318b48fc54fd6d7c2cbb1f71b201c.wasm", - "tx_withdraw.wasm": "tx_withdraw.172af39fd0ff8ca7b3930bf98425336dfac1158ad2d47df5a140c529877f89af.wasm", - "vp_nft.wasm": "vp_nft.de30d66996183e2458e18b35a1b245d1a6c894dbc6f8dcf913832144d51c331a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4475fd9ad16ddcd39e39a6317e6c450e2a14055799472b2a0a3068576ac653f6.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.36a43ac9488f36b3de7dc3cd1a7e7c6190e01258e1d46a6650c799d0a066b15a.wasm", + "tx_withdraw.wasm": "tx_withdraw.443820132a7af051215248d47a4e5636ed802ed35de9b634a9163321e26f0151.wasm", + "vp_nft.wasm": "vp_nft.cebe4febc075da02a6a9c37dbc8fc0a3495c644c37a4e0b21b55ea312f97c555.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c1018973f87dfe397cd7744a6371e8b82e3cfdfed1ac24c42601d17d37f87035.wasm", "vp_token.wasm": "vp_token.27dda715efb921bf2fe232159782eeea00a81deb450bdf79e4e1d9b3e181bd47.wasm", - "vp_user.wasm": "vp_user.98a2c2d23c00ee72e5bc8c2b426ed5372195bcf4ed7c14b2a507d9fa2434824e.wasm" + "vp_user.wasm": "vp_user.069f5eb052ee0507a1b2141cdd92c4f100ffe6a77f071c166bf3ed3bc7ac0d5d.wasm" } \ No newline at end of file From 368225e4549d4534bb85eb10ed5da07bd9ca374c Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 15:30:10 +0200 Subject: [PATCH 1203/1995] [feat]: Bridge pool vp now checks that funds to be transferred are escrowed. Needs tests --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 136 +++++++++++++++++- .../ledger/eth_bridge/storage/bridge_pool.rs | 17 +++ shared/src/ledger/eth_bridge/vp/mod.rs | 27 +++- shared/src/types/eth_bridge_pool.rs | 2 + 4 files changed, 169 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..8ae516d406 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -17,6 +17,8 @@ use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; +use crate::ledger::eth_bridge::storage::wrapped_erc20s; +use crate::ledger::eth_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; @@ -116,11 +118,29 @@ where }; let pending_key = get_pending_key(&transfer); + // check that transfer is not already in the pool + match (&self.ctx).read_pre(&pending_key) { + Ok(Some(_)) => { + tracing::debug!( + "Rejecting transaction as the transfer is already in the \ + Ethereum bridge pool." + ); + return Ok(false); + } + Err(_) => { + return Err(eyre!( + "Could not read the storage key associated with the \ + transfer." + ) + .into()); + } + _ => {} + } for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool: {}.\n \ + incorrect key in the Ethereum bridge pool: {}.\n \ Expected key: {}", key, pending_key @@ -131,12 +151,12 @@ where let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( "Rejecting transaction as the transfer wasn't added to the \ - pending transfers" + pool of pending transfers" ))?; if pending != transfer { tracing::debug!( - "An incorrect transfer was added to the pool: {:?}.\n \ - Expected: {:?}", + "An incorrect transfer was added to the Ethereum bridge pool: \ + {:?}.\n Expected: {:?}", transfer, pending ); @@ -164,7 +184,9 @@ where return Ok(false); } } else { - tracing::debug!("The bridge pools escrow was not credited."); + tracing::debug!( + "The Ethereum bridge pool's gas escrow was not credited." + ); return Ok(false); } tracing::info!( @@ -172,6 +194,40 @@ where transfer ); + // check that the assets to be transferred were escrowed + let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); + let owner_key = asset_key.balance(&transfer.transfer.sender); + let escrow_key = asset_key.balance(&BRIDGE_POOL_ADDRESS); + if keys_changed.contains(&owner_key) + && keys_changed.contains(&escrow_key) + { + match check_balance_changes( + &self.ctx, + (&escrow_key).try_into().expect("This should not fail"), + (&owner_key).try_into().expect("This should not fail"), + ) { + Ok(Some(delta)) + if delta + == ( + transfer.transfer.sender, + transfer.transfer.amount, + ) => {} + _ => { + tracing::debug!( + "The assets of the transfer were not properly \ + escrowed into the Ethereum bridge pool." + ); + return Ok(false); + } + } + } else { + tracing::debug!( + "The assets of the transfer were not properly escrowed into \ + the Ethereum bridge pool." + ); + return Ok(false); + } + Ok(true) } } @@ -226,6 +282,7 @@ mod test_bridge_pool_vp { PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), nonce: 0u64.into(), @@ -308,8 +365,9 @@ mod test_bridge_pool_vp { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 100.into(), + amount: 0.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -491,6 +549,7 @@ mod test_bridge_pool_vp { let t = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 10u64.into(), @@ -519,6 +578,7 @@ mod test_bridge_pool_vp { let t = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 10u64.into(), @@ -558,6 +618,69 @@ mod test_bridge_pool_vp { ); } + /// Test that adding a transfer to the pool + /// that is already in the pool fails. + #[test] + fn test_adding_transfer_twice_fails() { + // setup + let mut write_log = new_writelog(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = initial_pool(); + // change the payers account + let bertha_account_key = balance_key(&xan(), &bertha_address()); + let new_bertha_balance = (Amount::from(BERTHA_WEALTH) - GAS_FEE.into()) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&bertha_account_key, new_bertha_balance) + .expect("Test failed"); + // change the escrow account + let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let new_escrow_balance = (Amount::from(ESCROWED_AMOUNT) + + GAS_FEE.into()) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&escrow, new_escrow_balance) + .expect("Test failed"); + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let verifiers = BTreeSet::default(); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + assert!(!res.expect("Test failed")); + } + /// Test that a transfer added to the pool with zero gas fees /// is rejected. #[test] @@ -575,6 +698,7 @@ mod test_bridge_pool_vp { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 1u64.into(), diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 865c4a42ec..befe3d044a 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -349,6 +349,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -372,6 +373,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -400,6 +402,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -434,6 +437,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -460,6 +464,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -490,6 +495,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -513,6 +519,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -548,6 +555,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -565,6 +573,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -596,6 +605,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), nonce: 0.into(), @@ -624,6 +634,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -653,6 +664,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -682,6 +694,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -709,6 +722,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -736,6 +750,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -763,6 +778,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -797,6 +813,7 @@ mod test_bridge_pool_tree { .map(|(addr, nonce)| PendingTransfer { transfer: TransferToEthereum { asset: EthAddress(addr), + sender: bertha_address(), recipient: EthAddress(addr), amount: Default::default(), nonce: nonce.into(), diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0e62f7270f..51ebface24 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -71,7 +71,8 @@ where Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), }; - let sender = match check_balance_changes(&self.ctx, key_a, key_b)? { + let (sender, _) = match check_balance_changes(&self.ctx, key_a, key_b)? + { Some(sender) => sender, None => return Ok(false), }; @@ -152,13 +153,13 @@ fn extract_valid_keys_changed( /// Checks that the balances at both `key_a` and `key_b` have changed by some /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return the `Address` of the owner of the balance which is decreasing, which -/// should be authorizing the balance change. -fn check_balance_changes( +/// return the `Address` of the owner of the balance which is decreasing, +/// as by how much it decreased, which should be authorizing the balance change. +pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, -) -> Result> { +) -> Result> { let (balance_a, balance_b) = match (key_a.suffix.clone(), key_b.suffix.clone()) { ( @@ -264,14 +265,26 @@ fn check_balance_changes( if balance_a_delta < 0 { if let wrapped_erc20s::KeyType::Balance { owner } = key_a.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_b_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } } else { assert!(balance_b_delta < 0); if let wrapped_erc20s::KeyType::Balance { owner } = key_b.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_a_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 904c2c7eec..6ae5be841c 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -27,6 +27,8 @@ pub struct TransferToEthereum { pub asset: EthAddress, /// The recipient address pub recipient: EthAddress, + /// The sender of the transfer + pub sender: Address, /// The amount to be transferred pub amount: Amount, /// a nonce for replay protection From 8c6a9580277011b5da44b7a3510e1194de959c37 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 15:31:39 +0200 Subject: [PATCH 1204/1995] [chore]: Formatting --- shared/src/types/key/secp256k1.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 4c9cb17c83..5e5278b06e 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -332,7 +332,6 @@ impl<'de> Deserialize<'de> for Signature { impl<'de> Visitor<'de> for ByteArrayVisitor { type Value = [u8; SIGNATURE_LENGTH]; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str(&format!( "an array of length {}", From 147ba8227b7a9962e09c4d25d6a04ac2a651eb6f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 14:42:11 +0100 Subject: [PATCH 1205/1995] Rename fvp to voting_power --- .../protocol/transactions/ethereum_events/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index 4503e70fb1..e5e5317113 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -176,16 +176,16 @@ where ); let mut votes = HashMap::default(); seen_by.iter().for_each(|(address, block_height)| { - let fvp = voting_powers + let voting_power = voting_powers .get(&(address.to_owned(), block_height.to_owned())) .unwrap(); - if let Some(already_present_fvp) = - votes.insert(address.to_owned(), fvp.to_owned()) + if let Some(already_present_voting_power) = + votes.insert(address.to_owned(), voting_power.to_owned()) { tracing::warn!( ?address, - ?already_present_fvp, - new_fvp = ?fvp, + ?already_present_voting_power, + new_voting_power = ?voting_power, "Validator voted more than once, arbitrarily using later value", ) } From d3150bc66120624abc24189faa0ac2dcc432ad85 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 26 Oct 2022 13:04:28 +0100 Subject: [PATCH 1206/1995] Backport shim fix --- .../lib/node/ledger/shell/process_proposal.rs | 44 ++++++---- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 83 +++++++++++-------- 2 files changed, 75 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 75cab7aa8c..9d02a02929 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -15,7 +15,7 @@ use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessPropos /// Contains stateful data about the number of vote extension /// digests found as protocol transactions in a proposed block. #[derive(Default)] -pub(crate) struct DigestCounters { +pub struct DigestCounters { /// The number of Ethereum events vote extensions found thus far. eth_ev_digest_num: usize, /// The number of validator set update vote extensions found thus far. @@ -43,7 +43,6 @@ where &self, req: RequestProcessProposal, ) -> ProcessProposal { - let mut tx_queue_iter = self.storage.tx_queue.iter(); tracing::info!( proposer = ?hex::encode(&req.proposer_address), height = req.height, @@ -51,19 +50,7 @@ where n_txs = req.txs.len(), "Received block proposal", ); - // the number of vote extension digests included in the block proposal - let mut counters = DigestCounters::default(); - let tx_results: Vec<_> = req - .txs - .iter() - .map(|tx_bytes| { - self.process_single_tx( - tx_bytes, - &mut tx_queue_iter, - &mut counters, - ) - }) - .collect(); + let (tx_results, counters) = self.process_proposed_txs(&req.txs); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. @@ -128,6 +115,31 @@ where } } + /// Calculates what the [`TxResult`]s would be for the transactions in a + /// proposed block, as well as counting the number of digest transactions + /// present. `ProcessProposal` should be able to make a decision on whether + /// a proposed block is acceptable or not based solely on what this function + /// returns. + pub fn process_proposed_txs( + &self, + txs: &[Vec], + ) -> (Vec, DigestCounters) { + let mut tx_queue_iter = self.storage.tx_queue.iter(); + // the number of vote extension digests included in the block proposal + let mut counters = DigestCounters::default(); + let tx_results: Vec<_> = txs + .iter() + .map(|tx_bytes| { + self.process_proposed_tx( + tx_bytes, + &mut tx_queue_iter, + &mut counters, + ) + }) + .collect(); + (tx_results, counters) + } + /// Validates a list of vote extensions, included in PrepareProposal. /// /// If a vote extension is [`Some`], then it was validated properly, @@ -215,7 +227,7 @@ 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<'a>( + pub(crate) fn process_proposed_tx<'a>( &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 78a0531cd5..d20eec22cc 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -8,11 +8,17 @@ use futures::future::FutureExt; use namada::types::ethereum_events::EthereumEvent; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; +#[cfg(not(feature = "abcipp"))] +use namada::types::storage::BlockHash; +#[cfg(not(feature = "abcipp"))] +use namada::types::transaction::hash_tx; use tokio::sync::mpsc::{Receiver, UnboundedSender}; use tower::Service; use super::super::Shell; use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; +#[cfg(not(feature = "abcipp"))] +use super::abcipp_shim_types::shim::TxBytes; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; #[cfg(not(feature = "abcipp"))] @@ -27,7 +33,8 @@ pub struct AbcippShim { service: Shell, #[cfg(not(feature = "abcipp"))] begin_block_request: Option, - processed_txs: Vec, + #[cfg(not(feature = "abcipp"))] + delivered_txs: Vec, shell_recv: std::sync::mpsc::Receiver<( Req, tokio::sync::oneshot::Sender>, @@ -62,7 +69,8 @@ impl AbcippShim { ), #[cfg(not(feature = "abcipp"))] begin_block_request: None, - processed_txs: vec![], + #[cfg(not(feature = "abcipp"))] + delivered_txs: vec![], shell_recv, }, AbciService { shell_send }, @@ -72,12 +80,8 @@ impl AbcippShim { #[cfg(not(feature = "abcipp"))] /// Get the hash of the txs in the block pub fn get_hash(&self) -> Hash { - use namada::types::transaction::hash_tx; - let bytes: Vec = self - .processed_txs - .iter() - .flat_map(|processed| processed.tx.clone()) - .collect(); + let bytes: Vec = + self.delivered_txs.iter().flat_map(Clone::clone).collect(); hash_tx(bytes.as_slice()) } @@ -86,32 +90,28 @@ 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) => { - let response = - Ok(Resp::ProcessProposal((&resp).into())); - for (result, tx) in resp - .tx_results - .into_iter() - .zip(txs.into_iter()) - { - self.processed_txs - .push(ProcessedTx { tx, result }); - } - response - } - _ => 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).into())) + } + _ => unreachable!(), + }), #[cfg(feature = "abcipp")] Req::FinalizeBlock(block) => { - let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.processed_txs); + let unprocessed_txs = block.txs.clone(); + let (processing_results, _) = + self.service.process_proposed_txs(&block.txs); + let mut txs = Vec::with_capacity(unprocessed_txs.len()); + for (result, tx) in processing_results + .into_iter() + .zip(unprocessed_txs.into_iter()) + { + txs.push(ProcessedTx { tx, result }); + } let mut finalize_req: FinalizeBlock = block.into(); finalize_req.txs = txs; self.service @@ -131,12 +131,23 @@ impl AbcippShim { Ok(Resp::BeginBlock(Default::default())) } #[cfg(not(feature = "abcipp"))] - Req::DeliverTx(_) => Ok(Resp::DeliverTx(Default::default())), + Req::DeliverTx(tx) => { + self.delivered_txs.push(tx.tx); + Ok(Resp::DeliverTx(Default::default())) + } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - use namada::types::storage::BlockHash; - let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.processed_txs); + let (processing_results, _) = + self.service.process_proposed_txs(&self.delivered_txs); + let mut txs = Vec::with_capacity(self.delivered_txs.len()); + let mut delivered = vec![]; + std::mem::swap(&mut self.delivered_txs, &mut delivered); + for (result, tx) in processing_results + .into_iter() + .zip(delivered.into_iter()) + { + txs.push(ProcessedTx { tx, result }); + } let mut end_block_request: FinalizeBlock = self.begin_block_request.take().unwrap().into(); let hash = self.get_hash(); From 0ba9e4f6a124d3d195bdae6878526180b713aed1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 12:42:25 +0000 Subject: [PATCH 1207/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index e6c03957eb..a112ed3a63 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.bff6b2424d05de6dd951fd2d1660119768ca6896556e6f54d106b6aeec61f4ae.wasm", + "tx_bond.wasm": "tx_bond.5536bed92d94b1aaaa82fd3531b7d4fedb00ae1f39fede7ef1634e070e5f4455.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.802709899f297a6e7574e61cee92a9b73d4f1d78db8b0fecd6f71aee10a62c1b.wasm", - "tx_ibc.wasm": "tx_ibc.3df9ad5e43985b6a0d73429534b5bbacc8f4c5b067b65cc6c9517ad76d575d73.wasm", - "tx_init_account.wasm": "tx_init_account.814d24ec3e149c141cb90293f32b7d9229cf2adcc21eb64763cebefa30651cb0.wasm", - "tx_init_nft.wasm": "tx_init_nft.ca7a4085bfa8935329e9c72814c9f468499f0b4bdc32722aef69b31701f554bb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4201a913c38f90d9c7ed4a780f77eff359e0f3b8d20f0220237f72c06461e4a5.wasm", - "tx_init_validator.wasm": "tx_init_validator.2966562a461dd8bccceb23277f96f04c00eb7fccb6e122f31b070a2ae5170340.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.8c344161a360b130a94ba502f4e4fe03bc9b30b5835db23b1920043eb743c641.wasm", - "tx_transfer.wasm": "tx_transfer.a5026af845ccaa564a7a45b83362f1ef527254f66228217811819dd2786c3172.wasm", - "tx_unbond.wasm": "tx_unbond.c3af1a051ec4f06a719c654af4aa483934e114af0dc3618a6d3f80f443f2aac3.wasm", - "tx_update_vp.wasm": "tx_update_vp.f01e33f9de91690ab4a8a70369bf0b7fd97ad72bdc601d0f5b7521893e2ca3f1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.36a43ac9488f36b3de7dc3cd1a7e7c6190e01258e1d46a6650c799d0a066b15a.wasm", - "tx_withdraw.wasm": "tx_withdraw.443820132a7af051215248d47a4e5636ed802ed35de9b634a9163321e26f0151.wasm", - "vp_nft.wasm": "vp_nft.cebe4febc075da02a6a9c37dbc8fc0a3495c644c37a4e0b21b55ea312f97c555.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c1018973f87dfe397cd7744a6371e8b82e3cfdfed1ac24c42601d17d37f87035.wasm", - "vp_token.wasm": "vp_token.27dda715efb921bf2fe232159782eeea00a81deb450bdf79e4e1d9b3e181bd47.wasm", - "vp_user.wasm": "vp_user.069f5eb052ee0507a1b2141cdd92c4f100ffe6a77f071c166bf3ed3bc7ac0d5d.wasm" + "tx_from_intent.wasm": "tx_from_intent.7243e31594d0ca5e656228bed5c84359a3d1dbfd24f1656eb9b52b0266ffaa47.wasm", + "tx_ibc.wasm": "tx_ibc.1c09d3f083a91f28708b7f09698c311e609b712da683dcbcd40e7267a1cf2adc.wasm", + "tx_init_account.wasm": "tx_init_account.95b3a1e4867160eb91f9a7812f53adf33bb44f04b9eb3f310854b7a1d7a26e77.wasm", + "tx_init_nft.wasm": "tx_init_nft.022446ca174c6ccb1e7818224ebc30515123e0bc148ec8dd59ce5b47f7447bd7.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8f93bb139e932ba24871efe22cb62177c1c7133f8957965c041accad3d04516e.wasm", + "tx_init_validator.wasm": "tx_init_validator.916a8c6a6008d5f35341e58ccafd8169b7596464f1f3253eea96c277f167fe1b.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9e12869d07ac36a9fdddf69710f2f6905c55bb4dc49c885e87d47b3d6c389ec2.wasm", + "tx_transfer.wasm": "tx_transfer.48c8bfdd119c0753a03c65764b0e099f6647dd158490fb5e04eab171c27b422e.wasm", + "tx_unbond.wasm": "tx_unbond.b2851a1eefd2e781411e495ef52ce6148893c02e06d0160298871fd231e299b4.wasm", + "tx_update_vp.wasm": "tx_update_vp.5b9786f039324205c464fec6c795d84ea3ec9b24d3c6c63e7462444811237855.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.31c96b1b171d0020c0788170f9677606589324024a90e3d532da2b9a2cedf57e.wasm", + "tx_withdraw.wasm": "tx_withdraw.5e360d269c8d3ae574ae9cbf301e1d9eed8c3ad6c103deb5e1c46ddb35af5708.wasm", + "vp_nft.wasm": "vp_nft.29b7fb0b2c247bb5389166e82707abd256177f276bbaf40ccd201ecad8039bad.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e1b7805802df2871a93f584e4df97a4584cda9d58435b14c73791fe18d93e4f4.wasm", + "vp_token.wasm": "vp_token.8332429c84d70ab6614b20a41d208ac61126141d302319a99269f6fc9e08053a.wasm", + "vp_user.wasm": "vp_user.795a1dccb7b18f0abeb07161d07e31ce5d64221a52e85098627ab60115462ba4.wasm" } \ No newline at end of file From f66a4ba2f41795a6f4181398b8920b941a37740c Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Oct 2022 15:21:40 +0200 Subject: [PATCH 1208/1995] [feat]: Added checks the bridge pool vp that erc20 tokens are escrowed to its account --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 319 +++++++++++++----- 1 file changed, 242 insertions(+), 77 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 8ae516d406..331b80088e 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -255,9 +255,30 @@ mod test_bridge_pool_vp { use crate::vm::WasmCacheRwAccess; /// The amount of NAM Bertha has + const ASSET: EthAddress = EthAddress([0; 20]); const BERTHA_WEALTH: u64 = 1_000_000; + const BERTHA_TOKENS: u64 = 10_000; const ESCROWED_AMOUNT: u64 = 1_000; + const ESCROWED_TOKENS: u64 = 1_000; const GAS_FEE: u64 = 100; + const TOKENS: u64 = 100; + + /// A set of balances for an address + struct Balance { + owner: Address, + balance: Amount, + token: Amount, + } + + impl Balance { + fn new(address: Address) -> Self { + Self { + owner: address, + balance: 0.into(), + token: 0.into(), + } + } + } /// An established user address for testing & development fn bertha_address() -> Address { @@ -281,7 +302,7 @@ mod test_bridge_pool_vp { fn initial_pool() -> PendingTransfer { PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), @@ -294,33 +315,77 @@ mod test_bridge_pool_vp { } } - /// Create a new storage + /// Create a writelog representing storage before a transfer is added to the + /// pool. fn new_writelog() -> WriteLog { let mut writelog = WriteLog::default(); - // setup the bridge pool storage + // setup the initial bridge pool storage writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) - .unwrap(); + .expect("Test failed"); let transfer = initial_pool(); writelog .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) - .unwrap(); - let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let amount: Amount = ESCROWED_AMOUNT.into(); - writelog - .write(&escrow_key, amount.try_to_vec().unwrap()) - .unwrap(); - - // setup a user with a balance - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let bertha_wealth: Amount = BERTHA_WEALTH.into(); - writelog - .write(&bertha_account_key, bertha_wealth.try_to_vec().unwrap()) - .unwrap(); + .expect("Test failed"); + // set up a user with a balance + update_balances( + &mut writelog, + Balance::new(bertha_address()), + SignedAmount::Positive(BERTHA_WEALTH.into()), + SignedAmount::Positive(BERTHA_TOKENS.into()), + ); + // set up the initial balances of the bridge pool + update_balances( + &mut writelog, + Balance::new(BRIDGE_POOL_ADDRESS), + SignedAmount::Positive(ESCROWED_AMOUNT.into()), + SignedAmount::Positive(ESCROWED_TOKENS.into()), + ); writelog.commit_tx(); writelog } + /// Update gas and token balances of an address and + /// return the keys changed + fn update_balances( + write_log: &mut WriteLog, + balance: Balance, + gas_delta: SignedAmount, + token_delta: SignedAmount, + ) -> BTreeSet { + // get the balance keys + let token_key = + wrapped_erc20s::Keys::from(&ASSET).balance(&balance.owner); + let account_key = balance_key(&xan(), &balance.owner); + + // update the balance of xan + let new_balance = match gas_delta { + SignedAmount::Positive(amount) => balance.balance + amount, + SignedAmount::Negative(amount) => balance.balance - amount, + } + .try_to_vec() + .expect("Test failed"); + + // update the balance of tokens + let new_token_balance = match token_delta { + SignedAmount::Positive(amount) => balance.token + amount, + SignedAmount::Negative(amount) => balance.token - amount, + } + .try_to_vec() + .expect("Test failed"); + + // write the changes to the log + write_log + .write(&account_key, new_balance) + .expect("Test failed"); + write_log + .write(&token_key, new_token_balance) + .expect("Test failed"); + + // return the keys changed + [account_key, token_key].into() + } + /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, @@ -345,6 +410,8 @@ mod test_bridge_pool_vp { /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( + payer_gas_delta: SignedAmount, + gas_escrow_delta: SignedAmount, payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, @@ -364,10 +431,10 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 0.into(), + amount: TOKENS.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -375,39 +442,35 @@ mod test_bridge_pool_vp { payer: bertha_address(), }, }; - // change the payers account - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let new_bertha_balance = match payer_delta { - SignedAmount::Positive(amount) => { - Amount::from(BERTHA_WEALTH) + amount - } - SignedAmount::Negative(amount) => { - Amount::from(BERTHA_WEALTH) - amount - } - } - .try_to_vec() - .expect("Test failed"); - write_log - .write(&bertha_account_key, new_bertha_balance) - .expect("Test failed"); - // change the escrow account - let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let new_escrow_balance = match escrow_delta { - SignedAmount::Positive(amount) => { - Amount::from(ESCROWED_AMOUNT) + amount - } - SignedAmount::Negative(amount) => { - Amount::from(ESCROWED_AMOUNT) - amount - } - } - .try_to_vec() - .expect("Test failed"); - write_log - .write(&escrow, new_escrow_balance) - .expect("Test failed"); - // add transfer to pool - let keys_changed = insert_transfer(transfer.clone(), &mut write_log); + let mut keys_changed = + insert_transfer(transfer.clone(), &mut write_log); + + // change Bertha's balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: bertha_address(), + balance: BERTHA_WEALTH.into(), + token: BERTHA_TOKENS.into(), + }, + payer_gas_delta, + payer_delta, + ); + keys_changed.append(&mut new_keys_changed); + + // change the bridge pool balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: BRIDGE_POOL_ADDRESS, + balance: ESCROWED_AMOUNT.into(), + token: ESCROWED_TOKENS.into(), + }, + gas_escrow_delta, + escrow_delta, + ); + keys_changed.append(&mut new_keys_changed); // create the data to be given to the vp let vp = BridgePoolVp { @@ -438,6 +501,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -457,6 +522,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -476,6 +543,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -495,6 +564,51 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that if the number of tokens debited + /// from one account does not equal the amount + /// credited the other, the tx is rejected + #[test] + fn test_incorrect_token_deltas() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(10.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that if the number of tokens transferred + /// is incorrect, the tx is rejected + #[test] + fn test_incorrect_tokens_escrowed() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(10.into()), + SignedAmount::Positive(10.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -514,6 +628,29 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that the amount of tokens escrowed in the + /// bridge pool is positive. + #[test] + fn test_escrowed_tokens_must_increase() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Positive(TOKENS.into()), + SignedAmount::Negative(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -533,6 +670,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), Expect::Error, ); @@ -545,6 +684,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -574,6 +715,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -603,6 +746,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -633,26 +778,9 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = initial_pool(); - // change the payers account - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let new_bertha_balance = (Amount::from(BERTHA_WEALTH) - GAS_FEE.into()) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&bertha_account_key, new_bertha_balance) - .expect("Test failed"); - // change the escrow account - let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let new_escrow_balance = (Amount::from(ESCROWED_AMOUNT) - + GAS_FEE.into()) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&escrow, new_escrow_balance) - .expect("Test failed"); // add transfer to pool - let keys_changed = { + let mut keys_changed = { write_log .write( &get_pending_key(&transfer), @@ -662,6 +790,32 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }; + // update Bertha's balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: bertha_address(), + balance: BERTHA_WEALTH.into(), + token: BERTHA_TOKENS.into(), + }, + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + ); + keys_changed.append(&mut new_keys_changed); + + // update the bridge pool balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: BRIDGE_POOL_ADDRESS, + balance: ESCROWED_AMOUNT.into(), + token: ESCROWED_TOKENS.into(), + }, + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Positive(TOKENS.into()), + ); + keys_changed.append(&mut new_keys_changed); + // create the data to be given to the vp let vp = BridgePoolVp { ctx: setup_ctx(&tx, &storage, &write_log), @@ -697,10 +851,10 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 100.into(), + amount: 0.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -709,12 +863,23 @@ mod test_bridge_pool_vp { }, }; - write_log - .write( - &get_pending_key(&transfer), - transfer.try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); + // add transfer to pool + let mut keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 0 tokens + keys_changed.insert( + wrapped_erc20s::Keys::from(&ASSET).balance(&bertha_address()), + ); + keys_changed.insert( + wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), + ); // create the data to be given to the vp let vp = BridgePoolVp { From 21837aa301682ed53a24345a39bc4f6f68cdbf16 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 15:30:13 +0200 Subject: [PATCH 1209/1995] Update shared/src/types/eth_bridge_pool.rs Co-authored-by: James --- shared/src/types/eth_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 33bf927823..fc4e021dfd 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -136,7 +136,7 @@ impl Encode<2> for MultiSignedMerkleRoot { pub struct RelayProof { /// Information about the signing validators pub validator_args: ValidatorSetArgs, - /// A merkle root signed by ta quorum of validators + /// A merkle root signed by a quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, From 4872e684fa013cce3c37c2f2a33961beaaff5977 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Oct 2022 16:05:04 +0200 Subject: [PATCH 1210/1995] [fix]: Adding changes from code review --- apps/src/lib/node/ledger/shell/queries.rs | 130 ++++++++---------- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 - shared/src/types/eth_bridge_pool.rs | 10 +- shared/src/types/key/secp256k1.rs | 14 +- 4 files changed, 81 insertions(+), 75 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index ae505f903b..41411cf7bb 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -360,43 +360,36 @@ where /// Read the current contents of the Ethereum bridge /// pool. fn read_ethereum_bridge_pool(&self) -> response::Query { - if let Ok(Some(stores)) = self + let stores = self .storage .db .read_merkle_tree_stores(self.storage.last_height) - { - let store = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store, - _ => unreachable!(), - }; - let transfers: Vec = store - .iter() - .map(|hash| { - let res = self - .storage - .read(&get_key_from_hash(hash)) - .unwrap() - .0 - .unwrap(); - BorshDeserialize::try_from_slice(res.as_slice()).unwrap() - }) - .collect(); - response::Query { - code: 0, - value: transfers.try_to_vec().unwrap(), - ..Default::default() - } - } else { - response::Query { - code: 1, - log: "Could not retrieve the Ethereum bridge pool for the \ - latest height" - .into(), - info: "Could not retrieve the Ethereum bridge pool for the \ - latest height" - .into(), - ..Default::default() - } + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing block \ + height", + ); + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, + _ => unreachable!(), + }; + + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); + response::Query { + code: 0, + value: transfers.try_to_vec().unwrap(), + ..Default::default() } } @@ -410,43 +403,40 @@ where >::try_from_slice(request_bytes.as_slice()) { // get the latest signed merkle root of the Ethereum bridge pool - let signed_root: MultiSignedMerkleRoot = - match self.storage.read(&get_signed_root_key()) { - Ok((Some(bytes), _)) => { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .unwrap() - } - _ => { - return response::Query { - code: 1, - log: "Could not deserialize the signed Ethereum \ - bridge pool merkle root" - .into(), - info: "Could not deserialize the signed Ethereum \ - bridge pool merkle root" - .into(), - ..Default::default() - }; - } - }; - // get the merkle tree corresponding to the above root. - let tree = if let Ok(Some(stores)) = - self.storage.db.read_merkle_tree_stores(signed_root.height) + let signed_root: MultiSignedMerkleRoot = match self + .storage + .read(&get_signed_root_key()) + .expect("Reading the database should not faile") { - MerkleTree::::new(stores) - } else { - return response::Query { - code: 1, - log: "Could not retrieve the Ethereum bridge pool for the \ - latest signed root" - .into(), - info: "Could not retrieve the Ethereum bridge pool for \ - the latest signed root" - .into(), - ..Default::default() - }; + (Some(bytes), _) => { + BorshDeserialize::try_from_slice(bytes.as_slice()).unwrap() + } + _ => { + return response::Query { + code: 1, + log: "No signed root for the Ethereum bridge pool \ + exists in storage." + .into(), + info: "No signed root for the Ethereum bridge pool \ + exists in storage." + .into(), + ..Default::default() + }; + } }; + // get the merkle tree corresponding to the above root. + let tree = MerkleTree::::new( + self.storage + .db + .read_merkle_tree_stores(signed_root.height) + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing \ + block height", + ), + ); + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); match tree.get_sub_tree_existence_proof( @@ -1178,7 +1168,7 @@ mod test_queries { // create a signed Merkle root for this pool let signed_root = MultiSignedMerkleRoot { - sigs: vec![], + sigs: Default::default(), root: transfer.keccak256(), height: Default::default(), }; @@ -1256,7 +1246,7 @@ mod test_queries { // create a signed Merkle root for this pool let signed_root = MultiSignedMerkleRoot { - sigs: vec![], + sigs: Default::default(), root: transfer.keccak256(), height: Default::default(), }; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index c44628345f..362feed8b2 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -36,7 +36,6 @@ pub fn get_key_from_hash(hash: &KeccakHash) -> Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), hash.to_db_key(), - ], } } @@ -129,7 +128,6 @@ impl BridgePoolTree { /// Return the root as a [`struct@Hash`] type. pub fn root(&self) -> KeccakHash { self.root.clone() - } /// Get a reference to the backing store diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index fc4e021dfd..f7608da093 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,5 +1,7 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool +use std::collections::BTreeSet; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; @@ -12,6 +14,9 @@ use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; +/// A namespace used in our Ethereuem smart contracts +const NAMESPACE: &str = "transfer"; + /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. #[derive( @@ -61,8 +66,9 @@ pub struct PendingTransfer { impl Encode<8> for PendingTransfer { fn tokenize(&self) -> [Token; 8] { + // TODO: This version should be looked up from storage let version = Token::Uint(1.into()); - let namespace = Token::String("transfer".into()); + let namespace = Token::String(NAMESPACE.into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); @@ -111,7 +117,7 @@ pub struct GasFee { #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedMerkleRoot { /// The signatures from validators - pub sigs: Vec, + pub sigs: BTreeSet, /// The Merkle root being signed pub root: KeccakHash, /// The block height at which this root was valid diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 5e5278b06e..16cf7c1cda 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -1,5 +1,6 @@ //! secp256k1 keys and related functionality +use std::cmp::Ordering; use std::fmt; use std::fmt::{Debug, Display}; use std::hash::{Hash, Hasher}; @@ -443,7 +444,18 @@ impl Hash for Signature { impl PartialOrd for Signature { fn partial_cmp(&self, other: &Self) -> Option { - self.0.serialize().partial_cmp(&other.0.serialize()) + match self.0.serialize().partial_cmp(&other.0.serialize()) { + Some(Ordering::Equal) => { + self.1.serialize().partial_cmp(&other.1.serialize()) + } + res => res, + } + } +} + +impl Ord for Signature { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() } } From b42481e58388885ecffb4700a40341667b1e3353 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 16:05:53 +0200 Subject: [PATCH 1211/1995] Update shared/src/types/vote_extensions/validator_set_update.rs Co-authored-by: Tiago Carvalho --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 24e0145ac5..7578d246c2 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -329,7 +329,7 @@ impl Encode<1> for ValidatorSetArgs { .collect(), ); let nonce = Token::Uint(self.nonce.clone().into()); - [Token::FixedArray(vec![addrs, powers, nonce])] + [Token::Tuple(vec![addrs, powers, nonce])] } } From db422dcc4837e65f291f804740462dc460c21b48 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 16:06:00 +0200 Subject: [PATCH 1212/1995] Update shared/src/types/key/secp256k1.rs Co-authored-by: Tiago Carvalho --- shared/src/types/key/secp256k1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 16cf7c1cda..f23e2ec820 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -431,7 +431,7 @@ impl Encode<1> for Signature { let r = Token::FixedBytes(sig_serialized[..32].to_vec()); let s = Token::FixedBytes(sig_serialized[32..].to_vec()); let v = Token::FixedBytes(vec![self.1.serialize()]); - [Token::FixedArray(vec![r, s, v])] + [Token::Tuple(vec![r, s, v])] } } From 0fe5edf00b0afbd38eb7c6a174375385b47bb16b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 26 Oct 2022 17:04:39 +0100 Subject: [PATCH 1213/1995] Rename process -> checked --- apps/src/lib/node/ledger/shell/process_proposal.rs | 10 +++++----- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9d02a02929..e4203860c6 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -50,7 +50,7 @@ where n_txs = req.txs.len(), "Received block proposal", ); - let (tx_results, counters) = self.process_proposed_txs(&req.txs); + let (tx_results, counters) = self.check_proposal(&req.txs); // We should not have more than one `ethereum_events::VextDigest` in // a proposal from some round's leader. @@ -115,12 +115,12 @@ where } } - /// Calculates what the [`TxResult`]s would be for the transactions in a + /// Checks what the [`TxResult`]s would be for the transactions in a /// proposed block, as well as counting the number of digest transactions /// present. `ProcessProposal` should be able to make a decision on whether /// a proposed block is acceptable or not based solely on what this function /// returns. - pub fn process_proposed_txs( + pub fn check_proposal( &self, txs: &[Vec], ) -> (Vec, DigestCounters) { @@ -130,7 +130,7 @@ where let tx_results: Vec<_> = txs .iter() .map(|tx_bytes| { - self.process_proposed_tx( + self.check_proposal_tx( tx_bytes, &mut tx_queue_iter, &mut counters, @@ -227,7 +227,7 @@ 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_proposed_tx<'a>( + pub(crate) fn check_proposal_tx<'a>( &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index d20eec22cc..5851441a0e 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -104,7 +104,7 @@ impl AbcippShim { Req::FinalizeBlock(block) => { let unprocessed_txs = block.txs.clone(); let (processing_results, _) = - self.service.process_proposed_txs(&block.txs); + self.service.check_proposal(&block.txs); let mut txs = Vec::with_capacity(unprocessed_txs.len()); for (result, tx) in processing_results .into_iter() @@ -138,7 +138,7 @@ impl AbcippShim { #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { let (processing_results, _) = - self.service.process_proposed_txs(&self.delivered_txs); + self.service.check_proposal(&self.delivered_txs); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; std::mem::swap(&mut self.delivered_txs, &mut delivered); From 8947f32cdf1f9f4e40b58263dfe2ab5b118291e4 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 13:24:52 +0200 Subject: [PATCH 1214/1995] [feat]: Added a nomralized voting power type --- .../vote_extensions/validator_set_update.rs | 54 +++++++++++++------ 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 7578d246c2..b8e9126973 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -21,6 +21,31 @@ use crate::types::storage::Epoch; const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance"; +/// Voting power that has been normalized. Needed +/// for interacting with smart contracts. +/// +/// https://github.com/anoma/ethereum-bridge/blob/fe93d2e95ddb193a759811a79c8464ad4d709c12/test/utils/utilities.js#L29 +#[derive(Debug, Clone, Default)] +pub struct NormalizedVotingPower(ethereum::U256); + +impl NormalizedVotingPower { + fn new(voting_power: VotingPower, total_voting_power: VotingPower) -> Self { + let voting_power: u64 = voting_power.into(); + const NORMALIZED_VOTING_POWER: u64 = 1 << 32; + + let voting_power = Ratio::new(voting_power, total_voting_power.into()) + * NORMALIZED_VOTING_POWER; + let voting_power = voting_power.round().to_integer(); + Self(voting_power.into()) + } +} + +impl Encode<1> for NormalizedVotingPower { + fn tokenize(&self) -> [Token; 1] { + [Token::Uint(self.0)] + } +} + /// Contains the digest of all signatures from a quorum of /// validators for a [`Vext`]. #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] @@ -236,25 +261,21 @@ impl VotingPowersMapExt for VotingPowersMap { }); let sorted = unsorted; - let total_voting_power: u64 = sorted + let total_voting_power: VotingPower = sorted .iter() .map(|&(_, &voting_power)| u64::from(voting_power)) - .sum(); + .sum::() + .into(); // split the vec into three portions sorted.into_iter().fold( Default::default(), |accum, (addr_book, &voting_power)| { - let voting_power: u64 = voting_power.into(); - - // normalize the voting power - // https://github.com/anoma/ethereum-bridge/blob/fe93d2e95ddb193a759811a79c8464ad4d709c12/test/utils/utilities.js#L29 - const NORMALIZED_VOTING_POWER: u64 = 1 << 32; - - let voting_power = Ratio::new(voting_power, total_voting_power) - * NORMALIZED_VOTING_POWER; - let voting_power = voting_power.round().to_integer(); - let voting_power: ethereum::U256 = voting_power.into(); + let voting_power = NormalizedVotingPower::new( + voting_power, + total_voting_power, + ); + let [voting_power] = voting_power.tokenize(); let (mut hot_key_addrs, mut cold_key_addrs, mut voting_powers) = accum; @@ -267,7 +288,7 @@ impl VotingPowersMapExt for VotingPowersMap { .push(Token::Address(ethereum::H160(hot_key_addr))); cold_key_addrs .push(Token::Address(ethereum::H160(cold_key_addr))); - voting_powers.push(Token::Uint(voting_power)); + voting_powers.push(voting_power); (hot_key_addrs, cold_key_addrs, voting_powers) }, @@ -309,7 +330,7 @@ pub struct ValidatorSetArgs { /// Ethereum address of validators pub validators: Vec, /// Voting powers of validators - pub powers: Vec, + pub powers: Vec, /// A nonce pub nonce: Uint, } @@ -325,7 +346,10 @@ impl Encode<1> for ValidatorSetArgs { let powers = Token::Array( self.powers .iter() - .map(|power| Token::Uint(power.clone().into())) + .map(|power| { + let [power] = power.tokenize(); + power + }) .collect(), ); let nonce = Token::Uint(self.nonce.clone().into()); From 61d03634bbcecfddbf93c951801cd21d1f5b7acd Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 27 Oct 2022 14:08:17 +0200 Subject: [PATCH 1215/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 51ebface24..0c22b72344 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -154,7 +154,7 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// as by how much it decreased, which should be authorizing the balance change. +/// and by how much it decreased, which should be authorizing the balance change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, From b2bcd413072be5024aff95254facd1bc1e1773ff Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:46:31 +0200 Subject: [PATCH 1216/1995] [feat]: Added ability to escrow Nam to the bridge pool VP when wanting to mint wNam on Ethereum --- apps/src/lib/config/ethereum_bridge/params.rs | 7 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 308 ++++++++++++++++-- shared/src/types/address.rs | 11 + shared/src/types/ethereum_events.rs | 3 + 4 files changed, 289 insertions(+), 40 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 857230a6c4..0ab56b0b47 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,7 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use std::num::NonZeroU64; +use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address. @@ -42,7 +43,7 @@ pub struct GenesisConfig { pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. - pub native_erc20: Address, + pub native_erc20: EthAddress, /// The Ethereum address of the bridge contract. pub bridge: UpgradeableContract, /// The Ethereum address of the governance contract. @@ -87,9 +88,7 @@ mod tests { let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { - native_erc20: Address( - "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), - ), + native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { address: Address( "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 331b80088e..fc968fbfcd 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -8,7 +8,8 @@ //! //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is -//! added to the pool and gas fees are submitted appropriately. +//! added to the pool and gas fees are submitted appropriately +//! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; use borsh::BorshDeserialize; @@ -23,7 +24,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{xan, Address, InternalAddress}; +use crate::types::address::{wnam, xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; @@ -79,6 +80,44 @@ where Some(SignedAmount::Positive(after - before)) } } + + /// Check that the correct amount of Nam was sent + /// from the correct account into escrow + fn check_nam_escrowed( + &self, + payer_account: &Address, + escrow_account: &Address, + expected_debit: Amount, + expected_credit: Amount, + ) -> bool { + // check that the correct amount was deducted from the fee payer + if let Some(SignedAmount::Negative(amount)) = + self.account_balance_delta(payer_account) + { + if amount != expected_debit { + return false; + } + } else { + tracing::debug!("The account {} was not debited.", payer_account); + return false; + } + // check that the correct amount was credited to escrow + if let Some(SignedAmount::Positive(amount)) = + self.account_balance_delta(escrow_account) + { + if amount != expected_credit { + return false; + } + } else { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + return false; + } + true + } } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -163,36 +202,41 @@ where return Ok(false); } - // check that gas fees were put into escrow - - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(&transfer.gas_fee.payer) - { - if amount != transfer.gas_fee.amount { - return Ok(false); - } + // if we are going to mint wNam on Ethereum, the appropriate + // amount of Nam must be escrowed in the Ethereum bridge VP's storage. + // TODO: We should look this address up from storage + if transfer.transfer.asset == wnam() { + // check that correct amount of Nam was put into escrow. + return if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); + Ok(true) + }; } else { - tracing::debug!("The gas fee payers account was not debited."); - return Ok(false); - } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(&BRIDGE_POOL_ADDRESS) - { - if amount != transfer.gas_fee.amount { + // check that the correct amounnt of gas fees were escrowed + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount, + transfer.gas_fee.amount, + ) { return Ok(false); } - } else { - tracing::debug!( - "The Ethereum bridge pool's gas escrow was not credited." - ); - return Ok(false); } - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer {:?}.", - transfer - ); // check that the assets to be transferred were escrowed let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); @@ -206,12 +250,9 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some(delta)) - if delta - == ( - transfer.transfer.sender, - transfer.transfer.amount, - ) => {} + Ok(Some((addr, amt))) + if addr == transfer.transfer.sender + && amt == transfer.transfer.amount => {} _ => { tracing::debug!( "The assets of the transfer were not properly \ @@ -228,6 +269,10 @@ where return Ok(false); } + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); Ok(true) } } @@ -885,8 +930,199 @@ mod test_bridge_pool_vp { let vp = BridgePoolVp { ctx: setup_ctx(&tx, &storage, &write_log), }; - // inform the vp that the merkle root changed - let keys_changed = BTreeSet::default(); + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } + + /// Test that we can escrow Nam if we + /// want to mint wNam on Ethereum. + #[test] + fn test_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(res); + } + + /// Test that we can rejecte a transfer that + /// mints wNam if we don't escrow the correct + /// amount of Nam. + #[test] + fn test_reject_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 9954b4e568..843fbd21e4 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::key::PublicKeyHash; @@ -531,6 +532,16 @@ pub fn kartoffel() -> Address { Address::decode("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90").expect("The token address decoding shouldn't fail") } +/// Temporary helper for testing +pub const fn wnam() -> EthAddress { + // TODO: Replace this with the real wNam ERC20 address once it exists + // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" + EthAddress([ + 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, + 190, 239, 222, 173, 190, 239, + ]) +} + /// Temporary helper for testing, a hash map of tokens addresses with their /// informal currency codes. pub fn tokens() -> HashMap { diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..5a66906a49 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use eyre::{eyre, Context}; +use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -55,6 +56,8 @@ impl From for Uint { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From 179671e7c9906335288ed26f6f90885c3dd8072e Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:53:49 +0200 Subject: [PATCH 1217/1995] [feat]: Changed erc20 address to an ethereum address --- apps/src/lib/config/ethereum_bridge/params.rs | 7 +++---- shared/src/types/ethereum_events.rs | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 857230a6c4..0ab56b0b47 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,7 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use std::num::NonZeroU64; +use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address. @@ -42,7 +43,7 @@ pub struct GenesisConfig { pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. - pub native_erc20: Address, + pub native_erc20: EthAddress, /// The Ethereum address of the bridge contract. pub bridge: UpgradeableContract, /// The Ethereum address of the governance contract. @@ -87,9 +88,7 @@ mod tests { let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { - native_erc20: Address( - "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), - ), + native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { address: Address( "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..5a66906a49 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use eyre::{eyre, Context}; +use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -55,6 +56,8 @@ impl From for Uint { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From 7e3a3728497ddda66de837808248304751fdd078 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 17:06:45 +0200 Subject: [PATCH 1218/1995] [fix]: Some more error logging and formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 10 ++++++---- shared/src/ledger/eth_bridge/vp/mod.rs | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 331b80088e..b01eb98e1b 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -127,10 +127,11 @@ where ); return Ok(false); } - Err(_) => { + Err(e) => { return Err(eyre!( "Could not read the storage key associated with the \ - transfer." + transfer: {:?}", + e ) .into()); } @@ -212,10 +213,11 @@ where transfer.transfer.sender, transfer.transfer.amount, ) => {} - _ => { + other => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool." + escrowed into the Ethereum bridge pool: {:?}", + other ); return Ok(false); } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0c22b72344..0195e1ac20 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -154,7 +154,8 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// and by how much it decreased, which should be authorizing the balance change. +/// and by how much it decreased, which should be authorizing the balance +/// change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, From d022193a56f48326e8534a424ff3af0dfe0e4538 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 27 Oct 2022 17:42:49 +0100 Subject: [PATCH 1219/1995] Post-merge fixes done in review party --- Cargo.lock | 897 +++++++++++++++++- apps/Cargo.toml | 18 - proof_of_stake/src/lib.rs | 2 +- proof_of_stake/src/types.rs | 3 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 5 + shared/src/ledger/governance/vp.rs | 1 + shared/src/ledger/ibc/vp/client.rs | 3 +- shared/src/ledger/ibc/vp/connection.rs | 1 + shared/src/ledger/native_vp.rs | 1 - shared/src/ledger/pos/mod.rs | 21 +- shared/src/ledger/pos/vp.rs | 116 ++- shared/src/ledger/slash_fund/mod.rs | 3 +- shared/src/proto/mod.rs | 2 +- shared/src/proto/types.rs | 1 + shared/src/types/hash.rs | 1 - shared/src/types/storage.rs | 3 + shared/src/vm/wasm/host_env.rs | 2 +- 17 files changed, 963 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34ab8ab550..e081e3e4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes 1.2.1", + "futures-core", + "futures-sink", + "log 0.4.17", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util 0.7.4", +] + +[[package]] +name = "actix-http" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.13.0", + "bitflags", + "bytes 1.2.1", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags 0.3.2", + "local-channel", + "mime 0.3.16", + "percent-encoding 2.2.0", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec 1.10.0", + "tracing 0.1.37", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log 0.4.17", + "openssl", + "pin-project-lite", + "tokio-openssl", + "tokio-util 0.7.4", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -109,7 +212,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "paste", "rustc_version 0.3.3", @@ -132,7 +235,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "quote", "syn", @@ -392,7 +495,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-rustls", - "tungstenite", + "tungstenite 0.12.0", "webpki-roots", ] @@ -428,6 +531,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash", + "base64 0.13.0", + "bytes 1.2.1", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log 0.4.17", + "mime 0.3.16", + "openssl", + "percent-encoding 2.2.0", + "pin-project-lite", + "rand 0.8.5", + "serde 1.0.145", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -532,6 +669,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.4" @@ -652,7 +801,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -688,12 +837,28 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + [[package]] name = "byte-tools" version = "0.3.1" @@ -752,6 +917,15 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "bytestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" +dependencies = [ + "bytes 1.2.1", +] + [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -837,6 +1011,15 @@ dependencies = [ "generic-array 0.14.6", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check 0.9.4", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -865,6 +1048,24 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clarity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" +dependencies = [ + "lazy_static", + "num-bigint 0.4.3", + "num-traits 0.2.15", + "num256", + "secp256k1", + "serde 1.0.145", + "serde-rlp", + "serde_bytes", + "serde_derive", + "sha3 0.10.6", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -966,6 +1167,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1298,8 +1505,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn", ] @@ -1479,6 +1688,16 @@ dependencies = [ "syn", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +dependencies = [ + "traitobject", + "typeable", +] + [[package]] name = "escargot" version = "0.5.7" @@ -1491,6 +1710,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde 1.0.145", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1566,7 +1829,7 @@ dependencies = [ "itertools", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde 1.0.145", @@ -1625,6 +1888,18 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1709,6 +1984,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2035,6 +2316,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2113,7 +2400,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags", + "language-tags 0.2.2", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -2326,7 +2613,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2357,6 +2644,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde 1.0.145", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -2395,6 +2720,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + [[package]] name = "iovec" version = "0.1.4" @@ -2443,17 +2774,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" -dependencies = [ - "log 0.4.17", - "serde 1.0.145", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.2" @@ -2485,6 +2805,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -2650,6 +2976,24 @@ dependencies = [ "serde 1.0.145", ] +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.3.4" @@ -2799,6 +3143,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "message-io" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils 0.8.12", + "integer-encoding", + "lazy_static", + "log 0.4.17", + "mio 0.7.14", + "serde 1.0.145", + "strum", + "tungstenite 0.16.0", + "url 2.3.1", +] + [[package]] name = "mime" version = "0.2.6" @@ -2852,12 +3214,25 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", ] +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log 0.4.17", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + [[package]] name = "mio" version = "0.8.4" @@ -2882,6 +3257,15 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "miracl_core" version = "2.3.0" @@ -2909,6 +3293,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "quick-error 1.2.3", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "namada" version = "0.8.1" @@ -2925,9 +3327,12 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", + "hex", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -2937,6 +3342,7 @@ dependencies = [ "libsecp256k1", "loupe", "namada_proof_of_stake", + "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", @@ -2957,6 +3363,7 @@ dependencies = [ "tendermint-proto 0.23.6", "test-log", "thiserror", + "tiny-keccak", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", @@ -2976,6 +3383,7 @@ version = "0.8.1" dependencies = [ "ark-serialize", "ark-std", + "assert_matches", "async-std", "async-trait", "base64 0.13.0", @@ -2986,12 +3394,16 @@ dependencies = [ "borsh", "byte-unit", "byteorder", + "bytes 1.2.1", + "circular-queue", "clap", + "clarity", "color-eyre", "config", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", "eyre", "ferveo", "ferveo-common", @@ -3000,12 +3412,13 @@ dependencies = [ "futures 0.3.25", "git2", "itertools", - "jsonpath_lib", "libc", "libloading", + "message-io", "namada", "num-derive", "num-traits 0.2.15", + "num256", "num_cpus", "once_cell", "orion", @@ -3020,9 +3433,11 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "semver 1.0.14", "serde 1.0.145", "serde_bytes", "serde_json", + "serde_regex", "sha2 0.9.9", "signal-hook", "sparse-merkle-tree", @@ -3049,6 +3464,8 @@ dependencies = [ "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", + "warp", + "web30", "websocket", "winapi 0.3.9", ] @@ -3254,17 +3671,42 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits 0.2.15", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.4.3", + "num-complex 0.4.2", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", + "num-traits 0.2.15", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.1.0", + "num-integer", "num-traits 0.2.15", ] @@ -3277,6 +3719,17 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", + "serde 1.0.145", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.1.0", + "num-traits 0.2.15", ] [[package]] @@ -3320,6 +3773,18 @@ dependencies = [ "num-traits 0.2.15", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.1.0", + "num-bigint 0.2.6", + "num-integer", + "num-traits 0.2.15", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -3327,7 +3792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", ] @@ -3350,6 +3815,20 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static", + "num 0.4.0", + "num-derive", + "num-traits 0.2.15", + "serde 1.0.145", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -3487,10 +3966,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "owo-colors" -version = "1.3.0" +name = "owo-colors" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.145", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] [[package]] name = "parity-wasm" @@ -3720,6 +4225,19 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3729,6 +4247,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3798,7 +4327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.2.1", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log 0.4.17", @@ -3895,6 +4424,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -4293,6 +4828,16 @@ dependencies = [ "libc", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes 1.2.1", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.19.0" @@ -4342,6 +4887,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -4360,6 +4911,15 @@ 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.14", +] + [[package]] name = "rustls" version = "0.19.1" @@ -4385,6 +4945,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -4481,6 +5050,12 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -4509,6 +5084,24 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.3.1" @@ -4550,6 +5143,12 @@ dependencies = [ "semver-parser 0.10.2", ] +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + [[package]] name = "semver-parser" version = "0.7.0" @@ -4592,6 +5191,18 @@ dependencies = [ "serde 0.8.23", ] +[[package]] +name = "serde-rlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" +dependencies = [ + "byteorder", + "error", + "num 0.2.1", + "serde 1.0.145", +] + [[package]] name = "serde_bytes" version = "0.11.7" @@ -4618,12 +5229,21 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ - "indexmap", "itoa", "ryu", "serde 1.0.145", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde 1.0.145", +] + [[package]] name = "serde_repr" version = "0.1.9" @@ -4684,6 +5304,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.5", +] + [[package]] name = "sha1" version = "0.10.5" @@ -4731,6 +5362,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -4853,6 +5494,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subproductdomain" version = "0.1.0" @@ -4924,6 +5587,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.38" @@ -5296,6 +5965,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny_http" version = "0.11.0" @@ -5407,6 +6085,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -5496,6 +6186,18 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log 0.4.17", + "tokio", + "tungstenite 0.17.3", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -5831,6 +6533,53 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.9.8", + "thiserror", + "url 2.3.1", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.10.0", + "thiserror", + "url 2.3.1", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -5849,6 +6598,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicase" version = "1.4.2" @@ -6031,6 +6792,37 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +dependencies = [ + "bytes 1.2.1", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.20", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "multipart", + "percent-encoding 2.2.0", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde 1.0.145", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.7.4", + "tower-service", + "tracing 0.1.37", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -6391,6 +7183,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c0c0c928020760cc69884944d54e7fca43ff5d00933d0a63fd85155466fbed" +dependencies = [ + "awc", + "clarity", + "futures 0.3.25", + "lazy_static", + "log 0.4.17", + "num 0.4.0", + "num256", + "serde 1.0.145", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -6676,6 +7487,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "0.2.3" @@ -6715,6 +7535,25 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.1+zstd.1.5.2" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index f92377d52d..3aaa849ade 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -63,24 +63,6 @@ abciplus = [ "namada/abciplus" ] -abcipp = [ - "tendermint-abcipp", - "tendermint-config-abcipp", - "tendermint-proto-abcipp", - "tendermint-rpc-abcipp", - "tower-abci-abcipp", - "namada/abcipp" -] - -abciplus = [ - "tendermint", - "tendermint-config", - "tendermint-rpc", - "tendermint-proto", - "tower-abci", - "namada/abciplus" -] - [dependencies] namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand", "secp256k1-sign-verify"]} ark-serialize = "0.3.0" diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 07b1b6d2fa..d605aa4843 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -298,7 +298,7 @@ pub trait PosActions: PosReadOnly { eth_cold_key: &Self::PublicKey, eth_hot_key: &Self::PublicKey, current_epoch: impl Into, - ) -> Result<(), BecomeValidatorError> + ) -> Result<(), Self::BecomeValidatorError> where Self::PublicKey: TryRefTo, { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 2a31016f10..6d9841748a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -36,8 +36,7 @@ pub type Unbonds = pub type ValidatorSets
= Epoched, OffsetUnbondingLen>; /// Epoched total voting power. -pub type TotalVotingPowers = - EpochedDelta; +pub type TotalVotingPowers = EpochedDelta; /// Epoched validator's eth key. pub type ValidatorEthKey = Epoched; diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..df6620f1b3 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -270,11 +270,16 @@ mod test_bridge_pool_vp { storage: &'a Storage, write_log: &'a WriteLog, ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { + let verifiers = BTreeSet::default(); + let keys_changed = BTreeSet::default(); Ctx::new( + &BRIDGE_POOL_ADDRESS, storage, write_log, tx, VpGasMeter::new(0u64), + &keys_changed, + &verifiers, VpCache::new(temp_dir(), 100usize), ) } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index d281876720..aab5171ee9 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; +use native_vp::VpEnv; use thiserror::Error; use super::storage as gov_storage; diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 49a73e54c3..433a4f1c12 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::traits::StorageHasher; use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; @@ -556,7 +557,7 @@ where fn host_height(&self) -> Height { let height = self.ctx.storage.get_block_height().0.0; // the revision number is always 0 - Height::new(0, height) + Height::new(0, height.into()) } fn host_consensus_state( diff --git a/shared/src/ledger/ibc/vp/connection.rs b/shared/src/ledger/ibc/vp/connection.rs index 2361df2d77..69a87eb128 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::traits::StorageHasher; use crate::ledger::storage::{self}; use crate::tendermint_proto::Protobuf; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 3de576a13e..27be0832d7 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -5,7 +5,6 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::Context; -use thiserror::Error; use super::storage_api::{self, ResultExt, StorageRead}; pub use super::vp_env::VpEnv; diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 218d35fda5..b87f5be753 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -15,10 +15,9 @@ use namada_proof_of_stake::PosBase; pub use storage::*; pub use vp::PosVP; +use super::storage_api; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{self as ledger_storage, Storage}; -use super::storage_api; -use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{self, Address, InternalAddress}; use crate::types::ethereum_events::EthAddress; use crate::types::key::common; @@ -286,6 +285,24 @@ mod macros { $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_voting_power_key())?.unwrap(); Ok($crate::ledger::storage::types::decode(value).unwrap()) } + + fn read_validator_eth_cold_key( + &self, + key: &Self::Address, + ) -> Option> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_cold_key_key(key))?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + + fn read_validator_eth_hot_key( + &self, + key: &Self::Address, + ) -> Option> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_hot_key_key(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 46822987cd..338d740d04 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -20,24 +20,22 @@ use super::{ is_unbond_key, is_validator_set_key, is_validator_staking_reward_address_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, - total_voting_power_key, unbond_key, validator_consensus_key_key, - validator_eth_cold_key_key, validator_eth_hot_key_key, validator_set_key, - validator_slashes_key, validator_staking_reward_address_key, - validator_state_key, validator_total_deltas_key, - validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, - ValidatorSets, ValidatorTotalDeltas, + storage_api, total_voting_power_key, unbond_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, 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, CtxPostStorageRead, CtxPreStorageRead, NativeVp, + self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, VpEnv, }; use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, }; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::types::decode; use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; @@ -123,7 +121,7 @@ where let addr = Address::Internal(Self::ADDR); let mut changes: Vec> = vec![]; - let current_epoch = self.ctx.pre().get_block_epoch()?; + let current_epoch = self.ctx.get_block_epoch()?; for key in keys_changed { if is_params_key(key) { @@ -133,18 +131,18 @@ where _ => return Ok(false), } } else if is_validator_set_key(key) { - let pre = self.ctx.pre().read_bytes(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.post().read_bytes(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.pre().read_bytes(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.post().read_bytes(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -154,24 +152,24 @@ where } else if let Some(validator) = is_validator_staking_reward_address_key(key) { - 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() - }); + 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()); 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.pre().read_bytes(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.post().read_bytes(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -179,10 +177,10 @@ where update: ConsensusKey(Data { pre, post }), }); } else if let Some(validator) = is_validator_total_deltas_key(key) { - let pre = self.ctx.pre().read_bytes(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.post().read_bytes(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -190,10 +188,10 @@ where update: TotalDeltas(Data { pre, post }), }); } else if let Some(validator) = is_validator_voting_power_key(key) { - let pre = self.ctx.pre().read_bytes(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.post().read_bytes(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -203,14 +201,14 @@ where } else if let Some(raw_hash) = is_validator_address_raw_hash_key(key) { - 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() - }); + 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()); changes.push(ValidatorAddressRawHash { raw_hash: raw_hash.to_string(), data: Data { pre, post }, @@ -221,27 +219,26 @@ where if owner != &addr { continue; } - let pre = self.ctx.pre().read_bytes(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.post().read_bytes(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.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() - }); + 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()); // For bonds, we need to look-up slashes let slashes = self .ctx - .pre() - .read_bytes(&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 { @@ -250,19 +247,20 @@ where slashes, }); } else if let Some(unbond_id) = is_unbond_key(key) { - 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() - }); + 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()); // For unbonds, we need to look-up slashes let slashes = self .ctx - .pre() - .read_bytes(&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 { @@ -271,10 +269,10 @@ where slashes, }); } else if is_total_voting_power_key(key) { - let pre = self.ctx.pre().read_bytes(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.post().read_bytes(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 })); diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index 0d452af2e5..e5ef72b5fe 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -10,8 +10,9 @@ use thiserror::Error; use self::storage as slash_fund_storage; use super::governance::vp::is_proposal_accepted; +use super::storage::traits::StorageHasher; use crate::ledger::native_vp::{self, Ctx, NativeVp}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{xan as nam, Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index 3271037595..215e76ac45 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -3,7 +3,7 @@ pub mod generated; mod types; -pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; +pub use types::{Dkg, Error, Signed, SignedSerialize, SignedTxData, Tx}; #[cfg(test)] mod tests { diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index cd55d5d613..5097d6a0be 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index bb18bc800b..4d3d01d4c8 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,7 +4,6 @@ use std::fmt::{self, Display}; use std::ops::Deref; use arse_merkle_tree::traits::Value; -use arse_merkle_tree::Hash as TreeHash; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; use serde::{Deserialize, Serialize}; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index bb744fa645..3d1c3f772d 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::BASE32HEX_NOPAD; use ics23::CommitmentProof; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -34,6 +35,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Error parsing key segment {0}")] + ParseKeySeg(String), #[error("Could not parse string into a key segment: {0}")] ParseError(String), } diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 6c6cc13ae6..769613d42f 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -10,7 +10,7 @@ use wasmer::{ use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{self}; -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}; From 64dd5c11628b22307ed27cd5a06ba647ca7ad7f1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 09:39:18 +0100 Subject: [PATCH 1220/1995] Compiles for abciplus --- apps/src/lib/cli.rs | 1 - apps/src/lib/client/tx.rs | 3 +- apps/src/lib/client/utils.rs | 5 +-- apps/src/lib/config/mod.rs | 3 +- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- .../lib/node/ledger/shell/finalize_block.rs | 14 ++------- apps/src/lib/node/ledger/shell/governance.rs | 6 ++-- apps/src/lib/node/ledger/shell/init_chain.rs | 7 ++++- apps/src/lib/node/ledger/shell/mod.rs | 8 ++--- .../lib/node/ledger/shell/process_proposal.rs | 19 ++++++------ apps/src/lib/node/ledger/shell/queries.rs | 2 ++ apps/src/lib/node/ledger/shims/abcipp_shim.rs | 9 +----- apps/src/lib/node/ledger/tendermint_node.rs | 6 ++-- proof_of_stake/src/lib.rs | 2 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 28 +++++++++++++---- shared/src/ledger/pos/mod.rs | 10 +++--- shared/src/ledger/pos/vp.rs | 3 +- tests/src/e2e/ledger_tests.rs | 3 +- tests/src/native_vp/mod.rs | 7 ++--- tx_prelude/src/proof_of_stake.rs | 31 +++++++++++++++++-- 20 files changed, 97 insertions(+), 72 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 2fa8d69d6c..ad90f1d2cb 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1255,7 +1255,6 @@ pub mod args { use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; - use serde::Deserialize; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 398c2c2870..ec6b0894a5 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -16,13 +16,12 @@ use namada::types::governance::{ use namada::types::key::{self, *}; use namada::types::nft::{self, Nft, NftToken}; use namada::types::storage::{Epoch, Key}; -use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use namada::types::{address, storage, token}; +use namada::types::{address, token}; use namada::{ledger, vm}; use tokio::time::{Duration, Instant}; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index e3b87dbc45..de2cd18c0b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -25,12 +25,9 @@ use crate::config::genesis::genesis_config::{ self, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{ - self, Config, IntentGossiper, PeerAddress, TendermintMode, -}; +use crate::config::{self, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; -use crate::node::gossip; use crate::node::ledger::tendermint_node; use crate::wallet::{pre_genesis, Wallet}; use crate::wasm_loader; diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 9ef9051b0a..8e56efa0b8 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -13,8 +13,7 @@ use std::str::FromStr; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; -use regex::Regex; -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::cli; diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index 84d0b55503..e7b85b0f6c 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -12,10 +12,10 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::slash_fund::SlashFundVp; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, DB}; -use namada::ledger::treasury::TreasuryVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1b5742f08e..901d61e38d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,22 +1,12 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, -}; -use namada::ledger::governance::vp::ADDRESS as gov_address; -use namada::ledger::storage::types::encode; -use namada::ledger::treasury::ADDRESS as treasury_address; -use namada::types::address::{xan as m1t, Address}; -use namada::types::governance::TallyResult; -use namada::types::storage::{BlockHash, Epoch, Header}; +use namada::types::storage::{BlockHash, Header}; use namada::types::transaction::protocol::ProtocolTxType; -use super::queries::QueriesExt; +use super::governance::execute_governance_proposals; use super::*; use crate::facade::tendermint_proto::abci::Misbehavior as Evidence; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; -use crate::node::ledger::events::EventType; impl Shell where diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 979c3c0df1..f5e9505909 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -5,7 +5,7 @@ use namada::ledger::governance::utils::{ use namada::ledger::governance::vp::ADDRESS as gov_address; use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::ledger::storage::types::encode; -use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::storage::{DBIter, DB}; use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; use namada::types::storage::Epoch; @@ -78,14 +78,14 @@ where .storage .write(&pending_execution_key, "") .expect("Should be able to write to storage."); - let tx_result = protocol::apply_tx( + let tx_result = protocol::dispatch_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.storage, &mut shell.vp_wasm_cache, &mut shell.tx_wasm_cache, ); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 649d6c4876..35e3e63576 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,11 +2,16 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; +use namada::ledger::{ibc, pos}; use namada::types::key::*; +use namada::types::time::{DateTimeUtc, TimeZone, Utc}; +use namada::types::token; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; +use tower_abci::{request, response}; -use super::queries::QueriesExt; use super::*; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 131a8501e1..8f13daeed4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -23,6 +23,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::gas::BlockGasMeter; +use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, }; @@ -30,18 +31,16 @@ use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::storage::traits::{Sha256Hasher, StorageHasher}; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, DB}; -use namada::ledger::{ibc, parameters, pos}; use namada::proto::{self, Tx}; +use namada::types::address; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key}; -use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; -use namada::types::{address, token}; use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; @@ -56,8 +55,6 @@ use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; -#[cfg(not(feature = "abcipp"))] -use crate::facade::tendermint_proto::types::ConsensusParams; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tower_abci::{request, response}; @@ -801,6 +798,7 @@ mod test_utils { use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{BlockHash, Epoch, Header}; + use namada::types::time::DateTimeUtc; use namada::types::transaction::Fee; use tempfile::tempdir; use tokio::sync::mpsc::{Sender, UnboundedReceiver}; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index a2793b045b..bf341a51a0 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,6 +1,7 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell +use data_encoding::HEXUPPER; use namada::ledger::pos::types::VotingPower; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] @@ -44,9 +45,9 @@ where req: RequestProcessProposal, ) -> ProcessProposal { tracing::info!( - proposer = ?hex::encode(&req.proposer_address), + proposer = ?HEXUPPER.encode(&req.proposer_address), height = req.height, - hash = ?hex::encode(&req.hash), + hash = ?HEXUPPER.encode(&req.hash), n_txs = req.txs.len(), "Received block proposal", ); @@ -58,9 +59,9 @@ where !self.has_proper_eth_events_num(&counters); if invalid_num_of_eth_ev_digests { tracing::warn!( - proposer = ?hex::encode(&req.proposer_address), + proposer = ?HEXUPPER.encode(&req.proposer_address), height = req.height, - hash = ?hex::encode(&req.hash), + hash = ?HEXUPPER.encode(&req.hash), eth_ev_digest_num = counters.eth_ev_digest_num, "Found invalid number of Ethereum events vote extension digests, proposed block \ will be rejected" @@ -71,9 +72,9 @@ where !self.has_proper_valset_upd_num(&counters); if invalid_num_of_valset_upd_digests { tracing::warn!( - proposer = ?hex::encode(&req.proposer_address), + proposer = ?HEXUPPER.encode(&req.proposer_address), height = req.height, - hash = ?hex::encode(&req.hash), + hash = ?HEXUPPER.encode(&req.hash), valset_upd_digest_num = counters.valset_upd_digest_num, "Found invalid number of validator set update vote extension digests, proposed block \ will be rejected" @@ -92,9 +93,9 @@ where }); if invalid_txs { tracing::warn!( - proposer = ?hex::encode(&req.proposer_address), + proposer = ?HEXUPPER.encode(&req.proposer_address), height = req.height, - hash = ?hex::encode(&req.hash), + hash = ?HEXUPPER.encode(&req.hash), "Found invalid transactions, proposed block will be rejected" ); } @@ -441,8 +442,6 @@ mod test_process_proposal { }; use super::*; - use crate::facade::tendermint_proto::abci::RequestInitChain; - use crate::facade::tendermint_proto::google::protobuf::Timestamp; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 724771acb0..1fad92eb9d 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,5 +1,7 @@ //! Shell methods for querying state +use std::cmp::max; + use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::parameters::EpochDuration; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index cd549aa0fa..6975207c07 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -13,16 +13,8 @@ use namada::types::storage::BlockHash; #[cfg(not(feature = "abcipp"))] use namada::types::transaction::hash_tx; use tokio::sync::mpsc::{Receiver, UnboundedSender}; -#[cfg(not(feature = "abcipp"))] -use namada::types::hash::Hash; -#[cfg(not(feature = "abcipp"))] -use namada::types::storage::BlockHash; -#[cfg(not(feature = "abcipp"))] -use namada::types::transaction::hash_tx; -use tokio::sync::mpsc::UnboundedSender; use tower::Service; -use super::super::Shell; use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; #[cfg(not(feature = "abcipp"))] use super::abcipp_shim_types::shim::TxBytes; @@ -31,6 +23,7 @@ use crate::config; #[cfg(not(feature = "abcipp"))] use crate::facade::tendermint_proto::abci::RequestBeginBlock; use crate::facade::tower_abci::{BoxError, Request as Req, Response as Resp}; +use crate::node::ledger::shell::Shell; /// The shim wraps the shell, which implements ABCI++. /// The shim makes a crude translation between the ABCI interface currently used diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 48a7c61f58..26578cdf3e 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -5,9 +5,11 @@ use std::str::FromStr; use borsh::BorshSerialize; use eyre::{eyre, Context}; -use namada::types::address::Address; use namada::types::chain::ChainId; -use namada::types::key::*; +use namada::types::key::{ + common, ed25519, secp256k1, tm_consensus_key_raw_hash, ParseSecretKeyError, + RefTo, SecretKey, +}; use namada::types::time::DateTimeUtc; use semver::{Version, VersionReq}; use serde_json::json; diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index d605aa4843..a8edfa887e 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -263,7 +263,7 @@ pub trait PosActions: PosReadOnly { /// Write PoS validator's Eth validator set update signing key fn write_validator_eth_hot_key( - &self, + &mut self, address: &Self::Address, value: ValidatorEthKey, ); diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index df6620f1b3..97b66a4dcb 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -269,17 +269,17 @@ mod test_bridge_pool_vp { tx: &'a Tx, storage: &'a Storage, write_log: &'a WriteLog, + keys_changed: &'a BTreeSet, + verifiers: &'a BTreeSet
, ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { - let verifiers = BTreeSet::default(); - let keys_changed = BTreeSet::default(); Ctx::new( &BRIDGE_POOL_ADDRESS, storage, write_log, tx, VpGasMeter::new(0u64), - &keys_changed, - &verifiers, + keys_changed, + verifiers, VpCache::new(temp_dir(), 100usize), ) } @@ -354,11 +354,18 @@ mod test_bridge_pool_vp { .expect("Test failed"); // add transfer to pool + let verifiers = BTreeSet::new(); let keys_changed = insert_transfer(transfer.clone(), &mut write_log); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -597,9 +604,18 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); + let keys_changed = BTreeSet::default(); + let verifiers = BTreeSet::default(); + // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; // inform the vp that the merkle root changed let keys_changed = BTreeSet::default(); diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index b87f5be753..e3f7a1956e 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -286,22 +286,24 @@ mod macros { Ok($crate::ledger::storage::types::decode(value).unwrap()) } + // TODO: return result fn read_validator_eth_cold_key( &self, key: &Self::Address, ) -> Option> { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_cold_key_key(key))?.unwrap(); - Ok($crate::ledger::storage::types::decode(value).unwrap()) + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_cold_key_key(key)).unwrap().unwrap(); + Some($crate::ledger::storage::types::decode(value).unwrap()) } + // TODO: return result fn read_validator_eth_hot_key( &self, key: &Self::Address, ) -> Option> { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_hot_key_key(key))?.unwrap(); - Ok($crate::ledger::storage::types::decode(value).unwrap()) + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_hot_key_key(key)).unwrap().unwrap(); + Some($crate::ledger::storage::types::decode(value).unwrap()) } } } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 338d740d04..c3139c9d5e 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -21,7 +21,8 @@ use super::{ is_validator_staking_reward_address_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, storage_api, total_voting_power_key, unbond_key, - validator_consensus_key_key, validator_set_key, validator_slashes_key, + validator_consensus_key_key, validator_eth_cold_key_key, + validator_eth_hot_key_key, validator_set_key, validator_slashes_key, validator_staking_reward_address_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 7cf8ec15e5..e651269704 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -24,8 +24,7 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; -use super::setup::{disable_eth_fullnode, working_dir}; -use super::setup::get_all_wasms_hashes; +use super::setup::{disable_eth_fullnode, get_all_wasms_hashes}; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index c3edc77c28..8711f86d1d 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -5,10 +5,9 @@ use std::collections::BTreeSet; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::traits::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; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index c11b035495..6b1283efac 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -4,9 +4,10 @@ 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, + validator_eth_cold_key_key, validator_eth_hot_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::Address; use namada::types::transaction::InitValidator; @@ -80,6 +81,8 @@ impl Ctx { InitValidator { account_key, consensus_key, + eth_cold_key, + eth_hot_key, rewards_account_key, protocol_key, dkg_key, @@ -102,10 +105,14 @@ impl Ctx { let pk_key = key::pk_key(&rewards_address); self.write(&pk_key, &rewards_account_key)?; + let eth_cold_key = key::common::PublicKey::Secp256k1(eth_cold_key); + let eth_hot_key = key::common::PublicKey::Secp256k1(eth_hot_key); self.become_validator( &validator_address, &rewards_address, &consensus_key, + ð_cold_key, + ð_hot_key, current_epoch, )?; @@ -210,6 +217,24 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&total_voting_power_key(), &value) } + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) { + self.write(&validator_eth_cold_key_key(address), encode(&value)) + .unwrap(); + } + + fn write_validator_eth_hot_key( + &mut self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) { + self.write(&validator_eth_hot_key_key(address), encode(&value)) + .unwrap(); + } + fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { self.delete(&bond_key(key)) } From 836fc06ee2f98af5f25ed8e039cc9a7c0e882e3a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 10:19:23 +0100 Subject: [PATCH 1221/1995] Some abcipp fixes (still some type mismatches) --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 3 +-- apps/src/lib/node/ledger/tendermint_node.rs | 2 +- shared/src/types/vote_extensions/ethereum_events.rs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 901d61e38d..c5119f5cf1 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -725,7 +725,7 @@ mod test_finalize_block { let signed = MultiSignedEthEvent { event, #[cfg(feature = "abcipp")] - signers: HashSet::from([address.clone()]), + signers: BTreeSet::from([address.clone()]), #[cfg(not(feature = "abcipp"))] signers: BTreeSet::from([( address.clone(), diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8f13daeed4..ae8aade9b4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -55,8 +55,6 @@ use crate::facade::tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, ValidatorUpdate, }; use crate::facade::tendermint_proto::crypto::public_key; -#[cfg(feature = "abcipp")] -use crate::facade::tendermint_proto::types::ConsensusParams; use crate::facade::tower_abci::{request, response}; use crate::node::ledger::events::log::EventLog; use crate::node::ledger::events::Event; @@ -804,6 +802,7 @@ mod test_utils { use tokio::sync::mpsc::{Sender, UnboundedReceiver}; use super::*; + #[cfg(feature = "abciplus")] use crate::facade::tendermint_proto::abci::{ RequestInitChain, RequestProcessProposal, }; diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 26578cdf3e..0b4e6b2e9e 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -30,7 +30,7 @@ pub const ENV_VAR_TM_STDOUT: &str = "ANOMA_TM_STDOUT"; #[cfg(feature = "abciplus")] pub const VERSION_REQUIREMENTS: &str = ">= 0.37.0-alpha.2, <0.38.0"; -#[cfg(feature = "abcipp")] +#[cfg(not(feature = "abciplus"))] // TODO: update from our v0.36-based fork to v0.38 for full ABCI++ pub const VERSION_REQUIREMENTS: &str = "= 0.1.1-abcipp"; diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index ac0993a3b7..9e97692b53 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -233,7 +233,7 @@ mod tests { .collect(); #[cfg(feature = "abcipp")] let signers = { - let mut s = HashSet::new(); + let mut s = BTreeSet::new(); s.insert(validator_1.clone()); s.insert(validator_2); s From 1e0c7f53c8723acfe263aae4ee6eb4452a68c744 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 10:47:54 +0100 Subject: [PATCH 1222/1995] Remove unused patches from wasm crate --- wasm/wasm_source/Cargo.toml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index f86e6427f0..5190e9e598 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -49,28 +49,3 @@ namada_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] -# 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"} - -# patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} - -# patched to a commit on the `eth-bridge-integration` branch of our fork -ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} - -[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 e1dc6419e00a695a2a6633bd4f758308cadbcd9f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 10:50:23 +0100 Subject: [PATCH 1223/1995] Remove duplicate patches from test wasm crate --- wasm_for_tests/wasm_source/Cargo.toml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 4de5b0a5c9..90cb8ebb58 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -43,12 +43,6 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -# patched to a commit on the `eth-bridge-integration` branch of our fork -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "87be41b8c9cc2850830f4d8028c1fe1bd9f96284"} - # patched to a commit on the `eth-bridge-integration` branch of our fork ibc = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2c1f25cc431279ab74f10f3e0f6827e2"} From b59e79ebfc24c86fd06d2ea0d727042bbf50a3cc Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 10:50:33 +0100 Subject: [PATCH 1224/1995] Update wasm Cargo.lock files --- wasm/Cargo.lock | 263 +++++++++++++++++++++++++- wasm_for_tests/wasm_source/Cargo.lock | 263 +++++++++++++++++++++++++- 2 files changed, 522 insertions(+), 4 deletions(-) diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f9030a471a..e8a72f19d2 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -226,6 +226,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.1" @@ -281,7 +293,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -312,6 +324,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -810,6 +828,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -848,6 +910,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -870,6 +944,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.24" @@ -1107,7 +1187,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1117,6 +1197,44 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1364,7 +1482,10 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", @@ -1372,6 +1493,7 @@ dependencies = [ "libsecp256k1", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1388,6 +1510,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", @@ -1519,6 +1642,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1580,6 +1715,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1630,6 +1791,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1639,6 +1813,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1796,6 +1981,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1977,6 +2168,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -2000,6 +2201,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2203,6 +2410,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2253,6 +2470,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.4.1" @@ -2297,6 +2520,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.4" @@ -2455,6 +2684,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "toml" version = "0.5.9" @@ -2547,6 +2785,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -2977,6 +3227,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.7" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index b82f3b3d59..60d8733270 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -226,6 +226,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.3.1" @@ -281,7 +293,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -312,6 +324,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -810,6 +828,50 @@ dependencies = [ "syn", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -848,6 +910,18 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -870,6 +944,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.24" @@ -1107,7 +1187,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -1117,6 +1197,44 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "indenter" version = "0.3.3" @@ -1364,7 +1482,10 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", @@ -1372,6 +1493,7 @@ dependencies = [ "libsecp256k1", "loupe", "namada_proof_of_stake", + "num-rational", "parity-wasm", "proptest", "prost", @@ -1388,6 +1510,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", @@ -1513,6 +1636,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1574,6 +1709,32 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -1624,6 +1785,19 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1633,6 +1807,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1790,6 +1975,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1971,6 +2162,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -1994,6 +2195,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -2197,6 +2404,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2247,6 +2464,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.4.1" @@ -2291,6 +2514,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.4" @@ -2449,6 +2678,15 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "toml" version = "0.5.9" @@ -2530,6 +2768,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -2949,6 +3199,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + [[package]] name = "zeroize" version = "1.5.7" From 1c60077a7c28d928a7c177dd18bfd5620e401c03 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 10:52:55 +0100 Subject: [PATCH 1225/1995] Remove duplicate Makefile target --- Makefile | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Makefile b/Makefile index 8980d4b28f..b2d0393df9 100644 --- a/Makefile +++ b/Makefile @@ -127,32 +127,6 @@ test-unit-abcipp: $(TEST_FILTER) -- \ -Z unstable-options --report-time -test-unit-abcipp: - $(cargo) test \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "testing std abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path \ - ./proof_of_stake/Cargo.toml \ - --features "testing" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./shared/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "abcipp" \ - $(TEST_FILTER) -- \ - -Z unstable-options --report-time - test-unit: $(cargo) test \ $(TEST_FILTER) -- \ From 89d97b4fc8edd21e496e4a47a22f24cc912904f3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 11:01:36 +0100 Subject: [PATCH 1226/1995] Preliminary update to wasm/checksums.json --- wasm/checksums.json | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index a112ed3a63..57f17b8895 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.5536bed92d94b1aaaa82fd3531b7d4fedb00ae1f39fede7ef1634e070e5f4455.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.7243e31594d0ca5e656228bed5c84359a3d1dbfd24f1656eb9b52b0266ffaa47.wasm", - "tx_ibc.wasm": "tx_ibc.1c09d3f083a91f28708b7f09698c311e609b712da683dcbcd40e7267a1cf2adc.wasm", - "tx_init_account.wasm": "tx_init_account.95b3a1e4867160eb91f9a7812f53adf33bb44f04b9eb3f310854b7a1d7a26e77.wasm", - "tx_init_nft.wasm": "tx_init_nft.022446ca174c6ccb1e7818224ebc30515123e0bc148ec8dd59ce5b47f7447bd7.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8f93bb139e932ba24871efe22cb62177c1c7133f8957965c041accad3d04516e.wasm", - "tx_init_validator.wasm": "tx_init_validator.916a8c6a6008d5f35341e58ccafd8169b7596464f1f3253eea96c277f167fe1b.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9e12869d07ac36a9fdddf69710f2f6905c55bb4dc49c885e87d47b3d6c389ec2.wasm", - "tx_transfer.wasm": "tx_transfer.48c8bfdd119c0753a03c65764b0e099f6647dd158490fb5e04eab171c27b422e.wasm", - "tx_unbond.wasm": "tx_unbond.b2851a1eefd2e781411e495ef52ce6148893c02e06d0160298871fd231e299b4.wasm", - "tx_update_vp.wasm": "tx_update_vp.5b9786f039324205c464fec6c795d84ea3ec9b24d3c6c63e7462444811237855.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.31c96b1b171d0020c0788170f9677606589324024a90e3d532da2b9a2cedf57e.wasm", - "tx_withdraw.wasm": "tx_withdraw.5e360d269c8d3ae574ae9cbf301e1d9eed8c3ad6c103deb5e1c46ddb35af5708.wasm", - "vp_nft.wasm": "vp_nft.29b7fb0b2c247bb5389166e82707abd256177f276bbaf40ccd201ecad8039bad.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e1b7805802df2871a93f584e4df97a4584cda9d58435b14c73791fe18d93e4f4.wasm", - "vp_token.wasm": "vp_token.8332429c84d70ab6614b20a41d208ac61126141d302319a99269f6fc9e08053a.wasm", - "vp_user.wasm": "vp_user.795a1dccb7b18f0abeb07161d07e31ce5d64221a52e85098627ab60115462ba4.wasm" + "tx_bond.wasm": "tx_bond.dd1b3ae8806658dbd57bc2b64e257bc485a2af9a1d6476a32484f53ce781f198.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.866c6b884a84afb717cedc36fffbf0cb6defc188a8e9e68b9a4e7e4e78d21aa8.wasm", + "tx_ibc.wasm": "tx_ibc.04ad820117fcf130dc175496c8efa4bc60e3d042fc74f6e49e9d76aede2afb43.wasm", + "tx_init_account.wasm": "tx_init_account.610bc1aa788a09908c131f03a5d60d1dee2cc11d650c1f3bec0740bcc29b2182.wasm", + "tx_init_nft.wasm": "tx_init_nft.f554ea84b41bebb5c40c3e33652c0b1ab75bce49165928dc4d62808bfbe9ced4.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f53ae709af7fb9628d97025fe62dc1b009ab0d251a397477f948b174cac901fe.wasm", + "tx_init_validator.wasm": "tx_init_validator.78d6379572fa3e02eba9399fd4105789961d202ddec559f84ca861f12cceb055.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f52cb55a1137bea901f8251016058fa0d95d3eda69fe63da4604354be893293b.wasm", + "tx_transfer.wasm": "tx_transfer.1dac0aeca114a45120b2b50869bd08e50735e1ffc26545b4e8f350766321f697.wasm", + "tx_unbond.wasm": "tx_unbond.24bcf1f55eed85c6d45d0dc365b0c03132c6c70c9423ed58b65b22fd0c58947e.wasm", + "tx_update_vp.wasm": "tx_update_vp.145eb68c770df1cb31cb0c7faac579bfe1d39864739cc16c29b62f342c33f178.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d0caf71c526d1c2c357d74717dd33ba991fff08ea79e7856d520c61b21e848c3.wasm", + "tx_withdraw.wasm": "tx_withdraw.b1f3177f2ded0b0336ede5ae171f834c080964534b76de6af3b943f31944f8a6.wasm", + "vp_nft.wasm": "vp_nft.682c293bcb4ef89f98dfaa707de92170f03acf3870e00db902bb3beab077a026.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.78b579c7b8e5c0506d413f7b4ede4dbc37a3b2277e6f8a733cbe82a059bc8ef8.wasm", + "vp_token.wasm": "vp_token.9b81e87a13c5912159de2aa86ec917adb87e19c94d463e8726a03180806f3083.wasm", + "vp_user.wasm": "vp_user.17a9d4205db4159f19292e456ea500450e38e23765a37ff7cb99a4c7dd7b0087.wasm" } \ No newline at end of file From 5b80738e83767d041ab21f92b7f57694b5e89b85 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 11:37:25 +0100 Subject: [PATCH 1227/1995] bridge pool tests: remove duplicate variables --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 97b66a4dcb..a765a423af 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -604,6 +604,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); + // inform the vp that the merkle root changed let keys_changed = BTreeSet::default(); let verifiers = BTreeSet::default(); @@ -617,9 +618,6 @@ mod test_bridge_pool_vp { &verifiers, ), }; - // inform the vp that the merkle root changed - let keys_changed = BTreeSet::default(); - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); From 5d3e7eceab063d63041690f1826be85f4e82ff14 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 12:36:00 +0100 Subject: [PATCH 1228/1995] Fix StorageReader --- shared/src/ledger/native_vp.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 27be0832d7..6e3d8ad2af 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -523,8 +523,8 @@ where where T: BorshDeserialize, { - let maybe_bytes = Ctx::read_post(self, key) - .wrap_err_with(|| format!("couldn't read_post {}", key))?; + let maybe_bytes = Ctx::read_bytes_post(self, key) + .wrap_err_with(|| format!("couldn't read_bytes_post {}", key))?; Self::deserialize_if_present(maybe_bytes) } @@ -534,8 +534,8 @@ where where T: BorshDeserialize, { - let maybe_bytes = Ctx::read_pre(self, key) - .wrap_err_with(|| format!("couldn't read_pre {}", key))?; + let maybe_bytes = Ctx::read_bytes_pre(self, key) + .wrap_err_with(|| format!("couldn't read_bytes_pre {}", key))?; Self::deserialize_if_present(maybe_bytes) } } From 10f5c5458cd488bd058ca987c24e5ae8b400a277 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 12:41:05 +0100 Subject: [PATCH 1229/1995] Fix tower-abci imports --- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 35e3e63576..890e8a4513 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -10,12 +10,12 @@ use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; -use tower_abci::{request, response}; use super::*; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; +use crate::facade::tower_abci::{request, response}; use crate::wasm_loader; impl Shell From bd1cdd82358393647433ae987aeb3c1b829812c7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 14:05:10 +0100 Subject: [PATCH 1230/1995] Run make fmt --- shared/src/ledger/ibc/vp/port.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs index c82445bc15..9b91aac957 100644 --- a/shared/src/ledger/ibc/vp/port.rs +++ b/shared/src/ledger/ibc/vp/port.rs @@ -16,8 +16,8 @@ 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::ibc::core::ics26_routing::context::ModuleId; -use crate::ledger::storage::traits::StorageHasher; use crate::ledger::native_vp::VpEnv; +use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{self as ledger_storage}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; From 154254ab44060df416aa5f9c311cc037da7e5337 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 15:14:09 +0200 Subject: [PATCH 1231/1995] [feat]: Write the ethereum bridge config params on genesis --- apps/src/lib/config/ethereum_bridge/params.rs | 85 ++++-------- apps/src/lib/config/genesis.rs | 10 +- apps/src/lib/node/ledger/shell/init_chain.rs | 121 +++++++++++++++--- apps/src/lib/node/ledger/shell/mod.rs | 4 + apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +++++++++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 +++++++++++ shared/src/ledger/parameters/mod.rs | 1 + shared/src/ledger/storage/mockdb.rs | 54 ++++++++ shared/src/ledger/storage/mod.rs | 54 ++++++++ shared/src/types/ethereum_events.rs | 36 ++++++ 10 files changed, 440 insertions(+), 82 deletions(-) create mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 0ab56b0b47..d76b6964fd 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,33 +1,22 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. -use std::num::NonZeroU64; - +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -/// Represents a configuration value for an Ethereum address. -/// -/// For instance: -/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct Address(String); - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - /// Represents chain parameters for the Ethereum bridge. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct GenesisConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. @@ -39,7 +28,16 @@ pub struct GenesisConfig { /// Represents all the Ethereum contracts that need to be directly know about by /// validators. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. @@ -50,33 +48,10 @@ pub struct Contracts { pub governance: UpgradeableContract, } -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: Address, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} - #[cfg(test)] mod tests { use eyre::Result; + use namada::ledger::parameters::ethereum_bridge::ContractVersion; use super::*; @@ -90,17 +65,11 @@ mod tests { contracts: Contracts { native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { - address: Address( - "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" - .to_string(), - ), + address: EthAddress([23; 20]), version: ContractVersion::default(), }, governance: UpgradeableContract { - address: Address( - "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" - .to_string(), - ), + address: EthAddress([18; 20]), version: ContractVersion::default(), }, }, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 288f0efbd4..7fa47bbc61 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -18,6 +18,8 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; +use crate::config::ethereum_bridge; + /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -41,10 +43,10 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, + ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + TokenAccount, Validator, }; use crate::cli; - use crate::config::ethereum_bridge; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct HexString(pub String); @@ -609,6 +611,7 @@ pub mod genesis_config { pos_params, gov_params, treasury_params, + ethereum_bridge_params: config.ethereum_bridge_params, }; genesis.init(); genesis @@ -644,6 +647,8 @@ pub struct Genesis { pub pos_params: PosParams, pub gov_params: GovParams, pub treasury_params: TreasuryParams, + // Ethereum bridge config + pub ethereum_bridge_params: Option, } impl Genesis { @@ -892,6 +897,7 @@ pub fn genesis() -> Genesis { pos_params: PosParams::default(), gov_params: GovParams::default(), treasury_params: TreasuryParams::default(), + ethereum_bridge_params: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 77a23003da..6800dfde4b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::parameters::Parameters; +use namada::ledger::pos::PosParams; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; use super::queries::QueriesExt; use super::*; +use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -21,12 +24,13 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens - /// 3. A matchmaker + /// 3. Validators + /// 4. The PoS system + /// 5. The Ethereum bridge parameters pub fn init_chain( &mut self, init: request::InitChain, ) -> Result { - let mut response = response::InitChain::default(); let (current_chain_id, _) = self.storage.get_chain_id(); if current_chain_id != init.chain_id { return Err(Error::ChainId(format!( @@ -76,13 +80,46 @@ where let mut vp_code_cache: HashMap> = HashMap::default(); // Initialize genesis established accounts + self.initialize_established_accounts( + genesis.established_accounts, + &mut vp_code_cache, + ); + + // Initialize genesis implicit + self.initialize_implicit_accounts(genesis.implicit_accounts); + + // Initialize genesis token accounts + self.initialize_token_accounts( + genesis.token_accounts, + &mut vp_code_cache, + ); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + self.configure_ethereuem_bridge(config); + } + // Initialize genesis validator accounts + self.initialize_validators(&genesis.validators, &mut vp_code_cache); + // set the initial validators set + Ok(self.set_initial_validators( + genesis.validators, + &genesis.parameters, + &genesis.pos_params, + )) + } + + /// Initialize genesis established accounts + fn initialize_established_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { for genesis::EstablishedAccount { address, vp_code_path, vp_sha256, public_key, storage, - } in genesis.established_accounts + } in accounts { let vp_code = vp_code_cache .get_or_insert_with(vp_code_path.clone(), || { @@ -120,24 +157,36 @@ where self.storage.write(&key, value).unwrap(); } } + } + /// Initialize genesis implicit accounts + fn initialize_implicit_accounts( + &mut self, + accounts: Vec, + ) { // Initialize genesis implicit - for genesis::ImplicitAccount { public_key } in genesis.implicit_accounts - { + for genesis::ImplicitAccount { public_key } in accounts { let address: address::Address = (&public_key).into(); let pk_storage_key = pk_key(&address); self.storage .write(&pk_storage_key, public_key.try_to_vec().unwrap()) .unwrap(); } + } + /// Initialize genesis token accounts + fn initialize_token_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis token accounts for genesis::TokenAccount { address, vp_code_path, vp_sha256, balances, - } in genesis.token_accounts + } in accounts { let vp_code = vp_code_cache .get_or_insert_with(vp_code_path.clone(), || { @@ -173,9 +222,16 @@ where .unwrap(); } } + } + /// Initialize genesis validator accounts + fn initialize_validators( + &mut self, + validators: &[genesis::Validator], + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis validator accounts - for validator in &genesis.validators { + for validator in validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), || { @@ -244,31 +300,35 @@ where ) .expect("Unable to set genesis user public DKG session key"); } + } + /// Initialize the PoS and set the initial validator set + fn set_initial_validators( + &mut self, + validators: Vec, + parameters: &Parameters, + pos_params: &PosParams, + ) -> response::InitChain { + let mut response = response::InitChain::default(); // PoS system depends on epoch being initialized let (current_epoch, _gas) = self.storage.get_current_epoch(); pos::init_genesis_storage( &mut self.storage, - &genesis.pos_params, - genesis - .validators - .iter() - .map(|validator| &validator.pos_data), + pos_params, + validators.iter().map(|validator| &validator.pos_data), current_epoch, ); ibc::init_genesis_storage(&mut self.storage); - let evidence_params = self.storage.get_evidence_params( - &genesis.parameters.epoch_duration, - &genesis.pos_params, - ); + let evidence_params = self + .storage + .get_evidence_params(¶meters.epoch_duration, 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 { + for validator in validators { let mut abci_validator = abci::ValidatorUpdate::default(); let consensus_key: common::PublicKey = validator.pos_data.consensus_key.clone(); @@ -276,14 +336,33 @@ where sum: Some(key_to_tendermint(&consensus_key).unwrap()), }; abci_validator.pub_key = Some(pub_key); - let power: u64 = - validator.pos_data.voting_power(&genesis.pos_params).into(); + let power: u64 = validator.pos_data.voting_power(pos_params).into(); abci_validator.power = power .try_into() .expect("unexpected validator's voting power"); response.validators.push(abci_validator); } - Ok(response) + response + } + + /// Set the parameters for the Ethereum bridge + fn configure_ethereuem_bridge( + &mut self, + config: ethereum_bridge::params::GenesisConfig, + ) { + let ethereum_bridge::params::GenesisConfig { + min_confirmations, + contracts: + ethereum_bridge::params::Contracts { + native_erc20, + bridge, + governance, + }, + } = config; + self.storage.min_confirmations = Some(min_confirmations); + self.storage.native_erc20 = Some(native_erc20); + self.storage.bridge_contract = Some(bridge); + self.storage.governance_contract = Some(governance); } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f926d940b8..744e856597 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1126,6 +1126,10 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 069b6ed0a7..bccebd1cb0 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,7 +5,13 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` +//! - `tx_queue` +//! - `min_confirmations`: Minimum number of confirmations needed for the +//! Ethereum bridge to consider an event final. +//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents +//! this chain's native token. +//! - `bridge_contract`: The Ethereum address of the bridge contract. +//! - `governance_contract`: The Ethereum address of the governance contract. //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -32,11 +38,15 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; +use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -300,6 +310,61 @@ impl DB for RocksDB { return Ok(None); } }; + let min_confirmations: Option = match self + .0 + .get("min_confirmations") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the minimum confirmations from the DB" + ); + return Ok(None); + } + }; + let native_erc20: Option = match self + .0 + .get("native_erc20") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum address for wrapped Nam from \ + the DB" + ); + return Ok(None); + } + }; + let bridge_contract: Option = match self + .0 + .get("bridge_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum bridge contract address from \ + the DB" + ); + return Ok(None); + } + }; + let governance_contract: Option = match self + .0 + .get("governance_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum governance contract from the \ + DB" + ); + return Ok(None); + } + }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -398,6 +463,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -420,6 +489,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -448,6 +521,10 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); + batch.put("min_confirmations", types::encode(&min_confirmations)); + batch.put("native_erc20", types::encode(&native_erc20)); + batch.put("bridge_contract", types::encode(&bridge_contract)); + batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -976,6 +1053,10 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs new file mode 100644 index 0000000000..1e54207f6e --- /dev/null +++ b/shared/src/ledger/parameters/ethereum_bridge.rs @@ -0,0 +1,74 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4891818dc0..0ec554dd80 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,4 +1,5 @@ //! Protocol parameters +pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 99260d8333..64766c76d6 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,7 +12,11 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,6 +77,34 @@ impl DB for MockDB { } None => return Ok(None), }; + let min_confirmations: Option = + match self.0.borrow().get("min_confirmations") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let native_erc20: Option = + match self.0.borrow().get("native_erc20") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let bridge_contract: Option = + match self.0.borrow().get("bridge_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let governance_contract: Option = + match self.0.borrow().get("governance_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -153,6 +185,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -175,6 +211,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -186,6 +226,20 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); + self.0.borrow_mut().insert( + "min_confirmations".into(), + types::encode(&min_confirmations), + ); + self.0 + .borrow_mut() + .insert("native_erc20".into(), types::encode(&native_erc20)); + self.0 + .borrow_mut() + .insert("bridge_contract".into(), types::encode(&bridge_contract)); + self.0.borrow_mut().insert( + "goverenance_contract".into(), + types::encode(&governance_contract), + ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index c2464c52d1..6ddbdecef9 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -16,6 +16,9 @@ use thiserror::Error; use super::parameters; use super::parameters::Parameters; use crate::ledger::gas::MIN_STORAGE_GAS; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -27,6 +30,7 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -68,6 +72,16 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block storage data @@ -127,6 +141,16 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block's state to write into the database. @@ -152,6 +176,16 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// A database backend. @@ -296,6 +330,10 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } @@ -313,6 +351,10 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract: bridge, + governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -329,6 +371,10 @@ where { self.tx_queue = tx_queue; } + self.min_confirmations = min_confirmations; + self.native_erc20 = native_erc20; + self.bridge_contract = bridge; + self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -360,6 +406,10 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, + min_confirmations: self.min_confirmations, + native_erc20: self.native_erc20.clone(), + bridge_contract: self.bridge_contract.clone(), + governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -760,6 +810,10 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } } diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 5a66906a49..c6c8c061a2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -62,6 +62,8 @@ impl From for Uint { BorshDeserialize, BorshSchema, )] +#[serde(try_from = "String")] +#[serde(into = "String")] pub struct EthAddress(pub [u8; 20]); impl EthAddress { @@ -91,6 +93,20 @@ impl FromStr for EthAddress { } } +impl TryFrom for EthAddress { + type Error = eyre::Error; + + fn try_from(string: String) -> Result { + Self::from_str(string.as_ref()) + } +} + +impl From for String { + fn from(addr: EthAddress) -> Self { + addr.to_string() + } +} + /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, @@ -278,6 +294,26 @@ pub mod tests { assert!(result.is_err()); } + + /// Test that serde correct serializes EthAddress types to/from lowercase + /// hex encodings + #[test] + fn test_eth_address_serde_roundtrip() { + let addr = + EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED) + .unwrap(); + let serialized = serde_json::to_string(&addr).expect("Test failed"); + assert_eq!( + serialized, + format!( + r#""{}""#, + testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_lowercase() + ) + ); + let deserialized: EthAddress = + serde_json::from_str(&serialized).expect("Test failed"); + assert_eq!(addr, deserialized); + } } #[allow(missing_docs)] From 1333e6e97bd23b8f439b595e0ae6a33afa4e9065 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 15:30:04 +0100 Subject: [PATCH 1232/1995] Fix clippy --- shared/src/ledger/ibc/vp/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 433a4f1c12..45d66443bc 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -557,7 +557,7 @@ where fn host_height(&self) -> Height { let height = self.ctx.storage.get_block_height().0.0; // the revision number is always 0 - Height::new(0, height.into()) + Height::new(0, height) } fn host_consensus_state( From 768948e0efb6fa229c510f1299a7a8593d6f543c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 16:08:03 +0100 Subject: [PATCH 1233/1995] Fix wasms --- wasm/wasm_source/src/tx_bond.rs | 4 ++++ wasm/wasm_source/src/tx_unbond.rs | 8 ++++++-- wasm/wasm_source/src/tx_withdraw.rs | 8 ++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..18a96ad60d 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -71,6 +71,8 @@ mod tests { 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 eth_cold_key = key::testing::keypair_3().ref_to(); + let eth_hot_key = key::testing::keypair_4().ref_to(); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), @@ -78,6 +80,8 @@ mod tests { tokens: initial_stake, consensus_key, staking_reward_key, + eth_cold_key, + eth_hot_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..78cdfea252 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -70,6 +70,8 @@ mod tests { 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 eth_cold_key = key::testing::keypair_3().ref_to(); + let eth_hot_key = key::testing::keypair_4().ref_to(); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), @@ -83,6 +85,8 @@ mod tests { }, consensus_key, staking_reward_key, + eth_cold_key, + eth_hot_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); @@ -382,8 +386,8 @@ mod tests { Ok(()) } - fn arb_initial_stake_and_unbond() - -> impl Strategy { + 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 diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..609bb0c3b5 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -76,6 +76,8 @@ mod tests { 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 eth_cold_key = key::testing::keypair_3().ref_to(); + let eth_hot_key = key::testing::keypair_4().ref_to(); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), @@ -90,6 +92,8 @@ mod tests { }, consensus_key, staking_reward_key, + eth_cold_key, + eth_hot_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); @@ -198,8 +202,8 @@ mod tests { Ok(()) } - fn arb_initial_stake_and_unbonded_amount() - -> impl Strategy { + 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 From 73e03b46e117b5c6e0e22e9bb88ac7fb89fc81e4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 16:41:45 +0100 Subject: [PATCH 1234/1995] Run make fmt --- wasm/wasm_source/src/tx_unbond.rs | 4 ++-- wasm/wasm_source/src/tx_withdraw.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 78cdfea252..851329b908 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -386,8 +386,8 @@ mod tests { Ok(()) } - fn arb_initial_stake_and_unbond( - ) -> impl Strategy { + 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 diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 609bb0c3b5..675b079609 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -202,8 +202,8 @@ mod tests { Ok(()) } - fn arb_initial_stake_and_unbonded_amount( - ) -> impl Strategy { + 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 From 91f2fcb82866eaa147009da795f421eed31aa04a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 16:45:17 +0100 Subject: [PATCH 1235/1995] Temporarily disable some e2e tests --- tests/src/e2e/ledger_tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e651269704..6e76ddb079 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -779,6 +779,8 @@ fn pos_bonds() -> Result<()> { /// 6. Wait for the pipeline epoch /// 7. Check the new validator's voting power #[test] +#[ignore] +// TODO: fix and enable for eth-bridge-integration fn pos_init_validator() -> Result<()> { let pipeline_len = 1; let test = setup::network( @@ -1953,6 +1955,8 @@ fn test_genesis_validators() -> Result<()> { /// 5. Submit a valid token transfer tx to validator 0 /// 6. Wait for double signing evidence #[test] +#[ignore] +// TODO: fix and enable for eth-bridge-integration fn double_signing_gets_slashed() -> Result<()> { use std::net::SocketAddr; use std::str::FromStr; From 1f7b94f06be96f4bfdfd7de70db8c777107ce517 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 28 Oct 2022 16:06:23 +0000 Subject: [PATCH 1236/1995] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 57f17b8895..4001a69fa0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.dd1b3ae8806658dbd57bc2b64e257bc485a2af9a1d6476a32484f53ce781f198.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.866c6b884a84afb717cedc36fffbf0cb6defc188a8e9e68b9a4e7e4e78d21aa8.wasm", - "tx_ibc.wasm": "tx_ibc.04ad820117fcf130dc175496c8efa4bc60e3d042fc74f6e49e9d76aede2afb43.wasm", - "tx_init_account.wasm": "tx_init_account.610bc1aa788a09908c131f03a5d60d1dee2cc11d650c1f3bec0740bcc29b2182.wasm", - "tx_init_nft.wasm": "tx_init_nft.f554ea84b41bebb5c40c3e33652c0b1ab75bce49165928dc4d62808bfbe9ced4.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f53ae709af7fb9628d97025fe62dc1b009ab0d251a397477f948b174cac901fe.wasm", - "tx_init_validator.wasm": "tx_init_validator.78d6379572fa3e02eba9399fd4105789961d202ddec559f84ca861f12cceb055.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.f52cb55a1137bea901f8251016058fa0d95d3eda69fe63da4604354be893293b.wasm", - "tx_transfer.wasm": "tx_transfer.1dac0aeca114a45120b2b50869bd08e50735e1ffc26545b4e8f350766321f697.wasm", - "tx_unbond.wasm": "tx_unbond.24bcf1f55eed85c6d45d0dc365b0c03132c6c70c9423ed58b65b22fd0c58947e.wasm", - "tx_update_vp.wasm": "tx_update_vp.145eb68c770df1cb31cb0c7faac579bfe1d39864739cc16c29b62f342c33f178.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d0caf71c526d1c2c357d74717dd33ba991fff08ea79e7856d520c61b21e848c3.wasm", - "tx_withdraw.wasm": "tx_withdraw.b1f3177f2ded0b0336ede5ae171f834c080964534b76de6af3b943f31944f8a6.wasm", - "vp_nft.wasm": "vp_nft.682c293bcb4ef89f98dfaa707de92170f03acf3870e00db902bb3beab077a026.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.78b579c7b8e5c0506d413f7b4ede4dbc37a3b2277e6f8a733cbe82a059bc8ef8.wasm", - "vp_token.wasm": "vp_token.9b81e87a13c5912159de2aa86ec917adb87e19c94d463e8726a03180806f3083.wasm", - "vp_user.wasm": "vp_user.17a9d4205db4159f19292e456ea500450e38e23765a37ff7cb99a4c7dd7b0087.wasm" + "tx_bond.wasm": "tx_bond.ad18675419b9dd88d4eaf170366754dd12fddc45a67e2cd100b1554ea693de43.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_ibc.wasm": "tx_ibc.b5a3bf6ca1dea0767406d64251928816a7d95b974cff09f6e702a8f3fbd64b1f.wasm", + "tx_init_account.wasm": "tx_init_account.343e04328e157514ec85cfb650cad5cad659eac27e80b1a0dec61286352a3c9d.wasm", + "tx_init_nft.wasm": "tx_init_nft.36437ec1cf161d3e21f8a4f1e939676914dfdcb7ee667c92c2807767ddfabdb3.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c8e187e2bd7869253f9d9d7de5fa2cb5896e9ca114f124ad800131c9e82db1a7.wasm", + "tx_init_validator.wasm": "tx_init_validator.8c047862fb2e538dfe414880a9b847741b84a0b8fcb4beb67752f58b7d954b6f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.110baf82d92f78ca740750808237ecd4793e24b662876f363f51c5d1cd030694.wasm", + "tx_transfer.wasm": "tx_transfer.07335720d2ab07311219a81fd6baca5801792dc16b7c9bab490e2b756257a6dd.wasm", + "tx_unbond.wasm": "tx_unbond.895123c93e6e7c6d2303be44cc9332a7a9a867cf159ce87cf55abb155e8d83ca.wasm", + "tx_update_vp.wasm": "tx_update_vp.d2743de89548f3ae6decf2a32ab086e960be9b954bdd24bd6e8e731195449540.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.348c3c28fc9e7356a7106f4b037a0bc7b6b207761b000ad0e0cb786678afbee5.wasm", + "tx_withdraw.wasm": "tx_withdraw.a2a0a3f9eb961cba5bb4d1677805bafdcc807637fbd203f8afaa2aa2adb6857e.wasm", + "vp_nft.wasm": "vp_nft.e88e46e49cbbc28dd1fc4e518195bffc4d1feb43b4976d02580865298fd29e75.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f6b3d44133b0c35cdbfbe19328d8cdfc62809bd30a0c64eef57f3877cc7e8c2f.wasm", + "vp_token.wasm": "vp_token.2100aaa1fed90d35e87aace6516794facdd7aab3062102c1a762bef604c98075.wasm", + "vp_user.wasm": "vp_user.14fdcbaa1bd3c28115a3eb1f53802b4042080bb255e276b15d2fb16338aacf31.wasm" } \ No newline at end of file From cec75fb80665c809800f40c586ed1a478bbe6dd4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 17:27:46 +0100 Subject: [PATCH 1237/1995] Revert "Temporarily disable some e2e tests" This reverts commit 91f2fcb82866eaa147009da795f421eed31aa04a. --- tests/src/e2e/ledger_tests.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 6e76ddb079..e651269704 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -779,8 +779,6 @@ fn pos_bonds() -> Result<()> { /// 6. Wait for the pipeline epoch /// 7. Check the new validator's voting power #[test] -#[ignore] -// TODO: fix and enable for eth-bridge-integration fn pos_init_validator() -> Result<()> { let pipeline_len = 1; let test = setup::network( @@ -1955,8 +1953,6 @@ fn test_genesis_validators() -> Result<()> { /// 5. Submit a valid token transfer tx to validator 0 /// 6. Wait for double signing evidence #[test] -#[ignore] -// TODO: fix and enable for eth-bridge-integration fn double_signing_gets_slashed() -> Result<()> { use std::net::SocketAddr; use std::str::FromStr; From 50665e8effac5ce67c2ac287e5e4c0abd565c5d6 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 18:52:47 +0200 Subject: [PATCH 1238/1995] [feat]: Refactored the eth bridge params to live under the bridge vp storage --- apps/src/lib/config/ethereum_bridge/mod.rs | 37 ++++- apps/src/lib/config/ethereum_bridge/params.rs | 83 ---------- apps/src/lib/config/genesis.rs | 10 +- apps/src/lib/node/ledger/shell/init_chain.rs | 30 +--- apps/src/lib/node/ledger/shell/mod.rs | 4 - apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +--------- shared/src/ledger/eth_bridge/mod.rs | 1 + shared/src/ledger/eth_bridge/parameters.rs | 152 ++++++++++++++++++ shared/src/ledger/eth_bridge/storage/mod.rs | 51 +++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 --------- shared/src/ledger/parameters/mod.rs | 1 - shared/src/ledger/storage/mockdb.rs | 54 ------- shared/src/ledger/storage/mod.rs | 54 ------- 13 files changed, 249 insertions(+), 385 deletions(-) delete mode 100644 apps/src/lib/config/ethereum_bridge/params.rs create mode 100644 shared/src/ledger/eth_bridge/parameters.rs delete mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index ab72d58b42..d4606b0652 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,2 +1,37 @@ pub mod ledger; -pub mod params; + +#[cfg(test)] +mod tests { + use eyre::Result; + use namada::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use namada::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs deleted file mode 100644 index d76b6964fd..0000000000 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Blockchain-level parameters for the configuration of the Ethereum bridge. -use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; -use namada::types::ethereum_events::EthAddress; -use serde::{Deserialize, Serialize}; - -/// Represents chain parameters for the Ethereum bridge. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct GenesisConfig { - /// Minimum number of confirmations needed to trust an Ethereum branch. - /// This must be at least one. - pub min_confirmations: MinimumConfirmations, - /// The addresses of the Ethereum contracts that need to be directly known - /// by validators. - pub contracts: Contracts, -} - -/// Represents all the Ethereum contracts that need to be directly know about by -/// validators. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct Contracts { - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: EthAddress, - /// The Ethereum address of the bridge contract. - pub bridge: UpgradeableContract, - /// The Ethereum address of the governance contract. - pub governance: UpgradeableContract, -} - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::parameters::ethereum_bridge::ContractVersion; - - use super::*; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = GenesisConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: GenesisConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 7fa47bbc61..4498a345d2 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,6 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -18,8 +19,6 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; -use crate::config::ethereum_bridge; - /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -43,7 +42,7 @@ pub mod genesis_config { use thiserror::Error; use super::{ - ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; @@ -125,8 +124,7 @@ pub mod genesis_config { // Treasury parameters pub treasury_params: TreasuryParamasConfig, // Ethereum bridge config - pub ethereum_bridge_params: - Option, + pub ethereum_bridge_params: Option, // Wasm definitions pub wasm: HashMap, } @@ -648,7 +646,7 @@ pub struct Genesis { pub gov_params: GovParams, pub treasury_params: TreasuryParams, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 6800dfde4b..a65bccf60f 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -10,7 +10,6 @@ use sha2::{Digest, Sha256}; use super::queries::QueriesExt; use super::*; -use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -66,6 +65,10 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); genesis.treasury_params.init_storage(&mut self.storage); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + config.init_storage(&mut self.storage); + } // Depends on parameters being initialized self.storage @@ -93,10 +96,7 @@ where genesis.token_accounts, &mut vp_code_cache, ); - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - self.configure_ethereuem_bridge(config); - } + // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set @@ -344,26 +344,6 @@ where } response } - - /// Set the parameters for the Ethereum bridge - fn configure_ethereuem_bridge( - &mut self, - config: ethereum_bridge::params::GenesisConfig, - ) { - let ethereum_bridge::params::GenesisConfig { - min_confirmations, - contracts: - ethereum_bridge::params::Contracts { - native_erc20, - bridge, - governance, - }, - } = config; - self.storage.min_confirmations = Some(min_confirmations); - self.storage.native_erc20 = Some(native_erc20); - self.storage.bridge_contract = Some(bridge); - self.storage.governance_contract = Some(governance); - } } trait HashMapExt diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 744e856597..f926d940b8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1126,10 +1126,6 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index bccebd1cb0..069b6ed0a7 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,13 +5,7 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` -//! - `min_confirmations`: Minimum number of confirmations needed for the -//! Ethereum bridge to consider an event final. -//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents -//! this chain's native token. -//! - `bridge_contract`: The Ethereum address of the bridge contract. -//! - `governance_contract`: The Ethereum address of the governance contract. +//! - `tx_queue` //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -38,15 +32,11 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; -use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -310,61 +300,6 @@ impl DB for RocksDB { return Ok(None); } }; - let min_confirmations: Option = match self - .0 - .get("min_confirmations") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the minimum confirmations from the DB" - ); - return Ok(None); - } - }; - let native_erc20: Option = match self - .0 - .get("native_erc20") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum address for wrapped Nam from \ - the DB" - ); - return Ok(None); - } - }; - let bridge_contract: Option = match self - .0 - .get("bridge_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum bridge contract address from \ - the DB" - ); - return Ok(None); - } - }; - let governance_contract: Option = match self - .0 - .get("governance_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum governance contract from the \ - DB" - ); - return Ok(None); - } - }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -463,10 +398,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -489,10 +420,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -521,10 +448,6 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); - batch.put("min_confirmations", types::encode(&min_confirmations)); - batch.put("native_erc20", types::encode(&native_erc20)); - batch.put("bridge_contract", types::encode(&bridge_contract)); - batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -1053,10 +976,6 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index 047bcda91e..a740fa16a2 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,5 +1,6 @@ //! Validity predicate and storage keys for the Ethereum bridge account pub mod bridge_pool_vp; +pub mod parameters; pub mod storage; pub mod vp; diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs new file mode 100644 index 0000000000..2d22730a8a --- /dev/null +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -0,0 +1,152 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::storage::types::encode; +use crate::ledger::storage::{self, Storage}; +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} + +/// Represents all the Ethereum contracts that need to be directly know about by +/// validators. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct Contracts { + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: EthAddress, + /// The Ethereum address of the bridge contract. + pub bridge: UpgradeableContract, + /// The Ethereum address of the governance contract. + pub governance: UpgradeableContract, +} + +/// Represents chain parameters for the Ethereum bridge. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct EthereumBridgeConfig { + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +impl EthereumBridgeConfig { + /// Initialize the Ethereum bridge parameters in storage + pub fn init_storage(&self, storage: &mut Storage) + where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::traits::StorageHasher, + { + let Self { + min_confirmations, + contracts: + Contracts { + native_erc20, + bridge, + governance, + }, + } = self; + let min_confirmations_key = bridge_storage::min_confirmations_key(); + let native_erc20_key = bridge_storage::native_erc20_key(); + let bridge_contract_key = bridge_storage::bridge_contract_key(); + let governance_contract_key = bridge_storage::governance_contract_key(); + storage + .write(&min_confirmations_key, encode(min_confirmations)) + .unwrap(); + storage + .write(&native_erc20_key, encode(native_erc20)) + .unwrap(); + storage.write(&bridge_contract_key, encode(bridge)).unwrap(); + storage + .write(&governance_contract_key, encode(governance)) + .unwrap(); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 595f403034..b117f4b42d 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -4,7 +4,16 @@ pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; + +/// Sub-key for storing the minimum confirmations parameter +pub const MIN_CONFIRMATIONS_SUBKEY: &str = "min_confirmations"; +/// Sub-key for storing the Ethereum address for wNam. +pub const NATIVE_ERC20_SUBKEY: &str = "native_erc20"; +/// Sub-lkey for storing the Ethereum address of the bridge contract. +pub const BRIDGE_CONTRACT_SUBKEY: &str = "bridge_contract_address"; +/// Sub-key for storing the Ethereum address of the governance contract. +pub const GOVERNANCE_CONTRACT_SUBKEY: &str = "governance_contract_address"; /// Key prefix for the storage subspace pub fn prefix() -> Key { @@ -16,6 +25,46 @@ pub fn is_eth_bridge_key(key: &Key) -> bool { matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) } +/// Storage key for the minimum confirmations parameter. +pub fn min_confirmations_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(MIN_CONFIRMATIONS_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of wNam. +pub fn native_erc20_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(NATIVE_ERC20_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the bridge contract. +pub fn bridge_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(BRIDGE_CONTRACT_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the governance contract. +pub fn governance_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(GOVERNANCE_CONTRACT_SUBKEY.into()), + ], + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs deleted file mode 100644 index 1e54207f6e..0000000000 --- a/shared/src/ledger/parameters/ethereum_bridge.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Parameters for configuring the Ethereum bridge -use std::num::NonZeroU64; - -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; - -use crate::types::ethereum_events::EthAddress; - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: EthAddress, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 0ec554dd80..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,5 +1,4 @@ //! Protocol parameters -pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 64766c76d6..99260d8333 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,11 +12,7 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -77,34 +73,6 @@ impl DB for MockDB { } None => return Ok(None), }; - let min_confirmations: Option = - match self.0.borrow().get("min_confirmations") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let native_erc20: Option = - match self.0.borrow().get("native_erc20") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let bridge_contract: Option = - match self.0.borrow().get("bridge_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let governance_contract: Option = - match self.0.borrow().get("governance_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -185,10 +153,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -211,10 +175,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -226,20 +186,6 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); - self.0.borrow_mut().insert( - "min_confirmations".into(), - types::encode(&min_confirmations), - ); - self.0 - .borrow_mut() - .insert("native_erc20".into(), types::encode(&native_erc20)); - self.0 - .borrow_mut() - .insert("bridge_contract".into(), types::encode(&bridge_contract)); - self.0.borrow_mut().insert( - "goverenance_contract".into(), - types::encode(&governance_contract), - ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 6ddbdecef9..c2464c52d1 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -16,9 +16,6 @@ use thiserror::Error; use super::parameters; use super::parameters::Parameters; use crate::ledger::gas::MIN_STORAGE_GAS; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -30,7 +27,6 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -72,16 +68,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block storage data @@ -141,16 +127,6 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block's state to write into the database. @@ -176,16 +152,6 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// A database backend. @@ -330,10 +296,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } @@ -351,10 +313,6 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract: bridge, - governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -371,10 +329,6 @@ where { self.tx_queue = tx_queue; } - self.min_confirmations = min_confirmations; - self.native_erc20 = native_erc20; - self.bridge_contract = bridge; - self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -406,10 +360,6 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, - min_confirmations: self.min_confirmations, - native_erc20: self.native_erc20.clone(), - bridge_contract: self.bridge_contract.clone(), - governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -810,10 +760,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } } From 788d407baa34b251c66718a8c1f8c1592aaa83fa Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 17:56:56 +0100 Subject: [PATCH 1239/1995] Rebuilt test wasms --- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135219 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 229247 -> 235723 bytes wasm_for_tests/tx_no_op.wasm | Bin 25027 -> 24984 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 212270 -> 210937 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 154943 -> 153794 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 228606 -> 227003 bytes wasm_for_tests/vp_always_false.wasm | Bin 160341 -> 159004 bytes wasm_for_tests/vp_always_true.wasm | Bin 160683 -> 159348 bytes wasm_for_tests/vp_eval.wasm | Bin 161259 -> 159925 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 162871 -> 162007 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 176352 -> 174572 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 dd2372605d64f675a874dd0604fa352f0803151c..ba3d60787730d5a3724806cd62eb1dbd545ed5a7 100755 GIT binary patch delta 1127 zcmZ`%Uu;uV7(d@R=eD;y?RvYdUFo{+_I87{Wwsmcj&+;v3`Z9MrzU2G7ly_M?-*Oq z2Ox26!uVri0(ue>5Ngj zl=*egg8on=BNQdV0W=5)08p&y*aNkPa?k@P6T-O$^N`PL3`oQmz$LO;T_(aGqrTa< zX6N=Dpd_$s_x${x1H0bcPgtM`M1m;hG~Miob#}o(U)YEgo1X1&Zi#McUT5X<8+)E9 z4Gr7_>Sx=iGc2u4{|LpLDhZfi{VLZP~P3%wd!)k-Oq2;hk%HYFlR#vc87 z?*7?3PtLz6IFatnma6~>bv$+h+SJb0*Ad;;gRCtC>Xmg-H6EXXHuoZ8Y;EZm-qNQ> zyrp8bQK*M@i~Ch#$q(&n+KT;a?c-zE^#OuWc7L^20Fr8{^D<=AY}XWI-0NMR^H`b) z`S^WoNfBXcCYaO&BcrOh3~W%J<#r(cm3tM|yW{zX47$~ZLI+~Ga3a}?FMu@V-IMsX zS7`F8Ca={h^><+k;%ZxQ4_@nXaVC~XlOPosKoyK6_ST6S?7aZ$z)I{$6p4T_iEUV0Ppn^K2*TpC4lJwtP7?)1=JGa+y!M{Bj2VS(eQ6Z`%rK=BO{Xv8p>H0$}kT_0VX zu{e(ACU|qXcLy)Aq$8WZfRKsfmJ) Qn+>Vr&H6DX@D zL4~SdCeWa55iz~c7poEbqk=-|2ocFc@TY_j(iRjId{77?qNq>ytv$ON`_w+@F8iJD z-h1xe@0{~F=hT(+>ZwprvTd6T%ZM20+tL)l0x^H~{VM;t!W@2K;-ow_xLqsaFtdY#Yj5RtoA_23k&qp&8)~?Kf zQHgruKsc}sidqvW3x;50TdD4CYVJs^i?_C~t1v!@#x^u`_k5U4baigp+?(oaEm}Mc z{Z0xR+~9M@)Q|)%VusZt&an%KmX=2RSxedD0CpYW@jW3LCMGo@No5QqNpZykSxyVA zG0X7?_fm8t)$nNX_yQ)W$Z$W5S132LNm`j_GO}onC8>!AYL?%}be$1)wD4YUHl*Nt ziYO|+4(pxU#nrB|orNRwtrQZaV!6G;SjIl)|3ohd=7^can}m4~I7)EBqVf^{mn6sWRQ$dkv z2i+7#tPaAWFE)TU6Z;tIoYmN43F^gfO$OqVrc;4(ya3$fcfP~hxrM@SD*RS{CBAJQ z0-t!+ycdTw)Uv0-A0!%OIJ_y)t1!1f#Ql~(pjKRpPop22TepGNC`Nh!$)Kkn}4O0p##^1za<(;t-k)@;kiHGT-d^7 z!o|++HeWR>TN^gP8uCNe8^w>^S0N&H^wc1J(UXCQ^PuMu^+x#oS*+2Gy|{%$kBFsB z%YjHx!V#dwbyMPVvzUNG;dhYe5$D&<_Y`Op-}DbEK5VEgK1x@MH~pWY>VfqB|7o&w RH+|IgrGIeFKL~*v{{WOG7E=HK diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index b6f8f074bf213c9a7bcd33fbf811e213ff4bab37..b8ddaa495e7244796a97b71683393666a88b8a9e 100755 GIT binary patch literal 235723 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 delta 78945 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 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index bdab4054d9fe3a54897713c81028396ec6a24f0e..d2d1de127e8b6dfb607d068aae895a3bae3820cb 100755 GIT binary patch delta 47 zcmX?nm~qBo#tETHoQ(C1jtvb57`WNRnAmbtbMuQTnHV_Z6AKD*D&tEk7#KD#QcMH@ DYvK;? delta 90 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 SPnAA3P+i{Ues%dNz5ajG6DHgM delta 44866 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 diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 2dbc1b755101813e260f541ca6703e010e54997c..1c1f04b74b10d82e2d903601463b7b44c8b58e07 100755 GIT binary patch delta 11206 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 MVV1+{#2N2D0XMf`L;wH) delta 12456 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 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 44a87a74972b6bb06592b0a648b5cc5c73ae41bb..da08e72616fb22dc6651650661a93a6b62b7cf98 100755 GIT binary patch delta 58574 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

p z=DcJtK>!>p5?z%YClRn(0dp~~>tm4K-4o)u#g zz&ms@lLZO#BP*n9vnZ_b(cl~52q+Z-;Sl&CUG=wAe}bB#w?!bjZLN|wY!)*FBA3XH zS9k??70Nd@i_(;*(FggH8Zmh>yA-4uF?KQ^sxWrJ8WdAPGb*L~y|(m4G)f*BPM8p? zXG;}coK6Z%Y#VHn`I5P?zPd)=EF}{^NX>K*W04_X?jior2Yw zrZGYcS?kp>n^5rvdG}UPDiAo1D1K%Yid18y$a3aqbn-u`z0P?pwa&JdmM#Xb7IJm5 zC@xAtZ!9YeDH_08vDtvsvPEGjBWDyOHK;BX0@3b}-wzBk1b2lnxlBIUCkl$6f*=8_ zgt-`x+fms!D-WC{+QcoZnanNbg;jKiJ)lDbG=c9Y$(2+cmVpUF>zbnW*NEr6#S1dt zN410=h6sd8-G2TobQQbZB)65y!fhgV3Avw3mU_u5MJ_#PMI5(X&@E#!V=*lDEE={BtI*|OBNYEkd$OMBU2G^@;$mNN4(I;_NHWnjaU@UtG^RQKp%bTi7XStFlv ziP{$$dmf$IevBt>nZ{(BZ zR|Z5bFK?6A4TxE_-jSI^q$slS(H-J>Ym1Djk_8g}1a(`$5Fl}EV-yRT!*~>6@ zvCgZ7cz-{W8E^1exxdIU;b9cm6bpW*y#F|#BCmguw+O^Ll;O#uSip_9-0?bhmXP-q z2p%DqBJ*)ny-?4tJR;@`1PmhV&y*`YB0c9JAP{R>CwOlcd$bk3_=rE=MQ2qYEgOT3 zoE%2IekRdFWt?ZB6&5RFbYj%BH;@)x3#5d?QC7Y}h7FNl6+}lNkLrH1Q$1~egkgYy znZ1@cn=%jlda=CC5QWY=0QO0Yf-EZ*-5%UPCWo1%*p!#CS*SYZRdU)NAWj%NfLSt` zb+PMv=H%El6yNWiSe13%qTOx1Ck%q`zIv0f6-2Zg&0>sry7B|6Pqd z$0tq^aK0fweuGz!jwP&OWOOr%Ov| zMWCk5C87tn8;ow`DkPeGc?jL2^?4h&NzCn)#@S-###_K(Ed|=8gk17Crc{zaUj*v{ zS)JL;Wi;{7h|(A+PMgQKlTlhw7dm%uu^fF~5KaErna{Y;JjpMo>K#wm+QC1J!_tFUW1l@j+ z3XBCm#gugi!mQDhm07SEF(1XohNm$HVQm6yhU(HB2KRve1?JHcgb9XyG(>Pm-4UM& z&ULV7gv0{=i)J|#f;4*b8hKGj)bZmA{K@i(kQm761eIGuhT(W&pBc1ln2SW&4xhP+3nNtlMnzQ8G zLTk=p*e?Jz(}=mepmT=9W^(pRJT&?x%wmfIVZ^!1rXld|B!fdOmKmj9hA}2=4}>Zb zr@ZKOvJHK)nv=sPY|52^3P`PrGP<8`N}t=~umG?X!0Mnc6-FyW=q14Gmv^xz%N3oP zTU|5PqeLizEeb{#B}|(WUBdx$HBEivyEPO~k;IcW_N>~HshRr#5Nmd99>#Lb_{c(w z{ziqJ*9aR7&hW_(-{J#n%B|^4rfAj-JexXSls23hc@0VyV@O)1u-(Rvxb9CZC=5Sz vOl`%>i{hd}w#P*Q7dzy-IJOba*g-Gb`HefqejXQhr1I6DGfQ2!NA&*}A9(H2 delta 59930 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 LW!Bnosp$DHz3cT% diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 29b9edcabd3d6e9c4b6bc638978fa76a5e300b73..cdb840e4c2b1f96605025ed110298331eb26f23d 100755 GIT binary patch delta 3879 zcmbVP3s6gsb5Z<=pF4L6CHUHO!7s^!EcCd7;yoj0M>6{V#Q zPafkNJ8pc&jG2W~ig@AV+=-dllcqmBd(I0;3cWd>7}J=F~8d0M|e8lY$|LQ&FqZ`8473X zc^}Y#S50NmjAq3KF(~w-1J)w^CGjg|M$$`=(WiM7%}@=N%A#R2nre{f6&`hjLz?CP zIw7X2wR(6SmWRMw>a(;@kq>Ihcq+T2G< z6usK~S!^$*ZRTRVQ<)eR=z0+Xx3)om>D3Xf4<+#u28(zjRulTXk>*$KItOhN>mICh zhm~7?0=Zonq?|cvRJVfF|7cuM*wuFNrf)H@#IgMjVCF{|%`7JD;whNZ7m43weSHU| z%FN99y`An5m-obrt>vR5%&x!b3%eK=Tk@|2aINZMt z)L;Aa?$E0eQwmbStts${5rvx|-CWpiNoRTf;dG#$f^>s50-_WK$VxBs5mt=}e=vyfXs(LZsg)&BvNrljfK$t43C(A@pmep# z^32A}k;i;+g>eH;6{o zJll%%A{h)h1+y|M2{Pn>Zoj>HtSI8X=vX-G_7f+-b+M!*F&L+D>QSOy8dm_E8n*#B zG;Rm5Yuo`~)3_5r)wl~l(RdU9lkQ~)465fFERE%QtaF?l;{v%Tfco$_xC%LBipgWn z4D65_K+R{-0E%_1q=}cOxq~qDKNl;FQA3x)f7>aIfdjbrth-9CslrX7VXJHr2c&6w z8`ZFh#IVT%CXqYN4gd3)(5H_~z~ytta?+y#67ZQ{`8gH!)1S+_&k`2NrRDdlW4==Q z2&GNp+KgEkvu0Myg55rIcH*CL18^jo7X)~}od2yASAvshqNwPE(}k-}hGafIYXXgr zCMDPY-uhw7DxE>N`e<5$9xH;pPg#Nkju#_$BgNM_F)55D4~~jel~S{#a{qm6*3SRSP#x!8)|}GM zA=aGIn!d1Fe6qmJy4u981?yz));v~!zpI$~!nu_F_OMu1HBW-mRZn0g(_QVRw7^VT zR3yPui(8%P4QMU;Frpc5q-j?Ki8C}k2}I2prjH0STg==V7L^z3llzzv#cy`^p_^jQ zIv6YFSzHCJg9R||V*wuOj$Ri!?agknYE6#aYJzQ|bxn0-%GOqB;V$ZuQcKpRu$$dx z!`dzM8#mNfE4O87?TVqvxu*@iDK*X9I&|Fvb58vVO8w%@`Yeo}uCJhe)Bp4*j@UaB zV`ly+i_sSGL~tX-t_EL#*y9b&>dGox=hD;V&&ud-T81L9|Iy|%cMvSYPJlf zqrA!Kazz|WGcruvP^aPN>IJ-TBxbBB*v6u*c~Xh3hlITM#(9v#08CxoYC zcO>JqL6VgO@{sC(r!#_@YPt#?KCsIWJdk&rhLZM>7j=)IfhX>oY3f5M>GdcaG0Tc;J3 zf}0yQb+pMtpm5pUdrkYkDoWoKOPZ7E1@Xh?WY%*+7zdJQyLbnreR8yKMF>1jG&GN- zpNo_O$?UT(vFkt*yV@m+_b0J|F7ZNh5o3%jrmKjk99sfn<6@d=0Bdj*F$RbMUydN*g7OZk40` z#xW>7AQjlh$1K<0wSfJ+WLTcFH;+ked$*)mW>{-TwCR`_0Ar(M#9M3pyAzSHCTskx zQ=CGKe~^g%ij_CZu}kJJU)@HlFZDo64)RsSLa!=T%=a0YY->Rmzc$=?tfE(Q6UB73FR1{@_Kd#+8^ z4KVo&gXOWHoMr^LY7s+^0$b=&V3R~26l~U8F2S0qc-W-MnJjo4Od3q5L|b@w6w=ha zgy50U{ht`LZRYMHlPS9uXI?nEfl{A2qoYuv&1S>#^HIXplR)1S-k!;#`HW92>q!vw z%;uQZ;W{ZNggoUUBvw4rljYFkbdp5K-o_GIDAt|PcAs_SH}Y9w2H2=ql8H~6i!6y}Kgq_I6Q%SU)4AR==X$C5`F{Wg C^3^>6 delta 5023 zcmb_g3vgA%8Qy=-x#vF4z2rus*>wr~btj>^&ytS=P z%MAuiuvHV6msV;3Ed&DsCx}j|jZ+;nHr8PVs&+D!PE&`rFvS^1(`rk#C|9kWFpS&jrye4Z{w}KdBv^LmouFE2NrlCwWz)*@3ufL}JAc8P+4JTux~XpQcME(wdijzYh~nd!u$G5e4zrjT8xt(T z71Vd22T;1jqVk7So1GYXEG|5;Fw5nhFxBW)qIGW%6M3S`{tLBHaKPCEBuf+gUU6D( z(8f`>d`6p<*^Q-Obx1QU-Qv024<>#xL2f{v*RFOe^5W?OGD}|*Ou|WgAUv)Am`=;9 zUF9@jFLI?5x<+G`JAT{n;W-Y^EgTC+27Qqpvq%JE6FDo@umTWc&+ynHXGPsckBOHa z>`^$LSkN6(fE81sFjpn07cCDbsFOq5a>T=xpcZf?DJ~|(gzi95t(5yQ(X~;D&Y>RR zIXIZ2sbr}j9Z;@~Fo>gTgsQ{0YN*C&O$9+Bk~ zbNojhd?GV$Q7+fRnqFlQ0t77&yObPBO|!5e2%Z5ESw=o%VRt!mTkcVsizDQTOhG{% z5hvh+AE(E}FZT4XLdGf>bNpOLj3+Wy>Wtx1KN~r2P7b6|v9#K+VopDc2^ct-MX8k6 zV&=77%9Q2+az}WHgYWsv;aWOih zzLp_eu`tLn%gALE)&e9bM1qAhKfiKUs-EjNO-r*}^AL`|rJSCqxG}D%k>#?qIvC66OZm!#<#am$so9jzNZ;@?%awncoSf zc!mt20~3PesyuPchUm=4XbB;0T(qx8m#Q#n zRoBLcuTT~f+YxGqZ@|nekz2bV58V3V3T_csi3mEB?@b+#wDsSqIr2c{5u~wcrJad1 zcIy?&yXLU>FE~4LkLMz89(B$U+&qd~{lXRewO#TL3s);+s9Ln{GAJyS6*qm%-`y#v)ZL`I zhwE+!G5dqMG@=sw)5X(OSN6T#o>6;MEu8|-NVB}+CHeQ;#!|0s+|GTG#M@*$w|t`G zU{B(yu$S{HQ7ClG?d37F+j&eRUMgWP(rL#*S-Gs(a2_Om^1fwDhrkod>dfSe#;dT3 z8?$-GLA$Q;A^KXyeH>HbpS-+rq#(YL;@(`2owjigUO**w_KHTL3b|=zA=;N$)=`D+ zU-g0eA_*M+Q6Z;ZS#&~yC%`Iv;7NU?GzN#Haau{^byz9{ar$C$cp#2J`M>p( zheQyG4@+Z9eDS2DG(19)vwXSyw3CHBH33rv;T$#9tENmbXs0!y+WtBN|6rrKZTO)T zye1hwe4-cnIk$5}{IGm@pq|GsrP=Z%D~n#l%opG-A`cf)JNngN&_2*@S+>Q*OGB&KF>y@pcr<<9Hat&r8H+H6Wf=M67$@sZCR#2G zcyu#LmmX9eDjlM{vEb-^ikutFP<1qQKJnO28j!ot_R5)$cccCHuVnC@K&Sk$ZM1!+ zeJ0U)SuC^YUvfU$Kg&k6Z_Ax%dz|)w{0-n8YTjtykL`ZBQeI=UD$uJyKG-D}$V`5= zORnFNNq?0)HfPZvO$VICZEA6hd}Z?(XUBOdwr26O`yA4L+vjWqJz2HY zMGh1Cy~3bl@~y2|MyEm;?cCy*`<&w+lc8-{ynUan+?L57cIJ!?3d`jj>m$2;TMI4y z2>+B3s!<`FCmt%iN}Rz=K4b8VbBQ&qgh*8qYDG{vvb?jT%2mcu%c6#cHu$`?0T@yZ z{z0T!~HRq}INzm?lYOBu87wiokQ;6q`wqM-2o~YKIx_g>NuiC4A^}0{4J`|MO z59P?s=1bjdF;?edPj=Gqh%A&HXg1mzV)ggo68t$(iuxf zKQ!KeamI-8BNzwNSdu*@a2mT5_IP|bQrN#6cP$_fKA^Gd@U2_>gTi|n?<`omjj`f11^soB#j- diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8b3edc3de407b0a3aba8e31a4b0bf13ff573b80d..59eb75aa64d22381a3d4916e6eab299ba06c708a 100755 GIT binary patch delta 4503 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 Ph`=u#jpPhwLx(# delta 5561 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 BFPH!T diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index caadc022a595e1243cbc052a730b98ae984d66dd..846197f3700f46dd7e86d59bc199041b6b68ff9a 100755 GIT binary patch delta 4169 zcmb6c3s6+o_1^nFyUW|fC!kpul(*{|@q>UG{vuaJ4NCZ!_(2*&j2ae0EU49*5e&wl zV-<5UYWy3ih+-vunAG|yooJFt$P`=K>O?cznMT^QwUbPK9iu(>?Jg42Nd@Me-#zEt zbI!f@+`FHC>Nt7B;UwiX&rw1MeUV0O(^mVQQfl#UEnx$ww5V#OtGw#@=gJqXT()4* z%JQW|lYUgTZQtlA;{3xN9-fdm{m}_I6Q@jgBsD2{?D(lO9xE)$ zf3#p`$*jkpm_0FK`vq1ukv9aE>; zd@hXIp!`=aqUTaACdjq@938c;?;w*TeZF}v@(iWz<{G_4N(?EqHlV`omr;S~^TSqG zg|HKWCA<}>z4F9BbMSy_8*PWixLGkF+XEX>tr3AxGfM`akkC2vUXj?XX1L~`k6gyE z^ETwnyQ5!Yu26(+IGbJupC#S>j-=FlEajRSz3T`%RWOmb09Fu7)1N?TdOVDoUNz>U z<_?lXNEQ)^>H+=-R)d z(r435c!Q-vYyKuW4$@|{;K+>`NpL)KE{>>Kso$|Jg;nVZsBtje4<#A(Xz{0v=AcFG zXLcLz=32Vef{1Y=c9BXS^Anbf|FsqrROsKJ2~74Wa;`xQh0{u(n#yFPQA~Uirvb5Z znIR<-;|7+GZF)g$s~3}-LPlUpxP*n zQ?KN7Xk0>M*SLbnrg0UKs&N}4MdNlvvc?^VB#lQPVj{fcfI;;PgN#%0vQ>M+q^n1QPjgGf$ePDGN`&_=-flVfUeqW|eE&m{-@(f8-cb7273a;-Kg ze99Om(QtW`Z~$UzdXLp`;fSG#%}T=UH3s8PWl%6>pd0(%cif4Hx=6rpW@d2;ZBthF z^$?5Q8pzA&qnUXzuaMGL!8;_Hfu43?4k}q-zoYXh0h2MnlEik9wQg@0-sqBJDMw z9q0R^v=ln$#jw^U_{+Ry*c4988w5*B=l9W%`9*0FWtU!uX=TL%?J9c`SI+!TSq!C_ zX1^!11)8~FGo{&Z<*7voCzh|H+2(J{9}%Ljp5Bbi`h{OIWY#QhK{#y5`;m2caq2qJ zj2Oeg8{yj}ar6Lqo^8e9cb~0Ciy0Lc?atuMzmpjeya5ub2RnoJy3c`cga#Qvr}Zku^|lVYxG~0V#&Y=u!ey*aHwz%#$pSo7BRkgUdK>5)Q^J** zwy~#3hd0jBLPfF*EqRl{uD6*FZ~CSDe-FS{Te5m;WH&TXqj;mA;nq1!-}(q018cYD zAUwPE>E5nf_njq|I7>N=;w=5NadON~JY{?oX7CXK(7mzr9T<%8EyzH4#Dd%5$4I{<M1J&z-)CEcZ2;ZXa2T;5(mY##>5Vl)%v!K=8L2FcGn_6MWUXS|q zF*J=5rtI&>;NyKB^^%YbwEVs&NT=oZETrs@r6-|af2`Un#MaF8pe5ceAiH}M&h7W8 zpC1hysW*E_jH~~FSoYyjktqAkQEMji-f$DFI}l5^!Cq9_a1`$B^{}QE3w_f9Rr@^b zUlynS&8nOg?$s7^>cI^(=jxj{p8(>a$AS+%mdh`pB#{t2N0)WYGm$h|zWn4x#UenmhM=+8m;Ruq* z+k1F}S|fHGk@0zl$R5z`xN{}kOX3HWhCN06z)G(4Dahl75S>dAy97D$bBm?2T0xEu zaM={mC;mE0R5P#YaH&40Y8HA9< z4niW~_NgRAkJ5<;a!(hgj7KUT$LeH4+=#>cI^+lS;x{4h5gyPquUq83wt+^c(PW~73gFB=E delta 5424 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 81f3ff61cfc51352378107c4921e0dba424c4898..08e79ffa73cf272cb7bf7ece9fea353e989620bf 100755 GIT binary patch delta 20267 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 delta 21271 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 Zze|eeoD6&2_V@1k;rDj^Fnq`N{y*|rAVmNG diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index a762266713e4b46b7e7429362550b3957817bfe5..72b1d7efe6366739cdb31ffe8f4aaa09b9eebab7 100755 GIT binary patch delta 23887 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 D1)}gs From 3e60100ea860d6cc114155cad1ecba76df8f3875 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:03:10 +0100 Subject: [PATCH 1240/1995] [fix]: Fixed bugs in escrowing Nam when minting wNam and checking balance deltas. Covered with a test --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 208 +++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index fc968fbfcd..6b63bd6ef2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -90,33 +90,47 @@ where expected_debit: Amount, expected_credit: Amount, ) -> bool { - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(payer_account) - { - if amount != expected_debit { - return false; - } - } else { - tracing::debug!("The account {} was not debited.", payer_account); + let debited = self.account_balance_delta(payer_account); + let credited = self.account_balance_delta(escrow_account); + if debited.is_none() && credited.is_none() { return false; } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(escrow_account) - { - if amount != expected_credit { - return false; + + match (debited, credited) { + ( + Some(SignedAmount::Negative(debit)), + Some(SignedAmount::Positive(credit)), + ) => debit == expected_debit && credit == expected_credit, + (Some(SignedAmount::Positive(_)), _) => { + tracing::debug!( + "The account {} was not debited.", + payer_account + ); + false + } + (_, Some(SignedAmount::Negative(_))) => { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + false + } + (None, _) => { + tracing::debug!( + "Could not calculate the balance delta for {}", + payer_account + ); + false + } + (_, None) => { + tracing::debug!( + "Could not calculate the balance delta for the Ethereum \ + bridge pool" + ); + false } - } else { - tracing::debug!( - "The Ethereum bridge pool's escrow was not credited from \ - account {}.", - payer_account - ); - return false; } - true } } @@ -207,15 +221,36 @@ where // TODO: We should look this address up from storage if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if !self.check_nam_escrowed( + return if transfer.gas_fee.payer == transfer.transfer.sender { + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer \ + {:?}.", + transfer + ); + Ok(true) + } + } else if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, transfer.gas_fee.amount, ) || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, transfer.transfer.amount, ) { Ok(false) @@ -227,7 +262,7 @@ where Ok(true) }; } else { - // check that the correct amounnt of gas fees were escrowed + // check that the correct amount of gas fees were escrowed if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, @@ -331,6 +366,12 @@ mod test_bridge_pool_vp { .expect("The token address decoding shouldn't fail") } + /// A sampled established address for tests + pub fn established_address_1() -> Address { + Address::decode("atest1v4ehgw36g56ngwpk8ppnzsf4xqeyvsf3xq6nxde5gseyys3nxgenvvfex5cnyd2rx9zrzwfctgx7sp") + .expect("The token address decoding shouldn't fail") + } + fn bertha_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -1139,4 +1180,117 @@ mod test_bridge_pool_vp { .expect("Test failed"); assert!(!res); } + + /// Test that we check escrowing Nam correctly when minting wNam + /// and the gas payer account is different from the transferring + /// account. + #[test] + fn test_mint_wnam_separate_gas_payer() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + // initialize the gas payers account + let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: established_address_1(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } } From fb6f0f557558489a26ab695c5e81a2bf0aa968de Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:32:23 +0100 Subject: [PATCH 1241/1995] [feat]: Changed the bridge pool vp to read the native erc20 ethereum address from storage rather than using a hardcoded address. --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 105 ++++++++++++------ 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6b63bd6ef2..644e33993a 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -15,6 +15,7 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; +use super::storage; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; @@ -24,8 +25,9 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{wnam, xan, Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::ethereum_events::EthAddress; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -132,6 +134,26 @@ where } } } + + /// Get the Ethereum address for wNam from storage, if possible + fn native_erc20_address(&self) -> Result { + match self.ctx.storage.read(&storage::native_erc20_key()) { + Ok((Some(bytes), _)) => { + Ok(EthAddress::try_from_slice(bytes.as_slice()).expect( + "Deserializing the Native ERC20 address from storage \ + shouldn't fail.", + )) + } + Ok(_) => Err(Error(eyre!( + "The Ethereum bridge storage is not initialized" + ))), + Err(e) => Err(Error(eyre!( + "Failed to read storage when fetching the native ERC20 \ + address with: {}", + e.to_string() + ))), + } + } } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -218,8 +240,8 @@ where // if we are going to mint wNam on Ethereum, the appropriate // amount of Nam must be escrowed in the Ethereum bridge VP's storage. - // TODO: We should look this address up from storage - if transfer.transfer.asset == wnam() { + let wnam_address = self.native_erc20_address()?; + if transfer.transfer.asset == wnam_address { // check that correct amount of Nam was put into escrow. return if transfer.gas_fee.payer == transfer.transfer.sender { if !self.check_nam_escrowed( @@ -319,6 +341,9 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use super::*; + use crate::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; @@ -326,6 +351,7 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::Storage; use crate::proto::Tx; + use crate::types::address::wnam; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use crate::types::ethereum_events::EthAddress; @@ -472,6 +498,32 @@ mod test_bridge_pool_vp { [account_key, token_key].into() } + /// Initialize some dummy storage for testing + fn setup_storage() -> Storage { + let mut storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + // a dummy config for testing + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + config.init_storage(&mut storage); + storage + } + /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, @@ -507,11 +559,7 @@ mod test_bridge_pool_vp { { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -855,11 +903,7 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -927,11 +971,7 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1004,11 +1044,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1101,11 +1137,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1198,19 +1230,18 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); // initialize the gas payers account - let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + let gas_payer_balance_key = + balance_key(&xan(), &established_address_1()); write_log .write( &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + Amount::from(BERTHA_WEALTH) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1285,8 +1316,8 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) From 337ed2d854ec04f1371ac418e1d7e1e947918022 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 31 Oct 2022 11:23:03 +0000 Subject: [PATCH 1242/1995] write_validator_eth_*_key methods should return a Result --- proof_of_stake/src/lib.rs | 29 +++++++++++++-------------- tx_prelude/src/proof_of_stake.rs | 34 +++++++++++++++----------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a8edfa887e..d23450b74a 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -212,6 +212,18 @@ pub trait PosActions: PosReadOnly { key: &Self::Address, value: ValidatorConsensusKeys, ) -> Result<(), Self::Error>; + /// Write PoS validator's Eth bridge governance key + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: ValidatorEthKey, + ) -> Result<(), Self::Error>; + /// Write PoS validator's Eth validator set update signing key + fn write_validator_eth_hot_key( + &mut self, + address: &Self::Address, + value: ValidatorEthKey, + ) -> Result<(), Self::Error>; /// Write PoS validator's state. fn write_validator_state( &mut self, @@ -254,19 +266,6 @@ pub trait PosActions: PosReadOnly { &mut self, value: TotalVotingPowers, ) -> Result<(), Self::Error>; - /// Write PoS validator's Eth bridge governance key - fn write_validator_eth_cold_key( - &mut self, - address: &Self::Address, - value: ValidatorEthKey, - ); - - /// Write PoS validator's Eth validator set update signing key - fn write_validator_eth_hot_key( - &mut self, - address: &Self::Address, - value: ValidatorEthKey, - ); /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond( @@ -337,8 +336,8 @@ pub trait PosActions: PosReadOnly { staking_reward_address.clone(), )?; self.write_validator_consensus_key(address, consensus_key)?; - self.write_validator_eth_cold_key(address, eth_cold_key); - self.write_validator_eth_hot_key(address, eth_hot_key); + self.write_validator_eth_cold_key(address, eth_cold_key)?; + self.write_validator_eth_hot_key(address, eth_hot_key)?; self.write_validator_state(address, state)?; self.write_validator_set(validator_set)?; self.write_validator_address_raw_hash(address, &consensus_key_clone)?; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 6b1283efac..0a2a46d3b4 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -163,6 +163,22 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_consensus_key_key(key), &value) } + fn write_validator_eth_cold_key( + &mut self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) -> Result<(), Self::Error> { + self.write(&validator_eth_cold_key_key(address), encode(&value)) + } + + fn write_validator_eth_hot_key( + &mut self, + address: &Self::Address, + value: types::ValidatorEthKey, + ) -> Result<(), Self::Error> { + self.write(&validator_eth_hot_key_key(address), encode(&value)) + } + fn write_validator_state( &mut self, key: &Self::Address, @@ -217,24 +233,6 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&total_voting_power_key(), &value) } - fn write_validator_eth_cold_key( - &mut self, - address: &Self::Address, - value: types::ValidatorEthKey, - ) { - self.write(&validator_eth_cold_key_key(address), encode(&value)) - .unwrap(); - } - - fn write_validator_eth_hot_key( - &mut self, - address: &Self::Address, - value: types::ValidatorEthKey, - ) { - self.write(&validator_eth_hot_key_key(address), encode(&value)) - .unwrap(); - } - fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { self.delete(&bond_key(key)) } From bc09908b4dd76dce1a6117a95a56101524028474 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 13:54:14 +0100 Subject: [PATCH 1243/1995] [feat]: Ethereum bridge vp allows escrowing nam --- shared/src/ledger/eth_bridge/vp/mod.rs | 65 +++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0e62f7270f..8696d4325a 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,9 +12,9 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; /// Validity predicate for the Ethereum bridge @@ -27,6 +28,59 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } +impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// If the bridge's escrow key was changed, we check + /// that the balance increased and that the bridge pool + /// VP has been triggered. The bridge pool VP will carry + /// out the rest of the checks. + fn check_escrow( + &self, + verifiers: &BTreeSet

, + ) -> bool { + let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_pre: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the Ethereum bridge VP's balance from \ + storage" + ); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; + }; + + // The amount escrowed should increase. + if escrow_pre < escrow_post { + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + } else { + tracing::info!( + "A normal tx cannot decrease the amount of Nam escrowed in \ + the Ethereum bridge" + ); + false + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -50,6 +104,7 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount + /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -67,6 +122,12 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); + + // first check if Nam is being escrowed + if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { + return Ok(self.check_escrow(verifiers)); + } + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), From adaeb0bf812d3993a5684341c6cf9b569464f7be Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:25:27 +0100 Subject: [PATCH 1244/1995] [feat]: Initialized VP substorage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 27 +++++++++++++-- shared/src/ledger/eth_bridge/parameters.rs | 12 +++++-- shared/src/ledger/eth_bridge/vp/mod.rs | 33 +++++++++++++++---- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 644e33993a..482d297be6 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -12,7 +12,7 @@ //! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; use super::storage; @@ -23,7 +23,7 @@ use crate::ledger::eth_bridge::storage::wrapped_erc20s; use crate::ledger::eth_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -43,6 +43,29 @@ enum SignedAmount { Negative(Amount), } +/// Initialize the storage owned by the Bridge Pool VP. +/// +/// This means that the amount of escrowed gas fees is +/// initialized to 0. +pub fn init_storage(storage: &mut Storage) +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Bridge pool VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct BridgePoolVp<'ctx, D, H, CA> where diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 2d22730a8a..9961e32e34 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -3,8 +3,9 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use crate::ledger::eth_bridge; -use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::ethereum_events::EthAddress; @@ -119,7 +120,10 @@ pub struct EthereumBridgeConfig { } impl EthereumBridgeConfig { - /// Initialize the Ethereum bridge parameters in storage + /// Initialize the Ethereum bridge parameters in storage. + /// + /// If these parameters are initialized, the storage subspaces + /// for the Ethereum bridge VPs are also initialized. pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -148,5 +152,9 @@ impl EthereumBridgeConfig { storage .write(&governance_contract_key, encode(governance)) .unwrap(); + // Initialize the storage for the Ethereum Bridge VP. + eth_bridge::vp::init_storage(storage); + // Initialize the storage for the Bridge Pool VP. + bridge_pool_vp::init_storage(storage); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 56ec1c588c..213bc11f9f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -17,6 +17,30 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; + +/// Initialize the storage owned by the Ethereum Bridge VP. +/// +/// This means that the amount of escrowed Nam is +/// initialized to 0. +pub fn init_storage(storage: &mut ledger_storage::Storage) + where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &super::ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where @@ -30,18 +54,15 @@ where impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { /// If the bridge's escrow key was changed, we check /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow( - &self, - verifiers: &BTreeSet
, - ) -> bool { + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) From 914112fbc11703d76660e17fbe8840bbc1466c28 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:28:31 +0100 Subject: [PATCH 1245/1995] [feat]: Initialized VP substorage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 27 +++++++++++++++-- shared/src/ledger/eth_bridge/parameters.rs | 12 ++++++-- shared/src/ledger/eth_bridge/vp/mod.rs | 29 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3588d76734..d43356c475 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -11,7 +11,7 @@ //! added to the pool and gas fees are submitted appropriately. use std::collections::BTreeSet; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ @@ -19,7 +19,7 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -38,6 +38,29 @@ enum SignedAmount { Negative(Amount), } +/// Initialize the storage owned by the Bridge Pool VP. +/// +/// This means that the amount of escrowed gas fees is +/// initialized to 0. +pub fn init_storage(storage: &mut Storage) +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Bridge pool VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct BridgePoolVp<'ctx, D, H, CA> where diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 2d22730a8a..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -4,7 +4,8 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::eth_bridge; +use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::ethereum_events::EthAddress; @@ -119,7 +120,10 @@ pub struct EthereumBridgeConfig { } impl EthereumBridgeConfig { - /// Initialize the Ethereum bridge parameters in storage + /// Initialize the Ethereum bridge parameters in storage. + /// + /// If these parameters are initialized, the storage subspaces + /// for the Ethereum bridge VPs are also initialized. pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -148,5 +152,9 @@ impl EthereumBridgeConfig { storage .write(&governance_contract_key, encode(governance)) .unwrap(); + // Initialize the storage for the Ethereum Bridge VP. + eth_bridge::vp::init_storage(storage); + // Initialize the storage for the Bridge Pool VP. + bridge_pool_vp::init_storage(storage); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0e62f7270f..b9bb4237cc 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,11 +12,35 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; + +/// Initialize the storage owned by the Ethereum Bridge VP. +/// +/// This means that the amount of escrowed Nam is +/// initialized to 0. +pub fn init_storage(storage: &mut ledger_storage::Storage) + where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &super::ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where From 55d1b3e44f4b8b25e155a365c67acadaa5e9929f Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:33:25 +0100 Subject: [PATCH 1246/1995] [feat]: The ethereum bridge vp should allow nam to be escrowed in its xan account --- shared/src/ledger/eth_bridge/vp/mod.rs | 62 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 51ebface24..2079cea87c 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,9 +12,9 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; /// Validity predicate for the Ethereum bridge @@ -27,6 +28,56 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } +impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// If the bridge's escrow key was changed, we check + /// that the balance increased and that the bridge pool + /// VP has been triggered. The bridge pool VP will carry + /// out the rest of the checks. + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_pre: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the Ethereum bridge VP's balance from \ + storage" + ); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; + }; + + // The amount escrowed should increase. + if escrow_pre < escrow_post { + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + } else { + tracing::info!( + "A normal tx cannot decrease the amount of Nam escrowed in \ + the Ethereum bridge" + ); + false + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -50,6 +101,7 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount + /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -67,6 +119,12 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); + + // first check if Nam is being escrowed + if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { + return Ok(self.check_escrow(verifiers)); + } + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), From 73abdc9a4bc32a6d5c86133278af515df29638df Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:10:15 +0100 Subject: [PATCH 1247/1995] [chore]: Added unit tests checking that the eth bridge vp allows escrowing nam --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 20 +- shared/src/ledger/eth_bridge/parameters.rs | 2 +- shared/src/ledger/eth_bridge/vp/mod.rs | 214 +++++++++++++++++- 3 files changed, 210 insertions(+), 26 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 482d297be6..c8a2f3fe96 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1057,15 +1057,8 @@ mod test_bridge_pool_vp { fn test_mint_wnam() { // setup let mut write_log = new_writelog(); - // initialize the eth bridge balance to 0 let eb_account_key = balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); - write_log - .write( - &eb_account_key, - Amount::default().try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); write_log.commit_tx(); let storage = setup_storage(); let tx = Tx::new(vec![], None); @@ -1143,25 +1136,18 @@ mod test_bridge_pool_vp { assert!(res); } - /// Test that we can rejecte a transfer that + /// Test that we can reject a transfer that /// mints wNam if we don't escrow the correct /// amount of Nam. #[test] fn test_reject_mint_wnam() { // setup let mut write_log = new_writelog(); - // initialize the eth bridge balance to 0 - let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); - write_log - .write( - &eb_account_key, - Amount::default().try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); write_log.commit_tx(); let storage = setup_storage(); let tx = Tx::new(vec![], None); + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); // the transfer to be added to the pool let transfer = PendingTransfer { diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 9961e32e34..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -3,8 +3,8 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use crate::ledger::eth_bridge; +use crate::ledger::eth_bridge; use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 213bc11f9f..91be9dc69c 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,15 +17,14 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; - /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) - where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -36,8 +35,8 @@ pub fn init_storage(storage: &mut ledger_storage::Storage) .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ - fail.", + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", ); } @@ -77,7 +76,7 @@ where return false; }; let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) + self.ctx.read_post(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()) .expect("Deserializing a balance from storage shouldn't fail") @@ -388,10 +387,28 @@ fn calculate_delta(balance_pre: i128, balance_post: i128) -> Result { #[cfg(test)] mod tests { + use std::default::Default; + use std::env::temp_dir; + use rand::Rng; use super::*; + use crate::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; + use crate::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; + use crate::ledger::gas::VpGasMeter; + use crate::ledger::storage::mockdb::MockDB; + use crate::ledger::storage::traits::Sha256Hasher; + use crate::ledger::storage::write_log::WriteLog; + use crate::ledger::storage::Storage; + use crate::proto::Tx; + use crate::types::address::wnam; + use crate::types::chain::ChainId; use crate::types::ethereum_events; + use crate::types::ethereum_events::EthAddress; + use crate::vm::wasm::VpCache; + use crate::vm::WasmCacheRwAccess; const ARBITRARY_OWNER_A_ADDRESS: &str = "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; @@ -407,6 +424,60 @@ mod tests { .expect("should always be able to construct this key") } + /// Initialize some dummy storage for testing + fn setup_storage() -> Storage { + let mut storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + + // setup a user with a balance + let balance_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + storage + .write( + &balance_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // a dummy config for testing + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + config.init_storage(&mut storage); + storage + } + + /// Setup a ctx for running native vps + fn setup_ctx<'a>( + tx: &'a Tx, + storage: &'a Storage, + write_log: &'a WriteLog, + ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { + Ctx::new( + storage, + write_log, + tx, + VpGasMeter::new(0u64), + VpCache::new(temp_dir(), 100usize), + ) + } + #[test] fn test_error_if_triggered_without_keys_changed() { let keys_changed = BTreeSet::new(); @@ -524,4 +595,131 @@ mod tests { assert_matches!(result, Ok(None)); } } + + /// Test that escrowing Nam is accepted. + #[test] + fn test_escrow_nam_accepted() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog), + }; + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(res.expect("Test failed")); + } + + /// Test that escrowing must increase the balance + #[test] + fn test_escrowed_nam_must_increase() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // do not credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog), + }; + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(!res.expect("Test failed")); + } + + /// Test that the VP checks that the bridge pool vp will + /// be triggered if escrowing occurs. + #[test] + fn test_escrowing_must_trigger_bridge_pool_vp() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog), + }; + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([]); + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(!res.expect("Test failed")); + } } From 648be8ea63fc0cd5cb916588f048f1b0df555cc4 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:11:19 +0100 Subject: [PATCH 1248/1995] [fix]: Formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6b63bd6ef2..27a524dba1 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1198,11 +1198,14 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); // initialize the gas payers account - let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + let gas_payer_balance_key = + balance_key(&xan(), &established_address_1()); write_log .write( &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + Amount::from(BERTHA_WEALTH) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); write_log.commit_tx(); @@ -1285,8 +1288,8 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) From eb365078fd9ece0093846bfcba81dced224f8fdc Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:12:44 +0100 Subject: [PATCH 1249/1995] [fix]: Linting --- shared/src/ledger/eth_bridge/vp/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index b9bb4237cc..1e72b8d49b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshDeserialize; +use borsh::BorshSerialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,15 +17,14 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; - /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) - where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -36,8 +35,8 @@ pub fn init_storage(storage: &mut ledger_storage::Storage) .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ - fail.", + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", ); } From 8e664805ae9dbd50437d583cae2c2963b6d707b8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 31 Oct 2022 16:42:27 +0000 Subject: [PATCH 1250/1995] Don't double serialize eth keys --- tx_prelude/src/proof_of_stake.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 0a2a46d3b4..bb89052079 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -168,7 +168,7 @@ impl namada_proof_of_stake::PosActions for Ctx { address: &Self::Address, value: types::ValidatorEthKey, ) -> Result<(), Self::Error> { - self.write(&validator_eth_cold_key_key(address), encode(&value)) + self.write(&validator_eth_cold_key_key(address), &value) } fn write_validator_eth_hot_key( @@ -176,7 +176,7 @@ impl namada_proof_of_stake::PosActions for Ctx { address: &Self::Address, value: types::ValidatorEthKey, ) -> Result<(), Self::Error> { - self.write(&validator_eth_hot_key_key(address), encode(&value)) + self.write(&validator_eth_hot_key_key(address), &value) } fn write_validator_state( From fc70c76a581a3d6d8c9fcc118799f348f56e636e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 31 Oct 2022 16:44:24 +0000 Subject: [PATCH 1251/1995] Disable ethbridge in double_signing_gets_slashed test --- tests/src/e2e/ledger_tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index e651269704..45bf8392ae 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1965,6 +1965,9 @@ fn double_signing_gets_slashed() -> Result<()> { let test = setup::network(|genesis| setup::add_validators(1, genesis), None)?; + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(1)); + // 1. Run 2 genesis validator ledger nodes let args = ["ledger"]; let mut validator_0 = From 8ae9563455e50d94eda3f45e781eb4ffe5a967d5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Oct 2022 17:45:11 +0000 Subject: [PATCH 1252/1995] Update wasm/checksums.json --- wasm/checksums.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 4001a69fa0..19b8b54b52 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,14 +1,14 @@ { - "tx_bond.wasm": "tx_bond.ad18675419b9dd88d4eaf170366754dd12fddc45a67e2cd100b1554ea693de43.wasm", + "tx_bond.wasm": "tx_bond.482d444214f61b51df344da56f24cd0ca392e699e18c49d2d27b9f108ef643bd.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", "tx_ibc.wasm": "tx_ibc.b5a3bf6ca1dea0767406d64251928816a7d95b974cff09f6e702a8f3fbd64b1f.wasm", "tx_init_account.wasm": "tx_init_account.343e04328e157514ec85cfb650cad5cad659eac27e80b1a0dec61286352a3c9d.wasm", - "tx_init_nft.wasm": "tx_init_nft.36437ec1cf161d3e21f8a4f1e939676914dfdcb7ee667c92c2807767ddfabdb3.wasm", + "tx_init_nft.wasm": "tx_init_nft.ea5ace3004d4d63b6a648bf2d16c94c95da65f85c4bc20a77f940b9cdfe346e9.wasm", "tx_init_proposal.wasm": "tx_init_proposal.c8e187e2bd7869253f9d9d7de5fa2cb5896e9ca114f124ad800131c9e82db1a7.wasm", - "tx_init_validator.wasm": "tx_init_validator.8c047862fb2e538dfe414880a9b847741b84a0b8fcb4beb67752f58b7d954b6f.wasm", + "tx_init_validator.wasm": "tx_init_validator.23b5d73ff65718b5bc9f51423fad36c176dcde9e1006fc7c37cd8e64aae9b8b3.wasm", "tx_mint_nft.wasm": "tx_mint_nft.110baf82d92f78ca740750808237ecd4793e24b662876f363f51c5d1cd030694.wasm", "tx_transfer.wasm": "tx_transfer.07335720d2ab07311219a81fd6baca5801792dc16b7c9bab490e2b756257a6dd.wasm", - "tx_unbond.wasm": "tx_unbond.895123c93e6e7c6d2303be44cc9332a7a9a867cf159ce87cf55abb155e8d83ca.wasm", + "tx_unbond.wasm": "tx_unbond.f8879ee80dadf71bd663d46abbd39b47b4c59a3603d29465cf8cff1acbdfa9d3.wasm", "tx_update_vp.wasm": "tx_update_vp.d2743de89548f3ae6decf2a32ab086e960be9b954bdd24bd6e8e731195449540.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.348c3c28fc9e7356a7106f4b037a0bc7b6b207761b000ad0e0cb786678afbee5.wasm", "tx_withdraw.wasm": "tx_withdraw.a2a0a3f9eb961cba5bb4d1677805bafdcc807637fbd203f8afaa2aa2adb6857e.wasm", From e03e3fc90688adb912610eb13bde480d2196ef04 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 10:27:09 +0100 Subject: [PATCH 1253/1995] [feat]: Added bridge pool defs to the cli --- apps/src/lib/cli.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 59178da8f1..d7dd956eba 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1365,17 +1365,20 @@ pub mod args { use libp2p::Multiaddr; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; + use namada::types::ethereum_events::EthAddress; use namada::types::governance::ProposalVote; use namada::types::intent::{DecimalWrapper, Exchange}; use namada::types::key::*; use namada::types::storage::{self, Epoch}; use namada::types::token; + use namada::types::token::Amount; use namada::types::transaction::GasLimit; use serde::Deserialize; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; use super::ArgMatches; + use crate::cli::context::FromContext; use crate::config; use crate::config::TendermintMode; use crate::facade::tendermint::Timeout; @@ -1410,8 +1413,11 @@ pub mod args { const DONT_ARCHIVE: ArgFlag = flag("dont-archive"); const DRY_RUN_TX: ArgFlag = flag("dry-run"); const EPOCH: ArgOpt = arg_opt("epoch"); + const ERC20: Arg = arg("erc20"); + const ETH_ADDRESS: Arg = arg("ethereum-address"); const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); + const FEE_PAYER: Arg = arg("fee-payer"); const FEE_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("fee-token", DefaultFn(|| "XAN".into())); const FORCE: ArgFlag = flag("force"); @@ -1565,6 +1571,73 @@ pub mod args { } } + /// A transfer to be added to the Ethereum bridge pool. + pub struct EthereumBridgePool { + /// The type of token + pub asset: EthAddress, + /// The recipient address + pub recipient: EthAddress, + /// The sender of the transfer + pub sender: FromContext
, + /// The amount to be transferred + pub amount: Amount, + /// The amount of fees (in NAM) + pub gas_amount: Amount, + /// The account of fee payer. + pub gas_payer: FromContext
, + } + + impl Args for EthereumBridgePool { + fn parse(matches: &ArgMatches) -> Self { + let asset = ERC20.parse(matches); + let recipient = ETH_ADDRESS.parse(matches); + let sender = ADDRESS.parse(matches); + let amount = AMOUNT.parse(matches); + let gas_amount = FEE_AMOUNT.parse(matches); + let gas_payer = FEE_PAYER.parse(matches); + Self { + asset, + recipient, + sender, + amount, + gas_amount, + gas_payer, + } + } + + fn def(app: App) -> App { + app.arg( + ERC20 + .def() + .about("The Ethereum address of the ERC20 token."), + ) + .arg( + ETH_ADDRESS + .def() + .about("The Ethereum address receiving the tokens."), + ) + .arg( + ADDRESS + .def() + .about("The Namada address sending the tokens."), + ) + .arg( + AMOUNT.def().about( + "The amount of tokens being sent across the bridge.", + ), + ) + .arg(FEE_AMOUNT.def().about( + "The amount of NAM you wish to pay to have this transfer \ + relayed to Ethereum.", + )) + .arg( + FEE_PAYER + .def() + .about("The Namada address of the account paying the fee."), + ) + } + } + /// Custom transaction arguments #[derive(Clone, Debug)] pub struct TxCustom { From d8247f35863846ebfb1780307820880d794f2208 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 10:47:50 +0100 Subject: [PATCH 1254/1995] [fix]: Moved a test to a move appropriate location --- Cargo.lock | 1 + apps/src/lib/config/ethereum_bridge/mod.rs | 36 --------------------- shared/Cargo.toml | 1 + shared/src/ledger/eth_bridge/parameters.rs | 37 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..22408bcb1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,6 +4341,7 @@ dependencies = [ "test-log", "thiserror", "tiny-keccak", + "toml", "tonic-build", "tracing 0.1.35", "tracing-subscriber 0.3.11", diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index d4606b0652..370e1150a2 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,37 +1 @@ pub mod ledger; - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::eth_bridge::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, - }; - use namada::types::ethereum_events::EthAddress; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c16713d30c..c0318e10e5 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -135,6 +135,7 @@ pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} +toml = "0.5.8" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} [build-dependencies] diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index f16292223b..46ff52c0a3 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,3 +158,40 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use crate::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use crate::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} From 6051d0470a2c91f467f7c7df6a1f975a7cf0b834 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 1 Nov 2022 11:15:38 +0100 Subject: [PATCH 1255/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2079cea87c..f166dd5d87 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -53,7 +53,7 @@ where return false; }; let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) + self.ctx.read_post(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()) .expect("Deserializing a balance from storage shouldn't fail") From 876cbd12bc060bf75e4ccaa40dec5c6e87ccb3ec Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 11:56:09 +0100 Subject: [PATCH 1256/1995] [fix]: Added some errors --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 39 ++++++----------- shared/src/ledger/eth_bridge/vp/mod.rs | 43 +++++++++++-------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 27a524dba1..f20a31d6b7 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -89,24 +89,21 @@ where escrow_account: &Address, expected_debit: Amount, expected_credit: Amount, - ) -> bool { + ) -> Result { let debited = self.account_balance_delta(payer_account); let credited = self.account_balance_delta(escrow_account); - if debited.is_none() && credited.is_none() { - return false; - } match (debited, credited) { ( Some(SignedAmount::Negative(debit)), Some(SignedAmount::Positive(credit)), - ) => debit == expected_debit && credit == expected_credit, + ) => Ok(debit == expected_debit && credit == expected_credit), (Some(SignedAmount::Positive(_)), _) => { tracing::debug!( "The account {} was not debited.", payer_account ); - false + Ok(false) } (_, Some(SignedAmount::Negative(_))) => { tracing::debug!( @@ -114,22 +111,12 @@ where account {}.", payer_account ); - false - } - (None, _) => { - tracing::debug!( - "Could not calculate the balance delta for {}", - payer_account - ); - false - } - (_, None) => { - tracing::debug!( - "Could not calculate the balance delta for the Ethereum \ - bridge pool" - ); - false + Ok(false) } + (None, _) | (_, None) => Err(Error(eyre!( + "Could not calculate the balance delta for {}", + payer_account + ))), } } } @@ -227,12 +214,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount + transfer.transfer.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.gas_fee.amount + transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -247,12 +234,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -268,7 +255,7 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) { + )? { return Ok(false); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2079cea87c..0b27b03fc0 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -38,42 +38,47 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + fn check_escrow( + &self, + verifiers: &BTreeSet
, + ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| Error(eyre!("Couldn't deserialize a balance from storage")), + )? } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return false; - }; - let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's balance \ - after applying tx" - ); - return false; + return Ok(false); }; + let escrow_post: Amount = + if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + BorshDeserialize::try_from_slice(bytes.as_slice()).expect( + "Deserializing the balance of the Ethereum bridge VP from \ + storage shouldn't fail", + ) + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's \ + balance after applying tx" + ); + return Ok(false); + }; // The amount escrowed should increase. if escrow_pre < escrow_post { - verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - false + Ok(false) } } } @@ -122,7 +127,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return Ok(self.check_escrow(verifiers)); + return self.check_escrow(verifiers); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { From 3ec4a16a61b0526b4ee1469dc263b62fd54f7788 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 14:46:21 +0100 Subject: [PATCH 1257/1995] [fix]: Removed settled todo comment --- shared/src/types/address.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 843fbd21e4..2e4edce772 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -534,7 +534,6 @@ pub fn kartoffel() -> Address { /// Temporary helper for testing pub const fn wnam() -> EthAddress { - // TODO: Replace this with the real wNam ERC20 address once it exists // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" EthAddress([ 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, From ee0c9a10fa49e918f1c9dcc52d488d151ed80824 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 11:18:39 +0000 Subject: [PATCH 1258/1995] Move namada_apps::node::ledger::events to the shared crate --- Cargo.lock | 2 +- apps/Cargo.toml | 1 - apps/src/lib/client/rpc.rs | 14 ++++----- apps/src/lib/client/tendermint_rpc_types.rs | 2 +- apps/src/lib/node/ledger/mod.rs | 1 - apps/src/lib/node/ledger/shell/governance.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 4 +-- .../node/ledger/shims/abcipp_shim_types.rs | 7 +++-- shared/Cargo.toml | 1 + .../lib/node => shared/src}/ledger/events.rs | 29 +++++++++++++------ .../node => shared/src}/ledger/events/log.rs | 7 ++--- .../src}/ledger/events/log/dumb_queries.rs | 7 ++--- shared/src/ledger/mod.rs | 2 ++ wasm/Cargo.lock | 10 +++++++ wasm_for_tests/wasm_source/Cargo.lock | 10 +++++++ 15 files changed, 65 insertions(+), 34 deletions(-) rename {apps/src/lib/node => shared/src}/ledger/events.rs (87%) rename {apps/src/lib/node => shared/src}/ledger/events/log.rs (97%) rename {apps/src/lib/node => shared/src}/ledger/events/log/dumb_queries.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 30ab6e2655..9105993abd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3324,6 +3324,7 @@ dependencies = [ "borsh", "byte-unit", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", @@ -3401,7 +3402,6 @@ dependencies = [ "byte-unit", "byteorder", "bytes 1.2.1", - "circular-queue", "clap", "clarity", "color-eyre", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 3a0e59c495..9528e5200c 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -77,7 +77,6 @@ blake2b-rs = "0.2.0" borsh = "0.9.0" byte-unit = "4.0.13" byteorder = "1.4.2" -circular-queue = "0.2.6" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} clarity = "0.5.1" diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6d181085d0..8c77ad0636 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -13,6 +13,7 @@ use async_std::prelude::*; use borsh::BorshDeserialize; use data_encoding::HEXLOWER; use itertools::Itertools; +use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; @@ -34,17 +35,16 @@ use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; use tendermint::abci::Code; +use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::error::Error as TError; +use tendermint_rpc::query::Query; +use tendermint_rpc::{ + Client, HttpClient, Order, SubscriptionClient, WebSocketClient, +}; use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; -use crate::facade::tendermint_config::net::Address as TendermintAddress; -use crate::facade::tendermint_rpc::error::Error as TError; -use crate::facade::tendermint_rpc::query::Query; -use crate::facade::tendermint_rpc::{ - Client, HttpClient, Order, SubscriptionClient, WebSocketClient, -}; -use crate::node::ledger::events::Event; /// Query the status of a given transaction. /// diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 0e94155378..66fe1912df 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,11 +1,11 @@ use std::convert::TryFrom; +use namada::ledger::events::Event; use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; -use crate::node::ledger::events::Event; /// Data needed for broadcasting a tx and /// monitoring its progress on chain diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d4d6d4d290..bcac12f286 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,7 +1,6 @@ mod abortable; mod broadcaster; mod ethereum_node; -pub mod events; mod shell; mod shims; pub mod storage; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 6aadccf371..100bd429a2 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,3 +1,4 @@ +use namada::ledger::events::EventType; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, @@ -14,7 +15,6 @@ use namada::types::storage::Epoch; use namada::types::token; use super::*; -use crate::node::ledger::events::EventType; #[derive(Default)] pub struct ProposalsResult { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index aa12b80f1b..f0c158e8c7 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -21,6 +21,8 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::events::log::EventLog; +use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, @@ -54,8 +56,6 @@ use crate::facade::tendermint_proto::abci::{ }; use crate::facade::tendermint_proto::crypto::public_key; use crate::facade::tower_abci::{request, response}; -use crate::node::ledger::events::log::EventLog; -use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; 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 f5b9488033..90a752d7b5 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -257,6 +257,10 @@ pub mod shim { /// Custom types for response payloads pub mod response { + use namada::ledger::events::Event; + #[cfg(feature = "abcipp")] + use namada::ledger::events::EventLevel; + use crate::facade::tendermint_proto::abci::{ Event as TmEvent, ResponseProcessProposal, ValidatorUpdate, }; @@ -267,9 +271,6 @@ pub mod shim { abci::{ExecTxResult, ResponseFinalizeBlock}, types::ConsensusParams, }; - use crate::node::ledger::events::Event; - #[cfg(feature = "abcipp")] - use crate::node::ledger::events::EventLevel; #[derive(Debug, Default)] pub struct VerifyHeader; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c7be0fe608..165578eec3 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,6 +86,7 @@ arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/he async-trait = {version = "0.1.51", optional = true} bech32 = "0.8.0" borsh = "0.9.0" +circular-queue = "0.2.6" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} diff --git a/apps/src/lib/node/ledger/events.rs b/shared/src/ledger/events.rs similarity index 87% rename from apps/src/lib/node/ledger/events.rs rename to shared/src/ledger/events.rs index 0a8a50ad60..0260468e00 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -1,3 +1,4 @@ +//! Logic to do with events emitted by the ledger. pub mod log; use std::collections::HashMap; @@ -6,18 +7,20 @@ use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::governance::utils::ProposalEvent; -use namada::types::ibc::IbcEvent; -use namada::types::transaction::{hash_tx, TxType}; +use tendermint_proto::abci::EventAttribute; use thiserror::Error; -use crate::facade::tendermint_proto::abci::EventAttribute; +use crate::ledger::governance::utils::ProposalEvent; +use crate::types::ibc::IbcEvent; +use crate::types::transaction::{hash_tx, TxType}; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventLevel { + /// Indicates an event is to do with a finalized block. Block, + /// Indicates an event is to do with an individual transaction. Tx, } @@ -25,21 +28,25 @@ pub enum EventLevel { /// using a websocket client #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub struct Event { + /// The type of event. pub event_type: EventType, + /// The level of the event - whether it relates to a block or an individual + /// transaction. pub level: EventLevel, + /// Key-value attributes of the event. pub attributes: HashMap, } /// The two types of custom events we currently use #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventType { - // The transaction was accepted to be included in a block + /// The transaction was accepted to be included in a block Accepted, - // The transaction was applied during block finalization + /// The transaction was applied during block finalization Applied, - // The IBC transaction was applied during block finalization + /// The IBC transaction was applied during block finalization Ibc(String), - // The proposal that has been executed + /// The proposal that has been executed Proposal, } @@ -153,7 +160,7 @@ impl From for Event { } /// Convert our custom event into the necessary tendermint proto type -impl From for crate::facade::tendermint_proto::abci::Event { +impl From for tendermint_proto::abci::Event { fn from(event: Event) -> Self { Self { r#type: event.event_type.to_string(), @@ -187,12 +194,16 @@ impl Attributes { } } +/// Errors to do with emitting events. #[derive(Error, Debug)] pub enum Error { + /// Error when parsing attributes from an event JSON. #[error("Json missing `attributes` field")] MissingAttributes, + /// Missing key in attributes. #[error("Attributes missing key: {0}")] MissingKey(String), + /// Missing value in attributes. #[error("Attributes missing value: {0}")] MissingValue(String), } diff --git a/apps/src/lib/node/ledger/events/log.rs b/shared/src/ledger/events/log.rs similarity index 97% rename from apps/src/lib/node/ledger/events/log.rs rename to shared/src/ledger/events/log.rs index f3e655469a..931f0088c4 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/shared/src/ledger/events/log.rs @@ -8,7 +8,7 @@ use std::default::Default; use circular_queue::CircularQueue; -use crate::node::ledger::events::Event; +use crate::ledger::events::Event; pub mod dumb_queries; @@ -79,10 +79,9 @@ impl EventLog { #[cfg(test)] mod tests { - use namada::types::hash::Hash; - use super::*; - use crate::node::ledger::events::{EventLevel, EventType}; + use crate::ledger::events::{EventLevel, EventType}; + use crate::types::hash::Hash; const HASH: &str = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/shared/src/ledger/events/log/dumb_queries.rs similarity index 95% rename from apps/src/lib/node/ledger/events/log/dumb_queries.rs rename to shared/src/ledger/events/log/dumb_queries.rs index d7e2c51630..9d48979860 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/shared/src/ledger/events/log/dumb_queries.rs @@ -6,9 +6,8 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` -use namada::types::hash::Hash; - -use crate::node::ledger::events::{Event, EventType}; +use crate::ledger::events::{Event, EventType}; +use crate::types::hash::Hash; /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. @@ -59,7 +58,7 @@ impl QueryMatcher { #[cfg(test)] mod tests { use super::*; - use crate::node::ledger::events::EventLevel; + use crate::ledger::events::EventLevel; /// Test if query matching is working as expected. #[test] diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index cbe2528b76..1a3867195e 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,6 +1,8 @@ //! The ledger modules pub mod eth_bridge; +#[cfg(feature = "ferveo-tpke")] +pub mod events; pub mod gas; pub mod governance; pub mod ibc; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index b8dd38f417..ce21404ce7 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -393,6 +393,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1479,6 +1488,7 @@ dependencies = [ "bech32", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4c125ddb18..7c1a3be15f 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -393,6 +393,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1479,6 +1488,7 @@ dependencies = [ "bech32", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", From 616c94b411d9ff2e0e38bfcbaa56bed7773be301 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 11:44:15 +0000 Subject: [PATCH 1259/1995] Move event log from `Shell` to `Storage` --- .../lib/node/ledger/shell/finalize_block.rs | 5 ++- apps/src/lib/node/ledger/shell/mod.rs | 17 ------- shared/src/ledger/queries/shell.rs | 21 +++++---- shared/src/ledger/storage/mod.rs | 44 +++++++++++++++++++ 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a02c9b7559..577fa430f4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,6 +1,7 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use namada::ledger::protocol; +use namada::ledger::storage::EventLogExt; use namada::types::storage::{BlockHash, Header}; use namada::types::transaction::protocol::ProtocolTxType; @@ -255,7 +256,9 @@ where .finalize_transaction() .map_err(|_| Error::GasOverflow)?; - self.event_log_mut().log_events(response.events.clone()); + self.storage + .event_log_mut() + .log_events(response.events.clone()); Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f0c158e8c7..5ab3e9ce5d 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -21,7 +21,6 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ @@ -334,8 +333,6 @@ where pub(super) tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, - /// Log of events emitted by `FinalizeBlock`. - event_log: EventLog, } impl Shell @@ -450,23 +447,9 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), - // TODO: config event log params - event_log: EventLog::default(), } } - /// Return a reference to the [`EventLog`]. - #[inline] - pub fn event_log(&self) -> &EventLog { - &self.event_log - } - - /// Return a mutable reference to the [`EventLog`]. - #[inline] - pub fn event_log_mut(&mut self) -> &mut EventLog { - &mut self.event_log - } - /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7c14bdd089..3c658584de 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -1,10 +1,11 @@ use borsh::BorshSerialize; use tendermint_proto::crypto::{ProofOp, ProofOps}; +use crate::ledger::events::log::dumb_queries; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, EventLogExt, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; use crate::types::hash::Hash; use crate::types::storage::{self, Epoch, PrefixValue}; @@ -223,25 +224,27 @@ where } fn accepted( - _ctx: RequestCtx<'_, D, H>, - _tx_hash: Hash, -) -> storage_api::Result + ctx: RequestCtx<'_, D, H>, + tx_hash: Hash, +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - todo!("pending reimplementation with new query router") + let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); + Ok(ctx.storage.query_event_log(matcher)) } fn applied( - _ctx: RequestCtx<'_, D, H>, - _hash: Hash, -) -> storage_api::Result + ctx: RequestCtx<'_, D, H>, + tx_hash: Hash, +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - todo!("pending reimplementation with new query router") + let matcher = dumb_queries::QueryMatcher::applied(tx_hash); + Ok(ctx.storage.query_event_log(matcher)) } #[cfg(test)] diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 5ee2f57ffd..d88c8e906c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -11,8 +11,10 @@ pub mod write_log; use core::fmt::Debug; use std::array; +use borsh::BorshSerialize; use thiserror::Error; +use super::events::log::{dumb_queries, EventLog}; use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; @@ -69,6 +71,8 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Log of events emitted by `FinalizeBlock`. + event_log: EventLog, } /// The block storage data @@ -270,6 +274,44 @@ pub trait DBWriteBatch { fn delete>(&mut self, key: K); } +/// Methods for querying and mutating an event log. +pub trait EventLogExt { + /// Query events in the event log matching the given query. + fn query_event_log(&self, matcher: dumb_queries::QueryMatcher) -> Vec; + /// Return a reference to the [`EventLog`]. + fn event_log(&self) -> &EventLog; + /// Return a mutable reference to the [`EventLog`]. + fn event_log_mut(&mut self) -> &mut EventLog; +} + +impl EventLogExt for Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Query events in the event log matching the given query. + fn query_event_log(&self, matcher: dumb_queries::QueryMatcher) -> Vec { + self.event_log() + .iter_with_matcher(matcher) + .cloned() + .collect::>() + .try_to_vec() + .unwrap() + } + + /// Return a reference to the [`EventLog`]. + #[inline] + fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } +} + impl Storage where D: DB + for<'iter> DBIter<'iter>, @@ -302,6 +344,7 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + event_log: EventLog::default(), } } @@ -922,6 +965,7 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + event_log: EventLog::default(), } } } From 7a899afce99b8233da015e953ff3cc53b30fcff4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 12:05:53 +0000 Subject: [PATCH 1260/1995] Guard less things with the ferveo-tpke feature so that wasm compiles --- shared/src/ledger/events.rs | 2 ++ shared/src/ledger/mod.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/events.rs b/shared/src/ledger/events.rs index 0260468e00..b17d2db83d 100644 --- a/shared/src/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -12,6 +12,7 @@ use thiserror::Error; use crate::ledger::governance::utils::ProposalEvent; use crate::types::ibc::IbcEvent; +#[cfg(feature = "ferveo-tpke")] use crate::types::transaction::{hash_tx, TxType}; /// Indicates if an event is emitted do to @@ -65,6 +66,7 @@ impl Display for EventType { impl Event { /// Creates a new event with the hash and height of the transaction /// already filled in + #[cfg(feature = "ferveo-tpke")] pub fn new_tx_event(tx: &TxType, height: u64) -> Self { let mut event = match tx { TxType::Wrapper(wrapper) => { diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 1a3867195e..a04a9ded5f 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,7 +1,6 @@ //! The ledger modules pub mod eth_bridge; -#[cfg(feature = "ferveo-tpke")] pub mod events; pub mod gas; pub mod governance; From c793f1902ae987586b0455196fa9f9c5fdbc0dcb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 12:07:09 +0000 Subject: [PATCH 1261/1995] Fix cargo doc link --- shared/src/ledger/protocol/transactions/ethereum_events/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index bd6361832b..434eb7a9a4 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -1,5 +1,5 @@ //! Code for handling -//! [`namada::types::transaction::protocol::ProtocolTxType::EthereumEvents`] +//! [`crate::types::transaction::protocol::ProtocolTxType::EthereumEvents`] //! transactions. mod eth_msgs; mod events; From 6831014a1f1457e5767f758d81368ae2d34bb131 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 13:16:05 +0100 Subject: [PATCH 1262/1995] [feat]: Added a cli for interacting with the bridge pool. Needs testing and a couple of small todos --- apps/Cargo.toml | 6 + apps/src/bin/anoma-relayer/cli.rs | 24 +++ apps/src/bin/anoma-relayer/main.rs | 17 ++ apps/src/bin/anoma/cli.rs | 3 + apps/src/lib/cli.rs | 241 +++++++++++++++++++--- apps/src/lib/client/eth_bridge_pool.rs | 88 ++++++++ apps/src/lib/client/mod.rs | 1 + apps/src/lib/client/tx.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 51 ++++- shared/src/types/eth_bridge_pool.rs | 7 + shared/src/types/ethereum_events.rs | 2 + 11 files changed, 398 insertions(+), 44 deletions(-) create mode 100644 apps/src/bin/anoma-relayer/cli.rs create mode 100644 apps/src/bin/anoma-relayer/main.rs create mode 100644 apps/src/lib/client/eth_bridge_pool.rs diff --git a/apps/Cargo.toml b/apps/Cargo.toml index fc22eb7040..1dee460d48 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -38,6 +38,12 @@ doc = false name = "namadaw" path = "src/bin/anoma-wallet/main.rs" +# Namada relayer +[[bin]] +doc = false +name = "namadar" +path = "src/bin/anoma-relayer/main.rs" + [features] default = ["std", "abciplus"] dev = ["namada/dev"] diff --git a/apps/src/bin/anoma-relayer/cli.rs b/apps/src/bin/anoma-relayer/cli.rs new file mode 100644 index 0000000000..fef31b92e7 --- /dev/null +++ b/apps/src/bin/anoma-relayer/cli.rs @@ -0,0 +1,24 @@ +//! Anoma client CLI. + +use color_eyre::eyre::Result; +use namada_apps::cli; +use namada_apps::cli::cmds; +use namada_apps::client::eth_bridge_pool; + +pub async fn main() -> Result<()> { + let (cmd, ctx) = cli::anoma_relayer_cli(); + use cmds::EthBridgePool as Sub; + match cmd { + // Ledger cmds + Sub::AddTransfer(args) => { + eth_bridge_pool::add_to_eth_bridge_pool(ctx, args).await; + } + Sub::ConstructProof(args) => { + eth_bridge_pool::construct_bridge_pool_proof(args).await; + } + Sub::QueryPool(query) => { + eth_bridge_pool::query_bridge_pool(query).await; + } + } + Ok(()) +} diff --git a/apps/src/bin/anoma-relayer/main.rs b/apps/src/bin/anoma-relayer/main.rs new file mode 100644 index 0000000000..73876fe7d2 --- /dev/null +++ b/apps/src/bin/anoma-relayer/main.rs @@ -0,0 +1,17 @@ +mod cli; + +use color_eyre::eyre::Result; +use namada_apps::logging; +use tracing_subscriber::filter::LevelFilter; + +#[tokio::main] +async fn main() -> Result<()> { + // init error reporting + color_eyre::install()?; + + // init logging + logging::init_from_env_or(LevelFilter::INFO)?; + + // run the CLI + cli::main().await +} diff --git a/apps/src/bin/anoma/cli.rs b/apps/src/bin/anoma/cli.rs index b0ed783276..6ea298ff1f 100644 --- a/apps/src/bin/anoma/cli.rs +++ b/apps/src/bin/anoma/cli.rs @@ -55,6 +55,9 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Anoma::TxVoteProposal(_) | cli::cmds::Anoma::Intent(_) => handle_subcommand("namadac", sub_args), cli::cmds::Anoma::Wallet(_) => handle_subcommand("namadaw", sub_args), + cli::cmds::Anoma::EthBridgePool(_) => { + handle_subcommand("namadar", sub_args) + } } } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index d7dd956eba..056ceac534 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -23,12 +23,14 @@ const APP_NAME: &str = "Anoma"; const NODE_CMD: &str = "node"; const CLIENT_CMD: &str = "client"; const WALLET_CMD: &str = "wallet"; +const BRIDGE_POOL_CMD: &str = "ethereum-bridge-pool"; pub mod cmds { use clap::AppSettings; use super::utils::*; use super::{args, ArgMatches, CLIENT_CMD, NODE_CMD, WALLET_CMD}; + use crate::cli::BRIDGE_POOL_CMD; /// Commands for `anoma` binary. #[allow(clippy::large_enum_variant)] @@ -40,6 +42,7 @@ pub mod cmds { Wallet(AnomaWallet), // Inlined commands from the node. + EthBridgePool(EthBridgePool), Ledger(Ledger), Gossip(Gossip), Matchmaker(Matchmaker), @@ -60,6 +63,7 @@ pub mod cmds { app.subcommand(AnomaNode::def()) .subcommand(AnomaClient::def()) .subcommand(AnomaWallet::def()) + .subcommand(EthBridgePool::def()) .subcommand(Ledger::def()) .subcommand(Gossip::def()) .subcommand(Matchmaker::def()) @@ -1351,6 +1355,118 @@ pub mod cmds { .add_args::() } } + + /// Used as sub-commands (`SubCmd` instance) in `anoma` binary. + #[derive(Clone, Debug)] + #[allow(clippy::large_enum_variant)] + pub enum EthBridgePool { + /// Add a transfer to the pool. + AddTransfer(args::EthereumBridgePool), + /// Construct a proof that a set of transfers is in the pool. + /// This can be used to relay transfers across the + /// bridge to Ethereum. + ConstructProof(args::BridgePoolProof), + /// Query the contents of the pool. + QueryPool(args::Query), + } + + impl Cmd for EthBridgePool { + fn add_sub(app: App) -> App { + app.subcommand(AddToEthBridgePool::def().display_order(1)) + .subcommand(ConstructProof::def().display_order(1)) + .subcommand(QueryEthBridgePool::def().display_order(1)) + } + + fn parse(matches: &ArgMatches) -> Option { + let add_to_pool = AddToEthBridgePool::parse(matches) + .map(|add| Self::AddTransfer(add.0)); + let construct_proof = ConstructProof::parse(matches) + .map(|proof| Self::ConstructProof(proof.0)); + let query_pool = QueryEthBridgePool::parse(matches) + .map(|q| Self::QueryPool(q.0)); + add_to_pool.or(construct_proof).or(query_pool) + } + } + + impl SubCmd for EthBridgePool { + const CMD: &'static str = BRIDGE_POOL_CMD; + + fn parse(matches: &ArgMatches) -> Option { + Cmd::parse(matches) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Functionality for interacting with the Ethereum bridge \ + pool. This pool holds transfers waiting to be relayed to \ + Ethereum.", + ) + .subcommand(AddToEthBridgePool::def().display_order(1)) + .subcommand(ConstructProof::def().display_order(1)) + .subcommand(QueryEthBridgePool::def().display_order(1)) + } + } + + #[derive(Clone, Debug)] + pub struct AddToEthBridgePool(pub args::EthereumBridgePool); + + impl SubCmd for AddToEthBridgePool { + const CMD: &'static str = "add-transfer"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::EthereumBridgePool::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Add a new transfer to the Ethereum bridge pool.") + .add_args::() + } + } + + #[derive(Clone, Debug)] + pub struct ConstructProof(pub args::BridgePoolProof); + + impl SubCmd for ConstructProof { + const CMD: &'static str = "construct-proof"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::BridgePoolProof::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Construct a merkle proof that the given transfer is in \ + the pool.", + ) + .add_args::() + } + } + + #[derive(Clone, Debug)] + pub struct QueryEthBridgePool(args::Query); + + impl SubCmd for QueryEthBridgePool { + const CMD: &'static str = "query"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::Query::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Get the contents of the Ethereum bridge pool.") + .add_args::() + } + } } pub mod args { @@ -1368,6 +1484,7 @@ pub mod args { use namada::types::ethereum_events::EthAddress; use namada::types::governance::ProposalVote; use namada::types::intent::{DecimalWrapper, Exchange}; + use namada::types::keccak::KeccakHash; use namada::types::key::*; use namada::types::storage::{self, Epoch}; use namada::types::token; @@ -1418,13 +1535,16 @@ pub mod args { const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const FEE_PAYER: Arg = arg("fee-payer"); - const FEE_TOKEN: ArgDefaultFromCtx = - arg_default_from_ctx("fee-token", DefaultFn(|| "XAN".into())); const FORCE: ArgFlag = flag("force"); + const GAS_AMOUNT: ArgDefault = + arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); + const GAS_TOKEN: ArgDefaultFromCtx = + arg_default_from_ctx("gas-token", DefaultFn(|| "XAN".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); + const HASH_LIST: Arg = arg("hash-list"); const INTENT_GOSSIPER_ADDR: ArgDefault = arg_default( "intent-gossiper", DefaultFn(|| { @@ -1572,7 +1692,10 @@ pub mod args { } /// A transfer to be added to the Ethereum bridge pool. + #[derive(Clone, Debug)] pub struct EthereumBridgePool { + /// The args for building a tx to the bridge pool + pub tx: Tx, /// The type of token pub asset: EthAddress, /// The recipient address @@ -1589,6 +1712,7 @@ pub mod args { impl Args for EthereumBridgePool { fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); let asset = ERC20.parse(matches); let recipient = ETH_ADDRESS.parse(matches); let sender = ADDRESS.parse(matches); @@ -1596,6 +1720,7 @@ pub mod args { let gas_amount = FEE_AMOUNT.parse(matches); let gas_payer = FEE_PAYER.parse(matches); Self { + tx, asset, recipient, sender, @@ -1606,35 +1731,69 @@ pub mod args { } fn def(app: App) -> App { - app.arg( - ERC20 - .def() - .about("The Ethereum address of the ERC20 token."), - ) - .arg( - ETH_ADDRESS - .def() - .about("The Ethereum address receiving the tokens."), - ) - .arg( - ADDRESS - .def() - .about("The Namada address sending the tokens."), - ) - .arg( - AMOUNT.def().about( + app.add_args::() + .arg( + ERC20 + .def() + .about("The Ethereum address of the ERC20 token."), + ) + .arg( + ETH_ADDRESS + .def() + .about("The Ethereum address receiving the tokens."), + ) + .arg( + ADDRESS + .def() + .about("The Namada address sending the tokens."), + ) + .arg(AMOUNT.def().about( "The amount of tokens being sent across the bridge.", - ), - ) - .arg(FEE_AMOUNT.def().about( - "The amount of NAM you wish to pay to have this transfer \ - relayed to Ethereum.", + )) + .arg(FEE_AMOUNT.def().about( + "The amount of NAM you wish to pay to have this transfer \ + relayed to Ethereum.", + )) + .arg( + FEE_PAYER.def().about( + "The Namada address of the account paying the fee.", + ), + ) + } + } + + #[derive(Debug, Clone)] + pub struct BridgePoolProof { + /// The query parameters. + pub query: Query, + pub transfers: Vec, + } + + impl Args for BridgePoolProof { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let hashes = HASH_LIST.parse(matches); + Self { + query, + transfers: hashes + .split(' ') + .map(|hash| { + KeccakHash::try_from(hash).unwrap_or_else(|_| { + tracing::info!( + "Could not parse '{}' as a Keccak hash.", + hash + ); + safe_exit(1) + }) + }) + .collect(), + } + } + + fn def(app: App) -> App { + app.add_args::().arg(HASH_LIST.def().about( + "List of Keccak hashes of transfers in the bridge pool.", )) - .arg( - FEE_PAYER - .def() - .about("The Namada address of the account paying the fee."), - ) } } @@ -2728,7 +2887,7 @@ pub mod args { /// save it in the wallet. pub initialized_account_alias: Option, /// The amount being payed to include the transaction - pub fee_amount: token::Amount, + pub fee_amount: Amount, /// The token in which the fee is being paid pub fee_token: WalletAddress, /// The max amount of gas used to process tx @@ -2760,10 +2919,10 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(FEE_AMOUNT.def().about( + .arg(GAS_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(FEE_TOKEN.def().about("The token for paying the fee")) + .arg(GAS_TOKEN.def().about("The token for paying the fee")) .arg( GAS_LIMIT.def().about( "The maximum amount of gas needed to run transaction", @@ -2796,8 +2955,8 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = FEE_AMOUNT.parse(matches); - let fee_token = FEE_TOKEN.parse(matches); + let fee_amount = GAS_AMOUNT.parse(matches); + let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); let signing_key = SIGNING_KEY_OPT.parse(matches); @@ -3225,6 +3384,11 @@ pub fn anoma_wallet_cli() -> (cmds::AnomaWallet, Context) { cmds::AnomaWallet::parse_or_print_help(app) } +pub fn anoma_relayer_cli() -> (cmds::EthBridgePool, Context) { + let app = anoma_relayer_app(); + cmds::EthBridgePool::parse_or_print_help(app) +} + fn anoma_app() -> App { let app = App::new(APP_NAME) .version(anoma_version()) @@ -3260,3 +3424,12 @@ fn anoma_wallet_app() -> App { .setting(AppSettings::SubcommandRequiredElseHelp); cmds::AnomaWallet::add_sub(args::Global::def(app)) } + +fn anoma_relayer_app() -> App { + let app = App::new(APP_NAME) + .version(anoma_version()) + .author(crate_authors!("\n")) + .about("Anoma Ethereum bridge pool command line interface.") + .setting(AppSettings::SubcommandRequiredElseHelp); + cmds::AnomaWallet::add_sub(args::Global::def(app)) +} diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs new file mode 100644 index 0000000000..609f199f32 --- /dev/null +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -0,0 +1,88 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::proto::Tx; +use namada::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, +}; + +use super::tx::process_tx; +use crate::cli::{args, safe_exit, Context}; +use crate::facade::tendermint::abci::Code; +use crate::facade::tendermint_rpc::{Client, HttpClient}; +use crate::node::ledger::rpc::{BridgePoolSubpath, Path}; + +const ADD_TRANSFER_WASM: &str = "tx_bridge_pool.wasm"; + +/// Craft a transaction that adds a transfer to the Ethereum bridge pool. +pub async fn add_to_eth_bridge_pool( + ctx: Context, + args: args::EthereumBridgePool, +) { + let args::EthereumBridgePool { + ref tx, + asset, + recipient, + ref sender, + amount, + gas_amount, + ref gas_payer, + } = args; + let tx_code = ctx.read_wasm(ADD_TRANSFER_WASM); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset, + recipient, + sender: ctx.get(sender), + amount, + // TODO: Add real nonce + nonce: Default::default(), + }, + gas_fee: GasFee { + amount: gas_amount, + payer: ctx.get(gas_payer), + }, + }; + let data = transfer.try_to_vec().unwrap(); + let transfer_tx = Tx::new(tx_code, Some(data)); + // this should not initialize any new addresses, so we ignore the result. + process_tx(ctx, tx, transfer_tx, None).await; +} + +/// Construct a proof that a set of transfers are in the bridge pool. +pub async fn construct_bridge_pool_proof(args: args::BridgePoolProof) { + let client = HttpClient::new(args.query.ledger_address).unwrap(); + let path = Path::EthereumBridgePool(BridgePoolSubpath::Proof); + let data = args.transfers.try_to_vec().unwrap(); + let response = client + .abci_query(Some(path.into()), data, None, false) + .await + .unwrap(); + if response.code != Code::Ok { + println!("{}", response.info); + } else { + println!("ABI Encoded Proof:\n {:#?}", response.value); + } +} + +/// Query the contents of the Ethereum bridge pool. +/// Prints out a json payload. +pub async fn query_bridge_pool(args: args::Query) { + let client = HttpClient::new(args.ledger_address).unwrap(); + let path = Path::EthereumBridgePool(BridgePoolSubpath::Contents); + let response = client + .abci_query(Some(path.into()), vec![], None, false) + .await + .unwrap(); + if response.code != Code::Ok { + println!("{}", response.info); + } else { + let transfers: Vec = + BorshDeserialize::try_from_slice(response.value.as_slice()) + .unwrap_or_else(|_| { + tracing::info!( + "Could not parse the response from the ledger." + ); + safe_exit(1) + }); + println!("{:#?}", serde_json::to_string_pretty(&transfers)); + } +} diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 18b32889b5..752c232082 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,3 +1,4 @@ +pub mod eth_bridge_pool; pub mod gossip; pub mod rpc; pub mod signing; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 49d90a586d..459e919fd1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1062,7 +1062,7 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( +pub async fn process_tx( ctx: Context, args: &args::Tx, tx: Tx, diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 41411cf7bb..fe177f8a38 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -5,7 +5,7 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_key_from_hash, get_pending_key, get_signed_root_key, + get_key_from_hash, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; @@ -18,6 +18,7 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; +use namada::types::keccak::KeccakHash; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; @@ -399,8 +400,8 @@ where &self, request_bytes: Vec, ) -> response::Query { - if let Ok(transfers) = - >::try_from_slice(request_bytes.as_slice()) + if let Ok(transfer_hashes) = + >::try_from_slice(request_bytes.as_slice()) { // get the latest signed merkle root of the Ethereum bridge pool let signed_root: MultiSignedMerkleRoot = match self @@ -436,12 +437,34 @@ where block height", ), ); - + // from the hashes of the transfers, get the actual values. + let keys: Vec<_> = + transfer_hashes.iter().map(get_key_from_hash).collect(); + let values: Vec = keys + .iter() + .filter_map(|key| match self.storage.read(key) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(&bytes[..]).ok() + } + _ => None, + }) + .collect(); + if values.len() != keys.len() { + return response::Query { + code: 1, + log: "One or more of the provided hashes had no \ + corresponding transfer in storage." + .into(), + info: "One or more of the provided hashes had no \ + corresponding transfer in storage." + .into(), + ..Default::default() + }; + } // get the membership proof - let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); match tree.get_sub_tree_existence_proof( &keys, - transfers.into_iter().map(MerkleValue::from).collect(), + values.into_iter().map(MerkleValue::from).collect(), ) { Ok(BridgePool(proof)) => response::Query { code: 0, @@ -864,7 +887,9 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { - use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, BridgePoolTree, + }; use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use namada::types::ethereum_events::EthAddress; @@ -1061,6 +1086,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1098,6 +1124,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1151,6 +1178,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1196,7 +1224,9 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( - vec![transfer.clone()].try_to_vec().expect("Test failed"), + vec![transfer.keccak256()] + .try_to_vec() + .expect("Test failed"), ); assert_eq!(resp.code, 0); @@ -1229,6 +1259,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1275,7 +1306,9 @@ mod test_queries { // this is in the pool, but its merkle root has not been signed yet let resp = shell.generate_bridge_pool_proof( - vec![transfer2].try_to_vec().expect("Test failed"), + vec![transfer2.keccak256()] + .try_to_vec() + .expect("Test failed"), ); // thus proof generation should fail assert_eq!(resp.code, 1); diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index eb25dae7ba..c6e1877013 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use serde::{Deserialize, Serialize}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; @@ -27,6 +28,8 @@ const NAMESPACE: &str = "transfer"; PartialEq, Ord, Eq, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, @@ -54,6 +57,8 @@ pub struct TransferToEthereum { PartialEq, Ord, Eq, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, @@ -102,6 +107,8 @@ impl From<&PendingTransfer> for Key { PartialEq, Ord, Eq, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 153a358817..fcfc1f54e1 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -23,6 +23,8 @@ use crate::types::token::Amount; Eq, PartialOrd, Ord, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From 30ff06bb66141d7d25957823850465683fdc7866 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 13:29:31 +0100 Subject: [PATCH 1263/1995] [fix]: Tiny from code review --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index b01eb98e1b..fa295026ba 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -81,6 +81,11 @@ where } } +/// Check if a delta matches the delta given by a transfer +fn check_delta(delta: (Address, Amount), transfer: &PendingTransfer) -> bool { + &delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount +} + impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> where D: 'static + DB + for<'iter> DBIter<'iter>, @@ -207,12 +212,7 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some(delta)) - if delta - == ( - transfer.transfer.sender, - transfer.transfer.amount, - ) => {} + Ok(Some(delta)) if check_delta(delta, &transfer) => {} other => { tracing::debug!( "The assets of the transfer were not properly \ From ad954e67dbe909a7a05eda910c78707dd4fa4403 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 15:30:10 +0200 Subject: [PATCH 1264/1995] [feat]: Bridge pool vp now checks that funds to be transferred are escrowed. Needs tests --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 136 +++++++++++++++++- .../ledger/eth_bridge/storage/bridge_pool.rs | 17 +++ shared/src/ledger/eth_bridge/vp/mod.rs | 27 +++- shared/src/types/eth_bridge_pool.rs | 2 + 4 files changed, 169 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index a765a423af..886278bb5b 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -17,6 +17,8 @@ use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; +use crate::ledger::eth_bridge::storage::wrapped_erc20s; +use crate::ledger::eth_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; @@ -116,11 +118,29 @@ where }; let pending_key = get_pending_key(&transfer); + // check that transfer is not already in the pool + match (&self.ctx).read_pre(&pending_key) { + Ok(Some(_)) => { + tracing::debug!( + "Rejecting transaction as the transfer is already in the \ + Ethereum bridge pool." + ); + return Ok(false); + } + Err(_) => { + return Err(eyre!( + "Could not read the storage key associated with the \ + transfer." + ) + .into()); + } + _ => {} + } for key in keys_changed.iter().filter(|k| is_bridge_pool_key(k)) { if *key != pending_key { tracing::debug!( "Rejecting transaction as it is attempting to change an \ - incorrect key in the pending transaction pool: {}.\n \ + incorrect key in the Ethereum bridge pool: {}.\n \ Expected key: {}", key, pending_key @@ -131,12 +151,12 @@ where let pending: PendingTransfer = (&self.ctx).read_post_value(&pending_key)?.ok_or(eyre!( "Rejecting transaction as the transfer wasn't added to the \ - pending transfers" + pool of pending transfers" ))?; if pending != transfer { tracing::debug!( - "An incorrect transfer was added to the pool: {:?}.\n \ - Expected: {:?}", + "An incorrect transfer was added to the Ethereum bridge pool: \ + {:?}.\n Expected: {:?}", transfer, pending ); @@ -164,7 +184,9 @@ where return Ok(false); } } else { - tracing::debug!("The bridge pools escrow was not credited."); + tracing::debug!( + "The Ethereum bridge pool's gas escrow was not credited." + ); return Ok(false); } tracing::info!( @@ -172,6 +194,40 @@ where transfer ); + // check that the assets to be transferred were escrowed + let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); + let owner_key = asset_key.balance(&transfer.transfer.sender); + let escrow_key = asset_key.balance(&BRIDGE_POOL_ADDRESS); + if keys_changed.contains(&owner_key) + && keys_changed.contains(&escrow_key) + { + match check_balance_changes( + &self.ctx, + (&escrow_key).try_into().expect("This should not fail"), + (&owner_key).try_into().expect("This should not fail"), + ) { + Ok(Some(delta)) + if delta + == ( + transfer.transfer.sender, + transfer.transfer.amount, + ) => {} + _ => { + tracing::debug!( + "The assets of the transfer were not properly \ + escrowed into the Ethereum bridge pool." + ); + return Ok(false); + } + } + } else { + tracing::debug!( + "The assets of the transfer were not properly escrowed into \ + the Ethereum bridge pool." + ); + return Ok(false); + } + Ok(true) } } @@ -226,6 +282,7 @@ mod test_bridge_pool_vp { PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), nonce: 0u64.into(), @@ -313,8 +370,9 @@ mod test_bridge_pool_vp { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 100.into(), + amount: 0.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -503,6 +561,7 @@ mod test_bridge_pool_vp { let t = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 10u64.into(), @@ -531,6 +590,7 @@ mod test_bridge_pool_vp { let t = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 10u64.into(), @@ -570,6 +630,69 @@ mod test_bridge_pool_vp { ); } + /// Test that adding a transfer to the pool + /// that is already in the pool fails. + #[test] + fn test_adding_transfer_twice_fails() { + // setup + let mut write_log = new_writelog(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = initial_pool(); + // change the payers account + let bertha_account_key = balance_key(&xan(), &bertha_address()); + let new_bertha_balance = (Amount::from(BERTHA_WEALTH) - GAS_FEE.into()) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&bertha_account_key, new_bertha_balance) + .expect("Test failed"); + // change the escrow account + let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let new_escrow_balance = (Amount::from(ESCROWED_AMOUNT) + + GAS_FEE.into()) + .try_to_vec() + .expect("Test failed"); + write_log + .write(&escrow, new_escrow_balance) + .expect("Test failed"); + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let verifiers = BTreeSet::default(); + let res = vp.validate_tx(&signed, &keys_changed, &verifiers); + assert!(!res.expect("Test failed")); + } + /// Test that a transfer added to the pool with zero gas fees /// is rejected. #[test] @@ -587,6 +710,7 @@ mod test_bridge_pool_vp { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([1; 20]), amount: 100.into(), nonce: 1u64.into(), diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 865c4a42ec..befe3d044a 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -349,6 +349,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -372,6 +373,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -400,6 +402,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -434,6 +437,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -460,6 +464,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -490,6 +495,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -513,6 +519,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -548,6 +555,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([2; 20]), amount: 1.into(), nonce: 42u64.into(), @@ -565,6 +573,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([1; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 1u64.into(), nonce: 42u64.into(), @@ -596,6 +605,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([0; 20]), + sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), nonce: 0.into(), @@ -624,6 +634,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -653,6 +664,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -682,6 +694,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -709,6 +722,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -736,6 +750,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -763,6 +778,7 @@ mod test_bridge_pool_tree { let transfer = PendingTransfer { transfer: TransferToEthereum { asset: EthAddress([i; 20]), + sender: bertha_address(), recipient: EthAddress([i + 1; 20]), amount: (i as u64).into(), nonce: 42u64.into(), @@ -797,6 +813,7 @@ mod test_bridge_pool_tree { .map(|(addr, nonce)| PendingTransfer { transfer: TransferToEthereum { asset: EthAddress(addr), + sender: bertha_address(), recipient: EthAddress(addr), amount: Default::default(), nonce: nonce.into(), diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0e62f7270f..51ebface24 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -71,7 +71,8 @@ where Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), }; - let sender = match check_balance_changes(&self.ctx, key_a, key_b)? { + let (sender, _) = match check_balance_changes(&self.ctx, key_a, key_b)? + { Some(sender) => sender, None => return Ok(false), }; @@ -152,13 +153,13 @@ fn extract_valid_keys_changed( /// Checks that the balances at both `key_a` and `key_b` have changed by some /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return the `Address` of the owner of the balance which is decreasing, which -/// should be authorizing the balance change. -fn check_balance_changes( +/// return the `Address` of the owner of the balance which is decreasing, +/// as by how much it decreased, which should be authorizing the balance change. +pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, -) -> Result> { +) -> Result> { let (balance_a, balance_b) = match (key_a.suffix.clone(), key_b.suffix.clone()) { ( @@ -264,14 +265,26 @@ fn check_balance_changes( if balance_a_delta < 0 { if let wrapped_erc20s::KeyType::Balance { owner } = key_a.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_b_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } } else { assert!(balance_b_delta < 0); if let wrapped_erc20s::KeyType::Balance { owner } = key_b.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_a_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 904c2c7eec..6ae5be841c 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -27,6 +27,8 @@ pub struct TransferToEthereum { pub asset: EthAddress, /// The recipient address pub recipient: EthAddress, + /// The sender of the transfer + pub sender: Address, /// The amount to be transferred pub amount: Amount, /// a nonce for replay protection From 97c07a81d13d3c7c3c530a9983f5c1dae7e28326 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Oct 2022 15:21:40 +0200 Subject: [PATCH 1265/1995] [feat]: Added checks the bridge pool vp that erc20 tokens are escrowed to its account --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 329 +++++++++++++----- 1 file changed, 246 insertions(+), 83 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 886278bb5b..d250228f00 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -119,7 +119,7 @@ where let pending_key = get_pending_key(&transfer); // check that transfer is not already in the pool - match (&self.ctx).read_pre(&pending_key) { + match (&self.ctx).read_pre_value(&pending_key) { Ok(Some(_)) => { tracing::debug!( "Rejecting transaction as the transfer is already in the \ @@ -255,9 +255,30 @@ mod test_bridge_pool_vp { use crate::vm::WasmCacheRwAccess; /// The amount of NAM Bertha has + const ASSET: EthAddress = EthAddress([0; 20]); const BERTHA_WEALTH: u64 = 1_000_000; + const BERTHA_TOKENS: u64 = 10_000; const ESCROWED_AMOUNT: u64 = 1_000; + const ESCROWED_TOKENS: u64 = 1_000; const GAS_FEE: u64 = 100; + const TOKENS: u64 = 100; + + /// A set of balances for an address + struct Balance { + owner: Address, + balance: Amount, + token: Amount, + } + + impl Balance { + fn new(address: Address) -> Self { + Self { + owner: address, + balance: 0.into(), + token: 0.into(), + } + } + } /// An established user address for testing & development fn bertha_address() -> Address { @@ -281,7 +302,7 @@ mod test_bridge_pool_vp { fn initial_pool() -> PendingTransfer { PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([0; 20]), amount: 0.into(), @@ -294,33 +315,77 @@ mod test_bridge_pool_vp { } } - /// Create a new storage + /// Create a writelog representing storage before a transfer is added to the + /// pool. fn new_writelog() -> WriteLog { let mut writelog = WriteLog::default(); - // setup the bridge pool storage + // setup the initial bridge pool storage writelog .write(&get_signed_root_key(), Hash([0; 32]).try_to_vec().unwrap()) - .unwrap(); + .expect("Test failed"); let transfer = initial_pool(); writelog .write(&get_pending_key(&transfer), transfer.try_to_vec().unwrap()) - .unwrap(); - let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let amount: Amount = ESCROWED_AMOUNT.into(); - writelog - .write(&escrow_key, amount.try_to_vec().unwrap()) - .unwrap(); - - // setup a user with a balance - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let bertha_wealth: Amount = BERTHA_WEALTH.into(); - writelog - .write(&bertha_account_key, bertha_wealth.try_to_vec().unwrap()) - .unwrap(); + .expect("Test failed"); + // set up a user with a balance + update_balances( + &mut writelog, + Balance::new(bertha_address()), + SignedAmount::Positive(BERTHA_WEALTH.into()), + SignedAmount::Positive(BERTHA_TOKENS.into()), + ); + // set up the initial balances of the bridge pool + update_balances( + &mut writelog, + Balance::new(BRIDGE_POOL_ADDRESS), + SignedAmount::Positive(ESCROWED_AMOUNT.into()), + SignedAmount::Positive(ESCROWED_TOKENS.into()), + ); writelog.commit_tx(); writelog } + /// Update gas and token balances of an address and + /// return the keys changed + fn update_balances( + write_log: &mut WriteLog, + balance: Balance, + gas_delta: SignedAmount, + token_delta: SignedAmount, + ) -> BTreeSet { + // get the balance keys + let token_key = + wrapped_erc20s::Keys::from(&ASSET).balance(&balance.owner); + let account_key = balance_key(&xan(), &balance.owner); + + // update the balance of xan + let new_balance = match gas_delta { + SignedAmount::Positive(amount) => balance.balance + amount, + SignedAmount::Negative(amount) => balance.balance - amount, + } + .try_to_vec() + .expect("Test failed"); + + // update the balance of tokens + let new_token_balance = match token_delta { + SignedAmount::Positive(amount) => balance.token + amount, + SignedAmount::Negative(amount) => balance.token - amount, + } + .try_to_vec() + .expect("Test failed"); + + // write the changes to the log + write_log + .write(&account_key, new_balance) + .expect("Test failed"); + write_log + .write(&token_key, new_token_balance) + .expect("Test failed"); + + // return the keys changed + [account_key, token_key].into() + } + /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, @@ -350,6 +415,8 @@ mod test_bridge_pool_vp { /// Helper function that tests various ways gas can be escrowed, /// either correctly or incorrectly, is handled appropriately fn assert_bridge_pool( + payer_gas_delta: SignedAmount, + gas_escrow_delta: SignedAmount, payer_delta: SignedAmount, escrow_delta: SignedAmount, insert_transfer: F, @@ -369,10 +436,10 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 0.into(), + amount: TOKENS.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -380,41 +447,36 @@ mod test_bridge_pool_vp { payer: bertha_address(), }, }; - // change the payers account - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let new_bertha_balance = match payer_delta { - SignedAmount::Positive(amount) => { - Amount::from(BERTHA_WEALTH) + amount - } - SignedAmount::Negative(amount) => { - Amount::from(BERTHA_WEALTH) - amount - } - } - .try_to_vec() - .expect("Test failed"); - write_log - .write(&bertha_account_key, new_bertha_balance) - .expect("Test failed"); - // change the escrow account - let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let new_escrow_balance = match escrow_delta { - SignedAmount::Positive(amount) => { - Amount::from(ESCROWED_AMOUNT) + amount - } - SignedAmount::Negative(amount) => { - Amount::from(ESCROWED_AMOUNT) - amount - } - } - .try_to_vec() - .expect("Test failed"); - write_log - .write(&escrow, new_escrow_balance) - .expect("Test failed"); - // add transfer to pool - let verifiers = BTreeSet::new(); - let keys_changed = insert_transfer(transfer.clone(), &mut write_log); - + let mut keys_changed = + insert_transfer(transfer.clone(), &mut write_log); + + // change Bertha's balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: bertha_address(), + balance: BERTHA_WEALTH.into(), + token: BERTHA_TOKENS.into(), + }, + payer_gas_delta, + payer_delta, + ); + keys_changed.append(&mut new_keys_changed); + + // change the bridge pool balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: BRIDGE_POOL_ADDRESS, + balance: ESCROWED_AMOUNT.into(), + token: ESCROWED_TOKENS.into(), + }, + gas_escrow_delta, + escrow_delta, + ); + keys_changed.append(&mut new_keys_changed); + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { ctx: setup_ctx( @@ -435,7 +497,6 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); - let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); match expect { Expect::True => assert!(res.expect("Test failed")), @@ -450,6 +511,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -469,6 +532,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(10.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -488,6 +553,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Positive(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -507,6 +574,51 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(10.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that if the number of tokens debited + /// from one account does not equal the amount + /// credited the other, the tx is rejected + #[test] + fn test_incorrect_token_deltas() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(10.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that if the number of tokens transferred + /// is incorrect, the tx is rejected + #[test] + fn test_incorrect_tokens_escrowed() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(10.into()), + SignedAmount::Positive(10.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -526,6 +638,29 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, + ); + } + + /// Test that the amount of tokens escrowed in the + /// bridge pool is positive. + #[test] + fn test_escrowed_tokens_must_increase() { + assert_bridge_pool( + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Positive(TOKENS.into()), + SignedAmount::Negative(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -545,6 +680,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, _| BTreeSet::from([get_pending_key(&transfer)]), Expect::Error, ); @@ -557,6 +694,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -586,6 +725,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { let t = PendingTransfer { transfer: TransferToEthereum { @@ -615,6 +756,8 @@ mod test_bridge_pool_vp { assert_bridge_pool( SignedAmount::Negative(GAS_FEE.into()), SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + SignedAmount::Positive(TOKENS.into()), |transfer, log| { log.write( &get_pending_key(&transfer), @@ -645,26 +788,9 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = initial_pool(); - // change the payers account - let bertha_account_key = balance_key(&xan(), &bertha_address()); - let new_bertha_balance = (Amount::from(BERTHA_WEALTH) - GAS_FEE.into()) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&bertha_account_key, new_bertha_balance) - .expect("Test failed"); - // change the escrow account - let escrow = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - let new_escrow_balance = (Amount::from(ESCROWED_AMOUNT) - + GAS_FEE.into()) - .try_to_vec() - .expect("Test failed"); - write_log - .write(&escrow, new_escrow_balance) - .expect("Test failed"); // add transfer to pool - let keys_changed = { + let mut keys_changed = { write_log .write( &get_pending_key(&transfer), @@ -674,9 +800,36 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }; + // update Bertha's balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: bertha_address(), + balance: BERTHA_WEALTH.into(), + token: BERTHA_TOKENS.into(), + }, + SignedAmount::Negative(GAS_FEE.into()), + SignedAmount::Negative(TOKENS.into()), + ); + keys_changed.append(&mut new_keys_changed); + + // update the bridge pool balances + let mut new_keys_changed = update_balances( + &mut write_log, + Balance { + owner: BRIDGE_POOL_ADDRESS, + balance: ESCROWED_AMOUNT.into(), + token: ESCROWED_TOKENS.into(), + }, + SignedAmount::Positive(GAS_FEE.into()), + SignedAmount::Positive(TOKENS.into()), + ); + keys_changed.append(&mut new_keys_changed); + let verifiers = BTreeSet::default(); + // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -688,7 +841,6 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); - let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); assert!(!res.expect("Test failed")); } @@ -709,10 +861,10 @@ mod test_bridge_pool_vp { // the transfer to be added to the pool let transfer = PendingTransfer { transfer: TransferToEthereum { - asset: EthAddress([0; 20]), + asset: ASSET, sender: bertha_address(), recipient: EthAddress([1; 20]), - amount: 100.into(), + amount: 0.into(), nonce: 1u64.into(), }, gas_fee: GasFee { @@ -721,12 +873,23 @@ mod test_bridge_pool_vp { }, }; - write_log - .write( - &get_pending_key(&transfer), - transfer.try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); + // add transfer to pool + let mut keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 0 tokens + keys_changed.insert( + wrapped_erc20s::Keys::from(&ASSET).balance(&bertha_address()), + ); + keys_changed.insert( + wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), + ); // inform the vp that the merkle root changed let keys_changed = BTreeSet::default(); From 801a58c5fa9476db546a01d4e94d9a37477119d8 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 27 Oct 2022 14:08:17 +0200 Subject: [PATCH 1266/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 51ebface24..0c22b72344 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -154,7 +154,7 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// as by how much it decreased, which should be authorizing the balance change. +/// and by how much it decreased, which should be authorizing the balance change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, From 1e841abf37a193952b9640592dc1492ce93245c3 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 17:06:45 +0200 Subject: [PATCH 1267/1995] [fix]: Some more error logging and formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 10 ++++++---- shared/src/ledger/eth_bridge/vp/mod.rs | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index d250228f00..f7786539b6 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -127,10 +127,11 @@ where ); return Ok(false); } - Err(_) => { + Err(e) => { return Err(eyre!( "Could not read the storage key associated with the \ - transfer." + transfer: {:?}", + e ) .into()); } @@ -212,10 +213,11 @@ where transfer.transfer.sender, transfer.transfer.amount, ) => {} - _ => { + other => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool." + escrowed into the Ethereum bridge pool: {:?}", + other ); return Ok(false); } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0c22b72344..0195e1ac20 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -154,7 +154,8 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// and by how much it decreased, which should be authorizing the balance change. +/// and by how much it decreased, which should be authorizing the balance +/// change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, From 8fddca8d5f1d1b6ad5cf222a39b6b4a3d81c759c Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 13:29:31 +0100 Subject: [PATCH 1268/1995] [fix]: Tiny from code review --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index f7786539b6..3a51f0b28f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -81,6 +81,11 @@ where } } +/// Check if a delta matches the delta given by a transfer +fn check_delta(delta: (Address, Amount), transfer: &PendingTransfer) -> bool { + &delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount +} + impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> where D: 'static + DB + for<'iter> DBIter<'iter>, @@ -207,12 +212,7 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some(delta)) - if delta - == ( - transfer.transfer.sender, - transfer.transfer.amount, - ) => {} + Ok(Some(delta)) if check_delta(delta, &transfer) => {} other => { tracing::debug!( "The assets of the transfer were not properly \ From 7bbec59f0ba3f74139d61fc75784f4e94f323b42 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 13:53:07 +0100 Subject: [PATCH 1269/1995] [chore]: Rebasing on v0.8 --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 3a51f0b28f..e696051c72 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -82,8 +82,8 @@ where } /// Check if a delta matches the delta given by a transfer -fn check_delta(delta: (Address, Amount), transfer: &PendingTransfer) -> bool { - &delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount +fn check_delta(delta: &(Address, Amount), transfer: &PendingTransfer) -> bool { + delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -124,7 +124,7 @@ where let pending_key = get_pending_key(&transfer); // check that transfer is not already in the pool - match (&self.ctx).read_pre_value(&pending_key) { + match (&self.ctx).read_pre_value::(&pending_key) { Ok(Some(_)) => { tracing::debug!( "Rejecting transaction as the transfer is already in the \ @@ -212,7 +212,7 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some(delta)) if check_delta(delta, &transfer) => {} + Ok(Some(delta)) if check_delta(&delta, &transfer) => {} other => { tracing::debug!( "The assets of the transfer were not properly \ @@ -831,7 +831,13 @@ mod test_bridge_pool_vp { // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); From 3a436fcc54bae1b0359b032999aa1652ab60075b Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:53:49 +0200 Subject: [PATCH 1270/1995] [feat]: Changed erc20 address to an ethereum address --- apps/src/lib/config/ethereum_bridge/params.rs | 7 +++---- shared/src/types/ethereum_events.rs | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 857230a6c4..0ab56b0b47 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,7 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use std::num::NonZeroU64; +use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address. @@ -42,7 +43,7 @@ pub struct GenesisConfig { pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. - pub native_erc20: Address, + pub native_erc20: EthAddress, /// The Ethereum address of the bridge contract. pub bridge: UpgradeableContract, /// The Ethereum address of the governance contract. @@ -87,9 +88,7 @@ mod tests { let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { - native_erc20: Address( - "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), - ), + native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { address: Address( "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..5a66906a49 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use eyre::{eyre, Context}; +use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -55,6 +56,8 @@ impl From for Uint { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From e781093ed2fe981638f88380369f2a0cc15bf48b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 15:14:09 +0200 Subject: [PATCH 1271/1995] [feat]: Write the ethereum bridge config params on genesis --- apps/src/lib/config/ethereum_bridge/params.rs | 85 +++++-------- apps/src/lib/config/genesis.rs | 13 +- apps/src/lib/node/ledger/shell/init_chain.rs | 112 +++++++++++++++--- apps/src/lib/node/ledger/shell/mod.rs | 4 + apps/src/lib/node/ledger/storage/rocksdb.rs | 83 ++++++++++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 ++++++++++++ shared/src/ledger/parameters/mod.rs | 1 + shared/src/ledger/storage/mockdb.rs | 54 +++++++++ shared/src/ledger/storage/mod.rs | 54 +++++++++ shared/src/types/ethereum_events.rs | 36 ++++++ 10 files changed, 440 insertions(+), 76 deletions(-) create mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 0ab56b0b47..d76b6964fd 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,33 +1,22 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. -use std::num::NonZeroU64; - +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -/// Represents a configuration value for an Ethereum address. -/// -/// For instance: -/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct Address(String); - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - /// Represents chain parameters for the Ethereum bridge. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct GenesisConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. @@ -39,7 +28,16 @@ pub struct GenesisConfig { /// Represents all the Ethereum contracts that need to be directly know about by /// validators. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. @@ -50,33 +48,10 @@ pub struct Contracts { pub governance: UpgradeableContract, } -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: Address, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} - #[cfg(test)] mod tests { use eyre::Result; + use namada::ledger::parameters::ethereum_bridge::ContractVersion; use super::*; @@ -90,17 +65,11 @@ mod tests { contracts: Contracts { native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { - address: Address( - "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" - .to_string(), - ), + address: EthAddress([23; 20]), version: ContractVersion::default(), }, governance: UpgradeableContract { - address: Address( - "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" - .to_string(), - ), + address: EthAddress([18; 20]), version: ContractVersion::default(), }, }, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 6559804516..792d39bac1 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -17,6 +17,8 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; +use crate::config::ethereum_bridge; + /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -40,10 +42,10 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, + ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + TokenAccount, Validator, }; use crate::cli; - use crate::config::ethereum_bridge; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct HexString(pub String); @@ -588,6 +590,8 @@ pub mod genesis_config { parameters, pos_params, gov_params, + treasury_params, + ethereum_bridge_params: config.ethereum_bridge_params, }; genesis.init(); genesis @@ -635,6 +639,9 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, + pub treasury_params: TreasuryParams, + // Ethereum bridge config + pub ethereum_bridge_params: Option, } impl Genesis { @@ -871,6 +878,8 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), + treasury_params: TreasuryParams::default(), + ethereum_bridge_params: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 890e8a4513..adcc1aae6a 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,6 +5,8 @@ use std::hash::Hash; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::{ibc, pos}; +use namada::ledger::parameters::Parameters; +use namada::ledger::pos::PosParams; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; @@ -12,6 +14,7 @@ use namada::types::token; use sha2::{Digest, Sha256}; use super::*; +use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -26,11 +29,13 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens + /// 3. Validators + /// 4. The PoS system + /// 5. The Ethereum bridge parameters pub fn init_chain( &mut self, init: request::InitChain, ) -> Result { - let mut response = response::InitChain::default(); let (current_chain_id, _) = self.storage.get_chain_id(); if current_chain_id != init.chain_id { return Err(Error::ChainId(format!( @@ -79,13 +84,46 @@ where let mut vp_code_cache: HashMap> = HashMap::default(); // Initialize genesis established accounts + self.initialize_established_accounts( + genesis.established_accounts, + &mut vp_code_cache, + ); + + // Initialize genesis implicit + self.initialize_implicit_accounts(genesis.implicit_accounts); + + // Initialize genesis token accounts + self.initialize_token_accounts( + genesis.token_accounts, + &mut vp_code_cache, + ); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + self.configure_ethereuem_bridge(config); + } + // Initialize genesis validator accounts + self.initialize_validators(&genesis.validators, &mut vp_code_cache); + // set the initial validators set + Ok(self.set_initial_validators( + genesis.validators, + &genesis.parameters, + &genesis.pos_params, + )) + } + + /// Initialize genesis established accounts + fn initialize_established_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { for genesis::EstablishedAccount { address, vp_code_path, vp_sha256, public_key, storage, - } in genesis.established_accounts + } in accounts { let vp_code = match vp_code_cache.get(&vp_code_path).cloned() { Some(vp_code) => vp_code, @@ -129,24 +167,36 @@ where self.storage.write(&key, value).unwrap(); } } + } + /// Initialize genesis implicit accounts + fn initialize_implicit_accounts( + &mut self, + accounts: Vec, + ) { // Initialize genesis implicit - for genesis::ImplicitAccount { public_key } in genesis.implicit_accounts - { + for genesis::ImplicitAccount { public_key } in accounts { let address: address::Address = (&public_key).into(); let pk_storage_key = pk_key(&address); self.storage .write(&pk_storage_key, public_key.try_to_vec().unwrap()) .unwrap(); } + } + /// Initialize genesis token accounts + fn initialize_token_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis token accounts for genesis::TokenAccount { address, vp_code_path, vp_sha256, balances, - } in genesis.token_accounts + } in accounts { let vp_code = vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { @@ -183,9 +233,16 @@ where .unwrap(); } } + } + /// Initialize genesis validator accounts + fn initialize_validators( + &mut self, + validators: &[genesis::Validator], + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis validator accounts - for validator in &genesis.validators { + for validator in validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), || { @@ -255,22 +312,28 @@ where ) .expect("Unable to set genesis user public DKG session key"); } + } + /// Initialize the PoS and set the initial validator set + fn set_initial_validators( + &mut self, + validators: Vec, + parameters: &Parameters, + pos_params: &PosParams, + ) -> response::InitChain { + let mut response = response::InitChain::default(); // PoS system depends on epoch being initialized let (current_epoch, _gas) = self.storage.get_current_epoch(); pos::init_genesis_storage( &mut self.storage, - &genesis.pos_params, - genesis - .validators - .iter() - .map(|validator| &validator.pos_data), + pos_params, + validators.iter().map(|validator| &validator.pos_data), current_epoch, ); ibc::init_genesis_storage(&mut self.storage); // Set the initial validator set - for validator in genesis.validators { + for validator in validators { let mut abci_validator = abci::ValidatorUpdate::default(); let consensus_key: common::PublicKey = validator.pos_data.consensus_key.clone(); @@ -278,14 +341,33 @@ where sum: Some(key_to_tendermint(&consensus_key).unwrap()), }; abci_validator.pub_key = Some(pub_key); - let power: u64 = - validator.pos_data.voting_power(&genesis.pos_params).into(); + let power: u64 = validator.pos_data.voting_power(pos_params).into(); abci_validator.power = power .try_into() .expect("unexpected validator's voting power"); response.validators.push(abci_validator); } - Ok(response) + response + } + + /// Set the parameters for the Ethereum bridge + fn configure_ethereuem_bridge( + &mut self, + config: ethereum_bridge::params::GenesisConfig, + ) { + let ethereum_bridge::params::GenesisConfig { + min_confirmations, + contracts: + ethereum_bridge::params::Contracts { + native_erc20, + bridge, + governance, + }, + } = config; + self.storage.min_confirmations = Some(min_confirmations); + self.storage.native_erc20 = Some(native_erc20); + self.storage.bridge_contract = Some(bridge); + self.storage.governance_contract = Some(governance); } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ae8aade9b4..cfed6feea3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,6 +1121,10 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 5085663c41..0bf05c1cbe 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,7 +5,13 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` +//! - `tx_queue` +//! - `min_confirmations`: Minimum number of confirmations needed for the +//! Ethereum bridge to consider an event final. +//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents +//! this chain's native token. +//! - `bridge_contract`: The Ethereum address of the bridge contract. +//! - `governance_contract`: The Ethereum address of the governance contract. //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -32,11 +38,15 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; +use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -300,6 +310,61 @@ impl DB for RocksDB { return Ok(None); } }; + let min_confirmations: Option = match self + .0 + .get("min_confirmations") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the minimum confirmations from the DB" + ); + return Ok(None); + } + }; + let native_erc20: Option = match self + .0 + .get("native_erc20") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum address for wrapped Nam from \ + the DB" + ); + return Ok(None); + } + }; + let bridge_contract: Option = match self + .0 + .get("bridge_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum bridge contract address from \ + the DB" + ); + return Ok(None); + } + }; + let governance_contract: Option = match self + .0 + .get("governance_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum governance contract from the \ + DB" + ); + return Ok(None); + } + }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -402,6 +467,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -424,6 +493,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -452,6 +525,10 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); + batch.put("min_confirmations", types::encode(&min_confirmations)); + batch.put("native_erc20", types::encode(&native_erc20)); + batch.put("bridge_contract", types::encode(&bridge_contract)); + batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -994,6 +1071,10 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs new file mode 100644 index 0000000000..1e54207f6e --- /dev/null +++ b/shared/src/ledger/parameters/ethereum_bridge.rs @@ -0,0 +1,74 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4891818dc0..0ec554dd80 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,4 +1,5 @@ //! Protocol parameters +pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 234cad4498..29a19a9760 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,7 +12,11 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,6 +77,34 @@ impl DB for MockDB { } None => return Ok(None), }; + let min_confirmations: Option = + match self.0.borrow().get("min_confirmations") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let native_erc20: Option = + match self.0.borrow().get("native_erc20") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let bridge_contract: Option = + match self.0.borrow().get("bridge_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let governance_contract: Option = + match self.0.borrow().get("governance_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -153,6 +185,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -175,6 +211,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -186,6 +226,20 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); + self.0.borrow_mut().insert( + "min_confirmations".into(), + types::encode(&min_confirmations), + ); + self.0 + .borrow_mut() + .insert("native_erc20".into(), types::encode(&native_erc20)); + self.0 + .borrow_mut() + .insert("bridge_contract".into(), types::encode(&bridge_contract)); + self.0.borrow_mut().insert( + "goverenance_contract".into(), + types::encode(&governance_contract), + ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 805caa6348..0411375912 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,6 +17,9 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -28,6 +31,7 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -69,6 +73,16 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block storage data @@ -128,6 +142,16 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block's state to write into the database. @@ -153,6 +177,16 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// A database backend. @@ -302,6 +336,10 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } @@ -319,6 +357,10 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract: bridge, + governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -335,6 +377,10 @@ where { self.tx_queue = tx_queue; } + self.min_confirmations = min_confirmations; + self.native_erc20 = native_erc20; + self.bridge_contract = bridge; + self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -366,6 +412,10 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, + min_confirmations: self.min_confirmations, + native_erc20: self.native_erc20.clone(), + bridge_contract: self.bridge_contract.clone(), + governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -922,6 +972,10 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } } diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 5a66906a49..c6c8c061a2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -62,6 +62,8 @@ impl From for Uint { BorshDeserialize, BorshSchema, )] +#[serde(try_from = "String")] +#[serde(into = "String")] pub struct EthAddress(pub [u8; 20]); impl EthAddress { @@ -91,6 +93,20 @@ impl FromStr for EthAddress { } } +impl TryFrom for EthAddress { + type Error = eyre::Error; + + fn try_from(string: String) -> Result { + Self::from_str(string.as_ref()) + } +} + +impl From for String { + fn from(addr: EthAddress) -> Self { + addr.to_string() + } +} + /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, @@ -278,6 +294,26 @@ pub mod tests { assert!(result.is_err()); } + + /// Test that serde correct serializes EthAddress types to/from lowercase + /// hex encodings + #[test] + fn test_eth_address_serde_roundtrip() { + let addr = + EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED) + .unwrap(); + let serialized = serde_json::to_string(&addr).expect("Test failed"); + assert_eq!( + serialized, + format!( + r#""{}""#, + testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_lowercase() + ) + ); + let deserialized: EthAddress = + serde_json::from_str(&serialized).expect("Test failed"); + assert_eq!(addr, deserialized); + } } #[allow(missing_docs)] From af53c81fa52fc03b418fb1307600fd688f0ddd9a Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 18:52:47 +0200 Subject: [PATCH 1272/1995] [feat]: Refactored the eth bridge params to live under the bridge vp storage --- apps/src/lib/config/ethereum_bridge/mod.rs | 37 ++++- apps/src/lib/config/ethereum_bridge/params.rs | 83 ---------- apps/src/lib/config/genesis.rs | 10 +- apps/src/lib/node/ledger/shell/init_chain.rs | 31 +--- apps/src/lib/node/ledger/shell/mod.rs | 4 - apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +--------- shared/src/ledger/eth_bridge/mod.rs | 1 + shared/src/ledger/eth_bridge/parameters.rs | 152 ++++++++++++++++++ shared/src/ledger/eth_bridge/storage/mod.rs | 51 +++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 --------- shared/src/ledger/parameters/mod.rs | 1 - shared/src/ledger/storage/mockdb.rs | 54 ------- shared/src/ledger/storage/mod.rs | 54 ------- 13 files changed, 250 insertions(+), 385 deletions(-) delete mode 100644 apps/src/lib/config/ethereum_bridge/params.rs create mode 100644 shared/src/ledger/eth_bridge/parameters.rs delete mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index ab72d58b42..d4606b0652 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,2 +1,37 @@ pub mod ledger; -pub mod params; + +#[cfg(test)] +mod tests { + use eyre::Result; + use namada::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use namada::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs deleted file mode 100644 index d76b6964fd..0000000000 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Blockchain-level parameters for the configuration of the Ethereum bridge. -use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; -use namada::types::ethereum_events::EthAddress; -use serde::{Deserialize, Serialize}; - -/// Represents chain parameters for the Ethereum bridge. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct GenesisConfig { - /// Minimum number of confirmations needed to trust an Ethereum branch. - /// This must be at least one. - pub min_confirmations: MinimumConfirmations, - /// The addresses of the Ethereum contracts that need to be directly known - /// by validators. - pub contracts: Contracts, -} - -/// Represents all the Ethereum contracts that need to be directly know about by -/// validators. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct Contracts { - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: EthAddress, - /// The Ethereum address of the bridge contract. - pub bridge: UpgradeableContract, - /// The Ethereum address of the governance contract. - pub governance: UpgradeableContract, -} - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::parameters::ethereum_bridge::ContractVersion; - - use super::*; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = GenesisConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: GenesisConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 792d39bac1..717d4bf01f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,6 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -17,8 +18,6 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; -use crate::config::ethereum_bridge; - /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -42,7 +41,7 @@ pub mod genesis_config { use thiserror::Error; use super::{ - ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; @@ -122,8 +121,7 @@ pub mod genesis_config { // Governance parameters pub gov_params: GovernanceParamsConfig, // Ethereum bridge config - pub ethereum_bridge_params: - Option, + pub ethereum_bridge_params: Option, // Wasm definitions pub wasm: HashMap, } @@ -641,7 +639,7 @@ pub struct Genesis { pub gov_params: GovParams, pub treasury_params: TreasuryParams, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index adcc1aae6a..394d151855 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -14,7 +14,6 @@ use namada::types::token; use sha2::{Digest, Sha256}; use super::*; -use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -70,6 +69,11 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); + genesis.treasury_params.init_storage(&mut self.storage); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + config.init_storage(&mut self.storage); + } // Depends on parameters being initialized self.storage @@ -97,10 +101,7 @@ where genesis.token_accounts, &mut vp_code_cache, ); - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - self.configure_ethereuem_bridge(config); - } + // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set @@ -349,26 +350,6 @@ where } response } - - /// Set the parameters for the Ethereum bridge - fn configure_ethereuem_bridge( - &mut self, - config: ethereum_bridge::params::GenesisConfig, - ) { - let ethereum_bridge::params::GenesisConfig { - min_confirmations, - contracts: - ethereum_bridge::params::Contracts { - native_erc20, - bridge, - governance, - }, - } = config; - self.storage.min_confirmations = Some(min_confirmations); - self.storage.native_erc20 = Some(native_erc20); - self.storage.bridge_contract = Some(bridge); - self.storage.governance_contract = Some(governance); - } } trait HashMapExt diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cfed6feea3..ae8aade9b4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,10 +1121,6 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 0bf05c1cbe..5085663c41 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,13 +5,7 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` -//! - `min_confirmations`: Minimum number of confirmations needed for the -//! Ethereum bridge to consider an event final. -//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents -//! this chain's native token. -//! - `bridge_contract`: The Ethereum address of the bridge contract. -//! - `governance_contract`: The Ethereum address of the governance contract. +//! - `tx_queue` //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -38,15 +32,11 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; -use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -310,61 +300,6 @@ impl DB for RocksDB { return Ok(None); } }; - let min_confirmations: Option = match self - .0 - .get("min_confirmations") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the minimum confirmations from the DB" - ); - return Ok(None); - } - }; - let native_erc20: Option = match self - .0 - .get("native_erc20") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum address for wrapped Nam from \ - the DB" - ); - return Ok(None); - } - }; - let bridge_contract: Option = match self - .0 - .get("bridge_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum bridge contract address from \ - the DB" - ); - return Ok(None); - } - }; - let governance_contract: Option = match self - .0 - .get("governance_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum governance contract from the \ - DB" - ); - return Ok(None); - } - }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -467,10 +402,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -493,10 +424,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -525,10 +452,6 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); - batch.put("min_confirmations", types::encode(&min_confirmations)); - batch.put("native_erc20", types::encode(&native_erc20)); - batch.put("bridge_contract", types::encode(&bridge_contract)); - batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -1071,10 +994,6 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index 047bcda91e..a740fa16a2 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,5 +1,6 @@ //! Validity predicate and storage keys for the Ethereum bridge account pub mod bridge_pool_vp; +pub mod parameters; pub mod storage; pub mod vp; diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs new file mode 100644 index 0000000000..2d22730a8a --- /dev/null +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -0,0 +1,152 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::storage::types::encode; +use crate::ledger::storage::{self, Storage}; +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} + +/// Represents all the Ethereum contracts that need to be directly know about by +/// validators. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct Contracts { + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: EthAddress, + /// The Ethereum address of the bridge contract. + pub bridge: UpgradeableContract, + /// The Ethereum address of the governance contract. + pub governance: UpgradeableContract, +} + +/// Represents chain parameters for the Ethereum bridge. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct EthereumBridgeConfig { + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +impl EthereumBridgeConfig { + /// Initialize the Ethereum bridge parameters in storage + pub fn init_storage(&self, storage: &mut Storage) + where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::traits::StorageHasher, + { + let Self { + min_confirmations, + contracts: + Contracts { + native_erc20, + bridge, + governance, + }, + } = self; + let min_confirmations_key = bridge_storage::min_confirmations_key(); + let native_erc20_key = bridge_storage::native_erc20_key(); + let bridge_contract_key = bridge_storage::bridge_contract_key(); + let governance_contract_key = bridge_storage::governance_contract_key(); + storage + .write(&min_confirmations_key, encode(min_confirmations)) + .unwrap(); + storage + .write(&native_erc20_key, encode(native_erc20)) + .unwrap(); + storage.write(&bridge_contract_key, encode(bridge)).unwrap(); + storage + .write(&governance_contract_key, encode(governance)) + .unwrap(); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 595f403034..b117f4b42d 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -4,7 +4,16 @@ pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; + +/// Sub-key for storing the minimum confirmations parameter +pub const MIN_CONFIRMATIONS_SUBKEY: &str = "min_confirmations"; +/// Sub-key for storing the Ethereum address for wNam. +pub const NATIVE_ERC20_SUBKEY: &str = "native_erc20"; +/// Sub-lkey for storing the Ethereum address of the bridge contract. +pub const BRIDGE_CONTRACT_SUBKEY: &str = "bridge_contract_address"; +/// Sub-key for storing the Ethereum address of the governance contract. +pub const GOVERNANCE_CONTRACT_SUBKEY: &str = "governance_contract_address"; /// Key prefix for the storage subspace pub fn prefix() -> Key { @@ -16,6 +25,46 @@ pub fn is_eth_bridge_key(key: &Key) -> bool { matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) } +/// Storage key for the minimum confirmations parameter. +pub fn min_confirmations_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(MIN_CONFIRMATIONS_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of wNam. +pub fn native_erc20_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(NATIVE_ERC20_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the bridge contract. +pub fn bridge_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(BRIDGE_CONTRACT_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the governance contract. +pub fn governance_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(GOVERNANCE_CONTRACT_SUBKEY.into()), + ], + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs deleted file mode 100644 index 1e54207f6e..0000000000 --- a/shared/src/ledger/parameters/ethereum_bridge.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Parameters for configuring the Ethereum bridge -use std::num::NonZeroU64; - -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; - -use crate::types::ethereum_events::EthAddress; - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: EthAddress, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 0ec554dd80..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,5 +1,4 @@ //! Protocol parameters -pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 29a19a9760..234cad4498 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,11 +12,7 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -77,34 +73,6 @@ impl DB for MockDB { } None => return Ok(None), }; - let min_confirmations: Option = - match self.0.borrow().get("min_confirmations") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let native_erc20: Option = - match self.0.borrow().get("native_erc20") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let bridge_contract: Option = - match self.0.borrow().get("bridge_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let governance_contract: Option = - match self.0.borrow().get("governance_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -185,10 +153,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -211,10 +175,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -226,20 +186,6 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); - self.0.borrow_mut().insert( - "min_confirmations".into(), - types::encode(&min_confirmations), - ); - self.0 - .borrow_mut() - .insert("native_erc20".into(), types::encode(&native_erc20)); - self.0 - .borrow_mut() - .insert("bridge_contract".into(), types::encode(&bridge_contract)); - self.0.borrow_mut().insert( - "goverenance_contract".into(), - types::encode(&governance_contract), - ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0411375912..805caa6348 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,9 +17,6 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -31,7 +28,6 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,16 +69,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block storage data @@ -142,16 +128,6 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block's state to write into the database. @@ -177,16 +153,6 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// A database backend. @@ -336,10 +302,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } @@ -357,10 +319,6 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract: bridge, - governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -377,10 +335,6 @@ where { self.tx_queue = tx_queue; } - self.min_confirmations = min_confirmations; - self.native_erc20 = native_erc20; - self.bridge_contract = bridge; - self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -412,10 +366,6 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, - min_confirmations: self.min_confirmations, - native_erc20: self.native_erc20.clone(), - bridge_contract: self.bridge_contract.clone(), - governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -972,10 +922,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } } From cc7faae4d762458d626af1c47110eebeb0c0cb2d Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:28:31 +0100 Subject: [PATCH 1273/1995] [feat]: Initialized VP substorage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 27 +++++++++++++++-- shared/src/ledger/eth_bridge/parameters.rs | 12 ++++++-- shared/src/ledger/eth_bridge/vp/mod.rs | 29 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index a765a423af..bd855408d5 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -11,7 +11,7 @@ //! added to the pool and gas fees are submitted appropriately. use std::collections::BTreeSet; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ @@ -19,7 +19,7 @@ use crate::ledger::eth_bridge::storage::bridge_pool::{ }; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -38,6 +38,29 @@ enum SignedAmount { Negative(Amount), } +/// Initialize the storage owned by the Bridge Pool VP. +/// +/// This means that the amount of escrowed gas fees is +/// initialized to 0. +pub fn init_storage(storage: &mut Storage) +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Bridge pool VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct BridgePoolVp<'ctx, D, H, CA> where diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 2d22730a8a..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -4,7 +4,8 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::eth_bridge; +use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::ethereum_events::EthAddress; @@ -119,7 +120,10 @@ pub struct EthereumBridgeConfig { } impl EthereumBridgeConfig { - /// Initialize the Ethereum bridge parameters in storage + /// Initialize the Ethereum bridge parameters in storage. + /// + /// If these parameters are initialized, the storage subspaces + /// for the Ethereum bridge VPs are also initialized. pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -148,5 +152,9 @@ impl EthereumBridgeConfig { storage .write(&governance_contract_key, encode(governance)) .unwrap(); + // Initialize the storage for the Ethereum Bridge VP. + eth_bridge::vp::init_storage(storage); + // Initialize the storage for the Bridge Pool VP. + bridge_pool_vp::init_storage(storage); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0e62f7270f..b9bb4237cc 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,11 +12,35 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; + +/// Initialize the storage owned by the Ethereum Bridge VP. +/// +/// This means that the amount of escrowed Nam is +/// initialized to 0. +pub fn init_storage(storage: &mut ledger_storage::Storage) + where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &super::ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where From c3d5705a32d99ec385c29ef6da1303e2b6b23d31 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:12:44 +0100 Subject: [PATCH 1274/1995] [fix]: Linting --- shared/src/ledger/eth_bridge/vp/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index b9bb4237cc..1e72b8d49b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshDeserialize; +use borsh::BorshSerialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,15 +17,14 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; - /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) - where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -36,8 +35,8 @@ pub fn init_storage(storage: &mut ledger_storage::Storage) .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ - fail.", + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", ); } From a53897c6a6e42e3c6ee46b98ccea6db6217a11c7 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 10:47:50 +0100 Subject: [PATCH 1275/1995] [fix]: Moved a test to a move appropriate location --- Cargo.lock | 1 + apps/src/lib/config/ethereum_bridge/mod.rs | 36 --------------------- shared/Cargo.toml | 1 + shared/src/ledger/eth_bridge/parameters.rs | 37 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e081e3e4bd..0df7db2ceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3364,6 +3364,7 @@ dependencies = [ "test-log", "thiserror", "tiny-keccak", + "toml", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index d4606b0652..370e1150a2 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,37 +1 @@ pub mod ledger; - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::eth_bridge::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, - }; - use namada::types::ethereum_events::EthAddress; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..3ba24a3bf0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -136,6 +136,7 @@ pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} +toml = "0.5.8" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} [build-dependencies] diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index f16292223b..46ff52c0a3 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,3 +158,40 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use crate::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use crate::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} From 0638fb06d2a4df1d6b2e7f8eaeeb60cbaf176625 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 14:14:45 +0100 Subject: [PATCH 1276/1995] [fix] Upgrading to v0.8 --- apps/src/lib/config/genesis.rs | 3 --- apps/src/lib/node/ledger/shell/init_chain.rs | 21 ++++++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 717d4bf01f..e8794545ac 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -588,7 +588,6 @@ pub mod genesis_config { parameters, pos_params, gov_params, - treasury_params, ethereum_bridge_params: config.ethereum_bridge_params, }; genesis.init(); @@ -637,7 +636,6 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub treasury_params: TreasuryParams, // Ethereum bridge config pub ethereum_bridge_params: Option, } @@ -876,7 +874,6 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - treasury_params: TreasuryParams::default(), ethereum_bridge_params: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 394d151855..91ab6e7ee2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,11 +2,10 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::pos::PosParams; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::{ibc, pos}; -use namada::ledger::parameters::Parameters; -use namada::ledger::pos::PosParams; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; @@ -69,7 +68,6 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.treasury_params.init_storage(&mut self.storage); // configure the Ethereum bridge if the configuration is set. if let Some(config) = genesis.ethereum_bridge_params { config.init_storage(&mut self.storage); @@ -91,7 +89,7 @@ where self.initialize_established_accounts( genesis.established_accounts, &mut vp_code_cache, - ); + )?; // Initialize genesis implicit self.initialize_implicit_accounts(genesis.implicit_accounts); @@ -105,11 +103,12 @@ where // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set - Ok(self.set_initial_validators( - genesis.validators, - &genesis.parameters, - &genesis.pos_params, - )) + Ok( + self.set_initial_validators( + genesis.validators, + &genesis.pos_params, + ), + ) } /// Initialize genesis established accounts @@ -117,7 +116,7 @@ where &mut self, accounts: Vec, vp_code_cache: &mut HashMap>, - ) { + ) -> Result<()> { for genesis::EstablishedAccount { address, vp_code_path, @@ -168,6 +167,7 @@ where self.storage.write(&key, value).unwrap(); } } + Ok(()) } /// Initialize genesis implicit accounts @@ -319,7 +319,6 @@ where fn set_initial_validators( &mut self, validators: Vec, - parameters: &Parameters, pos_params: &PosParams, ) -> response::InitChain { let mut response = response::InitChain::default(); From 77fd0c5e4cc20651a481284d63c604ddee9684a3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 27 Oct 2022 11:48:01 +0100 Subject: [PATCH 1277/1995] Send valset upd vext only once per epoch --- apps/src/lib/node/ledger/shell/queries.rs | 48 +++++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1fad92eb9d..a89d08e965 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -393,17 +393,9 @@ pub(crate) trait QueriesExt { ) -> std::result::Result; /// Determines if it is possible to send a validator set update vote - /// extension at the provided [`BlockHeight`]. + /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. /// - /// This is done by checking if we are at the first block of a new epoch, - /// or if we are at block height 1 of the first epoch. - /// - /// The genesis block will not have vote extensions, - /// therefore it is a special case, which we account for - /// by checking if the block height is 1. Otherwise, - /// validator set update votes will always extend - /// Tendermint's PreCommit phase of the first block of - /// an epoch. + /// This is done by checking if we are at the second block of a new epoch. fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. @@ -603,6 +595,8 @@ where }) } + // TODO: fix this method; should only be able to send a validator + // set update at the second block of an epoch #[cfg(feature = "abcipp")] fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { let (check_prev_heights, height) = match can_send { @@ -636,9 +630,37 @@ where } #[cfg(not(feature = "abcipp"))] - #[inline(always)] - fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { - true + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { + // when checking vote extensions in Prepare + // and ProcessProposal, we simply return true + if matches!( + can_send, + SendValsetUpd::AtPrevHeight | SendValsetUpd::AtFixedHeight(_) + ) { + return true; + } + + let current_decision_height = self.get_current_decision_height(); + + // NOTE: the first stored height in `fst_block_heights_of_each_epoch` + // is 0, because of a bug (should be 1), so this code needs to + // handle that case + // + // we can remove this check once that's fixed + if current_decision_height == BlockHeight(2) { + return true; + } + + let fst_heights_of_each_epoch = + self.block.pred_epochs.first_block_heights(); + + fst_heights_of_each_epoch + .last() + .map(|&h| { + let second_height_of_epoch = h + 1; + current_decision_height == second_height_of_epoch + }) + .unwrap_or(false) } #[inline] From cda345002d159e44c2a57cc57972c5b8374c4f48 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 27 Oct 2022 13:08:22 +0100 Subject: [PATCH 1278/1995] Remove AtFixedHeight valset upd vext checks --- apps/src/lib/node/ledger/shell/queries.rs | 46 +++-------------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a89d08e965..2422d9e9af 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -599,10 +599,9 @@ where // set update at the second block of an epoch #[cfg(feature = "abcipp")] fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { - let (check_prev_heights, height) = match can_send { - SendValsetUpd::Now => (false, self.get_current_decision_height()), - SendValsetUpd::AtPrevHeight => (false, self.last_height), - SendValsetUpd::AtFixedHeight(h) => (true, h), + let height = match can_send { + SendValsetUpd::Now => self.get_current_decision_height(), + SendValsetUpd::AtPrevHeight => self.last_height, }; // handle genesis block corner case @@ -613,30 +612,19 @@ where let fst_heights_of_each_epoch = self.block.pred_epochs.first_block_heights(); - // tentatively check if the last stored height + // check if the last stored height // is the one we are looking for - if fst_heights_of_each_epoch + fst_heights_of_each_epoch .last() .map(|&h| h == height) .unwrap_or(false) - { - return true; - } - - // the values in `fst_block_heights_of_each_epoch` are stored in - // ascending order, so we can just do a binary search over them - check_prev_heights - && fst_heights_of_each_epoch.binary_search(&height).is_ok() } #[cfg(not(feature = "abcipp"))] fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { // when checking vote extensions in Prepare // and ProcessProposal, we simply return true - if matches!( - can_send, - SendValsetUpd::AtPrevHeight | SendValsetUpd::AtFixedHeight(_) - ) { + if matches!(can_send, SendValsetUpd::AtPrevHeight) { return true; } @@ -743,10 +731,6 @@ pub enum SendValsetUpd { /// Check if it is possible to send a validator set update /// vote extension at the previous block height. AtPrevHeight, - /// Check if it is possible to send a validator set update - /// vote extension at any given block height. - #[allow(dead_code)] - AtFixedHeight(BlockHeight), } #[cfg(test)] @@ -834,24 +818,6 @@ mod test_queries { shell.storage.next_epoch_min_start_time = time; } } - - // test `SendValsetUpd::AtFixedHeight` - for (curr_epoch, curr_block_height, can_send) in - epoch_assertions.iter().copied() - { - assert_eq!( - shell.storage.get_epoch(curr_block_height.into()), - Some(Epoch(curr_epoch)) - ); - assert_eq!( - shell.storage.can_send_validator_set_update( - SendValsetUpd::AtFixedHeight( - curr_block_height.into() - ) - ), - extract(can_send) - ); - } } }; } From 02b8e1134f7f94402c6a2cc04a289664fee59146 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:02:24 +0000 Subject: [PATCH 1279/1995] Add test-unit-debug to Makefile --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index b2d0393df9..bf7bb44c10 100644 --- a/Makefile +++ b/Makefile @@ -133,6 +133,13 @@ test-unit: --skip e2e \ -Z unstable-options --report-time +test-unit-debug: + $(debug-cargo) test \ + $(TEST_FILTER) -- \ + --skip e2e \ + --nocapture \ + -Z unstable-options --report-time + test-wasm: make -C $(wasms) test From 8249cb131a09eaaddcb2aa4cb7af6eaa74ef1e95 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:15:10 +0000 Subject: [PATCH 1280/1995] Make sure we send a valset upd at the second block of each epoch --- apps/src/lib/node/ledger/shell/queries.rs | 169 ++++++++-------------- 1 file changed, 64 insertions(+), 105 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 2422d9e9af..ee46cea735 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -635,8 +635,10 @@ where // handle that case // // we can remove this check once that's fixed - if current_decision_height == BlockHeight(2) { - return true; + match current_decision_height { + BlockHeight(1) => return false, + BlockHeight(2) => return true, + _ => (), } let fst_heights_of_each_epoch = @@ -744,7 +746,6 @@ mod test_queries { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as /// expected. #[test] - #[ignore] // TODO: we should fix this test to cope with epoch changes only // happening at the first block of a new epoch. an erroneous change // was introduced to the ledger, that updated the epoch correctly @@ -760,20 +761,9 @@ mod test_queries { let epoch_assertions = $epoch_assertions; - // TODO: switch to `Result::into_ok_or_err` when it becomes - // stable - const fn extract( - can_send: ::std::result::Result, - ) -> bool { - match can_send { - Ok(x) => x, - Err(x) => x, - } - } - // test `SendValsetUpd::Now` and `SendValsetUpd::AtPrevHeight` - for (idx, (curr_epoch, curr_block_height, can_send)) in - epoch_assertions.iter().copied().enumerate() + for (curr_epoch, curr_block_height, can_send) in + epoch_assertions { shell.storage.last_height = BlockHeight(curr_block_height - 1); @@ -789,34 +779,35 @@ mod test_queries { shell .storage .can_send_validator_set_update(SendValsetUpd::Now), - extract(can_send) + can_send, ); - if let Some((epoch, height, can_send)) = - epoch_assertions.get(idx.wrapping_sub(1)).copied() - { - assert_eq!( - shell.storage.get_epoch(height.into()), - Some(Epoch(epoch)) - ); - assert_eq!( - shell.storage.can_send_validator_set_update( - SendValsetUpd::AtPrevHeight - ), - extract(can_send) - ); - } - if epoch_assertions - .get(idx + 1) - .map(|&(_, _, change_epoch)| change_epoch.is_ok()) - .unwrap_or(false) - { - let time = namada::types::time::DateTimeUtc::now(); - let mut req = FinalizeBlock::default(); - req.header.time = time; - shell.finalize_block(req).expect("Test failed"); - shell.commit(); - shell.storage.next_epoch_min_start_time = time; - } + // TODO(feature = "abcipp"): test + // `SendValsetUpd::AtPrevHeight`; `idx` is the value + // of the current index being iterated over + // the array `epoch_assertions` + // + // ```ignore + // if let Some((epoch, height, can_send)) = + // epoch_assertions.get(_idx.wrapping_sub(1)).copied() + // { + // assert_eq!( + // shell.storage.get_epoch(height.into()), + // Some(Epoch(epoch)) + // ); + // assert_eq!( + // shell.storage.can_send_validator_set_update( + // SendValsetUpd::AtPrevHeight + // ), + // can_send, + // ); + // } + // ``` + let time = namada::types::time::DateTimeUtc::now(); + let mut req = FinalizeBlock::default(); + req.header.time = time; + shell.finalize_block(req).expect("Test failed"); + shell.commit(); + shell.storage.next_epoch_min_start_time = time; } } }; @@ -824,75 +815,43 @@ mod test_queries { #[cfg(feature = "abcipp")] test_can_send_validator_set_update! { - epoch_assertions: [ - // (current epoch, current block height, can send valset upd / Ok = change epoch) - (0, 1, Ok(true)), - (0, 2, Err(false)), - (0, 3, Err(false)), - (0, 4, Err(false)), - (0, 5, Err(false)), - (0, 6, Err(false)), - (0, 7, Err(false)), - (0, 8, Err(false)), - (0, 9, Err(false)), - (0, 10, Err(false)), - (0, 11, Err(false)), - // we will change epoch here - (1, 12, Ok(true)), - (1, 13, Err(false)), - (1, 14, Err(false)), - (1, 15, Err(false)), - (1, 16, Err(false)), - (1, 17, Err(false)), - (1, 18, Err(false)), - (1, 19, Err(false)), - (1, 20, Err(false)), - (1, 21, Err(false)), - (1, 22, Err(false)), - (1, 23, Err(false)), - (1, 24, Err(false)), - // we will change epoch here - (2, 25, Ok(true)), - (2, 26, Err(false)), - (2, 27, Err(false)), - (2, 28, Err(false)), - ], + epoch_assertions: [] } #[cfg(not(feature = "abcipp"))] test_can_send_validator_set_update! { epoch_assertions: [ - // (current epoch, current block height, can send valset upd / Ok = change epoch) - (0, 1, Ok(true)), - (0, 2, Err(true)), - (0, 3, Err(true)), - (0, 4, Err(true)), - (0, 5, Err(true)), - (0, 6, Err(true)), - (0, 7, Err(true)), - (0, 8, Err(true)), - (0, 9, Err(true)), - (0, 10, Err(true)), - (0, 11, Err(true)), + // (current epoch, current block height, can send valset upd) + (0, 1, false), + (0, 2, true), + (0, 3, false), + (0, 4, false), + (0, 5, false), + (0, 6, false), + (0, 7, false), + (0, 8, false), + (0, 9, false), // we will change epoch here - (1, 12, Ok(true)), - (1, 13, Err(true)), - (1, 14, Err(true)), - (1, 15, Err(true)), - (1, 16, Err(true)), - (1, 17, Err(true)), - (1, 18, Err(true)), - (1, 19, Err(true)), - (1, 20, Err(true)), - (1, 21, Err(true)), - (1, 22, Err(true)), - (1, 23, Err(true)), - (1, 24, Err(true)), + (0, 10, false), + (1, 11, true), + (1, 12, false), + (1, 13, false), + (1, 14, false), + (1, 15, false), + (1, 16, false), + (1, 17, false), + (1, 18, false), + (1, 19, false), // we will change epoch here - (2, 25, Ok(true)), - (2, 26, Err(true)), - (2, 27, Err(true)), - (2, 28, Err(true)), + (1, 20, false), + (2, 21, true), + (2, 22, false), + (2, 23, false), + (2, 24, false), + (2, 25, false), + (2, 26, false), + (2, 27, false), + (2, 28, false), ], } } From 61d1de3207a62260457a7f71bc2a7f00ea62ca35 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:16:15 +0000 Subject: [PATCH 1281/1995] Add a TODO --- apps/src/lib/node/ledger/shell/queries.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index ee46cea735..8eaa7f0393 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -815,6 +815,7 @@ mod test_queries { #[cfg(feature = "abcipp")] test_can_send_validator_set_update! { + // TODO(feature = "abcipp"): add some epoch assertions epoch_assertions: [] } From 7a7016152b47438ee4320cf0b25933bd69904de9 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:46:31 +0200 Subject: [PATCH 1282/1995] [feat]: Added ability to escrow Nam to the bridge pool VP when wanting to mint wNam on Ethereum --- apps/src/lib/config/ethereum_bridge/params.rs | 7 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 303 +++++++++++++++--- shared/src/types/address.rs | 11 + shared/src/types/ethereum_events.rs | 3 + 4 files changed, 283 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 857230a6c4..0ab56b0b47 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,7 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use std::num::NonZeroU64; +use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address. @@ -42,7 +43,7 @@ pub struct GenesisConfig { pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. - pub native_erc20: Address, + pub native_erc20: EthAddress, /// The Ethereum address of the bridge contract. pub bridge: UpgradeableContract, /// The Ethereum address of the governance contract. @@ -87,9 +88,7 @@ mod tests { let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { - native_erc20: Address( - "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), - ), + native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { address: Address( "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index e696051c72..83f760e251 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -8,7 +8,8 @@ //! //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is -//! added to the pool and gas fees are submitted appropriately. +//! added to the pool and gas fees are submitted appropriately +//! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; use borsh::BorshDeserialize; @@ -23,7 +24,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{xan, Address, InternalAddress}; +use crate::types::address::{wnam, xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; @@ -79,6 +80,44 @@ where Some(SignedAmount::Positive(after - before)) } } + + /// Check that the correct amount of Nam was sent + /// from the correct account into escrow + fn check_nam_escrowed( + &self, + payer_account: &Address, + escrow_account: &Address, + expected_debit: Amount, + expected_credit: Amount, + ) -> bool { + // check that the correct amount was deducted from the fee payer + if let Some(SignedAmount::Negative(amount)) = + self.account_balance_delta(payer_account) + { + if amount != expected_debit { + return false; + } + } else { + tracing::debug!("The account {} was not debited.", payer_account); + return false; + } + // check that the correct amount was credited to escrow + if let Some(SignedAmount::Positive(amount)) = + self.account_balance_delta(escrow_account) + { + if amount != expected_credit { + return false; + } + } else { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + return false; + } + true + } } /// Check if a delta matches the delta given by a transfer @@ -169,36 +208,41 @@ where return Ok(false); } - // check that gas fees were put into escrow - - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(&transfer.gas_fee.payer) - { - if amount != transfer.gas_fee.amount { - return Ok(false); - } + // if we are going to mint wNam on Ethereum, the appropriate + // amount of Nam must be escrowed in the Ethereum bridge VP's storage. + // TODO: We should look this address up from storage + if transfer.transfer.asset == wnam() { + // check that correct amount of Nam was put into escrow. + return if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); + Ok(true) + }; } else { - tracing::debug!("The gas fee payers account was not debited."); - return Ok(false); - } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(&BRIDGE_POOL_ADDRESS) - { - if amount != transfer.gas_fee.amount { + // check that the correct amounnt of gas fees were escrowed + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount, + transfer.gas_fee.amount, + ) { return Ok(false); } - } else { - tracing::debug!( - "The Ethereum bridge pool's gas escrow was not credited." - ); - return Ok(false); } - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer {:?}.", - transfer - ); // check that the assets to be transferred were escrowed let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); @@ -230,6 +274,10 @@ where return Ok(false); } + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); Ok(true) } } @@ -898,20 +946,201 @@ mod test_bridge_pool_vp { keys_changed.insert( wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), ); + let verifiers = BTreeSet::default(); + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + }; + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } + + /// Test that we can escrow Nam if we + /// want to mint wNam on Ethereum. + #[test] + fn test_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); - // inform the vp that the merkle root changed - let keys_changed = BTreeSet::default(); let verifiers = BTreeSet::default(); + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + }; + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(res); + } + + /// Test that we can rejecte a transfer that + /// mints wNam if we don't escrow the correct + /// amount of Nam. + #[test] + fn test_reject_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx( - &tx, - &storage, - &write_log, - &keys_changed, - &verifiers, - ), + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), }; let to_sign = transfer.try_to_vec().expect("Test failed"); diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 74ab4f53fe..6c525ca819 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::key::PublicKeyHash; @@ -533,6 +534,16 @@ pub fn kartoffel() -> Address { Address::decode("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90").expect("The token address decoding shouldn't fail") } +/// Temporary helper for testing +pub const fn wnam() -> EthAddress { + // TODO: Replace this with the real wNam ERC20 address once it exists + // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" + EthAddress([ + 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, + 190, 239, 222, 173, 190, 239, + ]) +} + /// Temporary helper for testing, a hash map of tokens addresses with their /// informal currency codes. pub fn tokens() -> HashMap { diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..5a66906a49 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use eyre::{eyre, Context}; +use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -55,6 +56,8 @@ impl From for Uint { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From 8a95b72d9d4ea76887c5de902e472ec0963c48c4 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:03:10 +0100 Subject: [PATCH 1283/1995] [fix]: Fixed bugs in escrowing Nam when minting wNam and checking balance deltas. Covered with a test --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 208 +++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 83f760e251..02440f0cdd 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -90,33 +90,47 @@ where expected_debit: Amount, expected_credit: Amount, ) -> bool { - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(payer_account) - { - if amount != expected_debit { - return false; - } - } else { - tracing::debug!("The account {} was not debited.", payer_account); + let debited = self.account_balance_delta(payer_account); + let credited = self.account_balance_delta(escrow_account); + if debited.is_none() && credited.is_none() { return false; } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(escrow_account) - { - if amount != expected_credit { - return false; + + match (debited, credited) { + ( + Some(SignedAmount::Negative(debit)), + Some(SignedAmount::Positive(credit)), + ) => debit == expected_debit && credit == expected_credit, + (Some(SignedAmount::Positive(_)), _) => { + tracing::debug!( + "The account {} was not debited.", + payer_account + ); + false + } + (_, Some(SignedAmount::Negative(_))) => { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + false + } + (None, _) => { + tracing::debug!( + "Could not calculate the balance delta for {}", + payer_account + ); + false + } + (_, None) => { + tracing::debug!( + "Could not calculate the balance delta for the Ethereum \ + bridge pool" + ); + false } - } else { - tracing::debug!( - "The Ethereum bridge pool's escrow was not credited from \ - account {}.", - payer_account - ); - return false; } - true } } @@ -213,15 +227,36 @@ where // TODO: We should look this address up from storage if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if !self.check_nam_escrowed( + return if transfer.gas_fee.payer == transfer.transfer.sender { + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer \ + {:?}.", + transfer + ); + Ok(true) + } + } else if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, transfer.gas_fee.amount, ) || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, transfer.transfer.amount, ) { Ok(false) @@ -233,7 +268,7 @@ where Ok(true) }; } else { - // check that the correct amounnt of gas fees were escrowed + // check that the correct amount of gas fees were escrowed if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, @@ -336,6 +371,12 @@ mod test_bridge_pool_vp { .expect("The token address decoding shouldn't fail") } + /// A sampled established address for tests + pub fn established_address_1() -> Address { + Address::decode("atest1v4ehgw36g56ngwpk8ppnzsf4xqeyvsf3xq6nxde5gseyys3nxgenvvfex5cnyd2rx9zrzwfctgx7sp") + .expect("The token address decoding shouldn't fail") + } + fn bertha_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -1157,4 +1198,117 @@ mod test_bridge_pool_vp { .expect("Test failed"); assert!(!res); } + + /// Test that we check escrowing Nam correctly when minting wNam + /// and the gas payer account is different from the transferring + /// account. + #[test] + fn test_mint_wnam_separate_gas_payer() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + // initialize the gas payers account + let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: established_address_1(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } } From 8c74e5aae6437f789b5f712469c5a4ca68fc5b87 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:33:25 +0100 Subject: [PATCH 1284/1995] [feat]: The ethereum bridge vp should allow nam to be escrowed in its xan account --- shared/src/ledger/eth_bridge/vp/mod.rs | 62 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0195e1ac20..5f79a42996 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,9 +12,9 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; /// Validity predicate for the Ethereum bridge @@ -27,6 +28,56 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } +impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// If the bridge's escrow key was changed, we check + /// that the balance increased and that the bridge pool + /// VP has been triggered. The bridge pool VP will carry + /// out the rest of the checks. + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_pre: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the Ethereum bridge VP's balance from \ + storage" + ); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; + }; + + // The amount escrowed should increase. + if escrow_pre < escrow_post { + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + } else { + tracing::info!( + "A normal tx cannot decrease the amount of Nam escrowed in \ + the Ethereum bridge" + ); + false + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -50,6 +101,7 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount + /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -67,6 +119,12 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); + + // first check if Nam is being escrowed + if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { + return Ok(self.check_escrow(verifiers)); + } + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), From 4f55715641c6f0e2b5f39e31633f1d420c1a74e0 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:11:19 +0100 Subject: [PATCH 1285/1995] [fix]: Formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 02440f0cdd..66eeb37c5c 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1216,11 +1216,14 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); // initialize the gas payers account - let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + let gas_payer_balance_key = + balance_key(&xan(), &established_address_1()); write_log .write( &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + Amount::from(BERTHA_WEALTH) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); write_log.commit_tx(); @@ -1303,8 +1306,8 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) From 9a1d3cc2cdb6e58f9bec7c4403bac116329ed7fd Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 11:56:09 +0100 Subject: [PATCH 1286/1995] [fix]: Added some errors --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 39 ++++++----------- shared/src/ledger/eth_bridge/vp/mod.rs | 43 +++++++++++-------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 66eeb37c5c..322e63b1ea 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -89,24 +89,21 @@ where escrow_account: &Address, expected_debit: Amount, expected_credit: Amount, - ) -> bool { + ) -> Result { let debited = self.account_balance_delta(payer_account); let credited = self.account_balance_delta(escrow_account); - if debited.is_none() && credited.is_none() { - return false; - } match (debited, credited) { ( Some(SignedAmount::Negative(debit)), Some(SignedAmount::Positive(credit)), - ) => debit == expected_debit && credit == expected_credit, + ) => Ok(debit == expected_debit && credit == expected_credit), (Some(SignedAmount::Positive(_)), _) => { tracing::debug!( "The account {} was not debited.", payer_account ); - false + Ok(false) } (_, Some(SignedAmount::Negative(_))) => { tracing::debug!( @@ -114,22 +111,12 @@ where account {}.", payer_account ); - false - } - (None, _) => { - tracing::debug!( - "Could not calculate the balance delta for {}", - payer_account - ); - false - } - (_, None) => { - tracing::debug!( - "Could not calculate the balance delta for the Ethereum \ - bridge pool" - ); - false + Ok(false) } + (None, _) | (_, None) => Err(Error(eyre!( + "Could not calculate the balance delta for {}", + payer_account + ))), } } } @@ -233,12 +220,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount + transfer.transfer.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.gas_fee.amount + transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -253,12 +240,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -274,7 +261,7 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) { + )? { return Ok(false); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 5f79a42996..2d9f40ca3b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -38,42 +38,47 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + fn check_escrow( + &self, + verifiers: &BTreeSet
, + ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| Error(eyre!("Couldn't deserialize a balance from storage")), + )? } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return false; - }; - let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's balance \ - after applying tx" - ); - return false; + return Ok(false); }; + let escrow_post: Amount = + if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + BorshDeserialize::try_from_slice(bytes.as_slice()).expect( + "Deserializing the balance of the Ethereum bridge VP from \ + storage shouldn't fail", + ) + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's \ + balance after applying tx" + ); + return Ok(false); + }; // The amount escrowed should increase. if escrow_pre < escrow_post { - verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - false + Ok(false) } } } @@ -122,7 +127,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return Ok(self.check_escrow(verifiers)); + return self.check_escrow(verifiers); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { From 2736c0874a24a584aad313befe277c9d37b1e20a Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 1 Nov 2022 11:15:38 +0100 Subject: [PATCH 1287/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2d9f40ca3b..d3c2b279a3 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -44,7 +44,7 @@ where ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) + self.ctx.read_bytes_pre(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( |_| Error(eyre!("Couldn't deserialize a balance from storage")), @@ -57,7 +57,7 @@ where return Ok(false); }; let escrow_post: Amount = - if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()).expect( "Deserializing the balance of the Ethereum bridge VP from \ storage shouldn't fail", From b6cdd32862b1cd4162ced2dce8f68114930cb567 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 14:27:50 +0100 Subject: [PATCH 1288/1995] [fix]: Upgrading to version v0.8 --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 35 +++++++++++++++---- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 322e63b1ea..6c02d60a60 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -977,7 +977,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -1073,7 +1079,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); @@ -1168,7 +1180,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -1280,12 +1298,17 @@ mod test_bridge_pool_vp { Amount::from(10).try_to_vec().expect("Test failed"), ) .expect("Test failed"); - + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index d3c2b279a3..34d09e03c1 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -9,7 +9,7 @@ use eyre::{eyre, Result}; use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; -use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{xan, Address, InternalAddress}; From 3dd0db02d60c143d82e8fa59d35749fa6ac9580c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:30:38 +0000 Subject: [PATCH 1289/1995] Remove can_send_validator_set_update() impl for ABCI++ --- apps/src/lib/node/ledger/shell/queries.rs | 26 ++++------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 8eaa7f0393..3115a83eb5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -595,29 +595,11 @@ where }) } - // TODO: fix this method; should only be able to send a validator - // set update at the second block of an epoch #[cfg(feature = "abcipp")] - fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { - let height = match can_send { - SendValsetUpd::Now => self.get_current_decision_height(), - SendValsetUpd::AtPrevHeight => self.last_height, - }; - - // handle genesis block corner case - if height == BlockHeight(1) { - return true; - } - - let fst_heights_of_each_epoch = - self.block.pred_epochs.first_block_heights(); - - // check if the last stored height - // is the one we are looking for - fst_heights_of_each_epoch - .last() - .map(|&h| h == height) - .unwrap_or(false) + fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { + // TODO: implement this method for ABCI++; should only be able to send + // a validator set update at the second block of an epoch + true } #[cfg(not(feature = "abcipp"))] From 1c966cf4a9befcd1459a3d1a343c2b13851246f0 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:53:49 +0200 Subject: [PATCH 1290/1995] [feat]: Changed erc20 address to an ethereum address --- apps/src/lib/config/ethereum_bridge/params.rs | 7 +++---- shared/src/types/ethereum_events.rs | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 857230a6c4..0ab56b0b47 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,6 +1,7 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. use std::num::NonZeroU64; +use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; /// Represents a configuration value for an Ethereum address. @@ -42,7 +43,7 @@ pub struct GenesisConfig { pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. - pub native_erc20: Address, + pub native_erc20: EthAddress, /// The Ethereum address of the bridge contract. pub bridge: UpgradeableContract, /// The Ethereum address of the governance contract. @@ -87,9 +88,7 @@ mod tests { let config = GenesisConfig { min_confirmations: MinimumConfirmations::default(), contracts: Contracts { - native_erc20: Address( - "0x1721b337BBdd2b11f9Eef736d9192a8E9Cba5872".to_string(), - ), + native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { address: Address( "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..5a66906a49 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::Uint as ethUint; use eyre::{eyre, Context}; +use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; @@ -55,6 +56,8 @@ impl From for Uint { PartialOrd, Ord, Hash, + Serialize, + Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, From f07784a733940c21935f1583df71fb6e0add33cd Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 15:14:09 +0200 Subject: [PATCH 1291/1995] [feat]: Write the ethereum bridge config params on genesis --- apps/src/lib/config/ethereum_bridge/params.rs | 85 ++++------ apps/src/lib/config/genesis.rs | 91 +++++++---- apps/src/lib/node/ledger/shell/init_chain.rs | 147 +++++++++++++----- apps/src/lib/node/ledger/shell/mod.rs | 4 + apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +++++++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 +++++++++ shared/src/ledger/parameters/mod.rs | 1 + shared/src/ledger/storage/mockdb.rs | 54 +++++++ shared/src/ledger/storage/mod.rs | 54 +++++++ shared/src/types/ethereum_events.rs | 36 +++++ 10 files changed, 503 insertions(+), 126 deletions(-) create mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 0ab56b0b47..d76b6964fd 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,33 +1,22 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. -use std::num::NonZeroU64; - +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -/// Represents a configuration value for an Ethereum address. -/// -/// For instance: -/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct Address(String); - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - /// Represents chain parameters for the Ethereum bridge. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct GenesisConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. @@ -39,7 +28,16 @@ pub struct GenesisConfig { /// Represents all the Ethereum contracts that need to be directly know about by /// validators. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. @@ -50,33 +48,10 @@ pub struct Contracts { pub governance: UpgradeableContract, } -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: Address, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} - #[cfg(test)] mod tests { use eyre::Result; + use namada::ledger::parameters::ethereum_bridge::ContractVersion; use super::*; @@ -90,17 +65,11 @@ mod tests { contracts: Contracts { native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { - address: Address( - "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" - .to_string(), - ), + address: EthAddress([23; 20]), version: ContractVersion::default(), }, governance: UpgradeableContract { - address: Address( - "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" - .to_string(), - ), + address: EthAddress([18; 20]), version: ContractVersion::default(), }, }, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 6559804516..7fa47bbc61 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -9,6 +9,7 @@ use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; +use namada::ledger::treasury::parameters::TreasuryParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -17,6 +18,8 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; +use crate::config::ethereum_bridge; + /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -25,12 +28,12 @@ pub mod genesis_config { use std::path::Path; use std::str::FromStr; - use data_encoding::HEXLOWER; - use eyre::Context; + use hex; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::ledger::treasury::parameters::TreasuryParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -40,22 +43,22 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, + ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + TokenAccount, Validator, }; use crate::cli; - use crate::config::ethereum_bridge; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct HexString(pub String); impl HexString { pub fn to_bytes(&self) -> Result, HexKeyError> { - let bytes = HEXLOWER.decode(self.0.as_ref())?; + let bytes = hex::decode(&self.0)?; Ok(bytes) } pub fn to_sha256_bytes(&self) -> Result<[u8; 32], HexKeyError> { - let bytes = HEXLOWER.decode(self.0.as_ref())?; + let bytes = hex::decode(&self.0)?; let slice = bytes.as_slice(); let array: [u8; 32] = slice.try_into()?; Ok(array) @@ -76,15 +79,15 @@ pub mod genesis_config { #[derive(Error, Debug)] pub enum HexKeyError { #[error("Invalid hex string: {0:?}")] - InvalidHexString(data_encoding::DecodeError), + InvalidHexString(hex::FromHexError), #[error("Invalid sha256 checksum: {0}")] InvalidSha256(TryFromSliceError), #[error("Invalid public key: {0}")] InvalidPublicKey(ParsePublicKeyError), } - impl From for HexKeyError { - fn from(err: data_encoding::DecodeError) -> Self { + impl From for HexKeyError { + fn from(err: hex::FromHexError) -> Self { Self::InvalidHexString(err) } } @@ -119,6 +122,8 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, + // Treasury parameters + pub treasury_params: TreasuryParamasConfig, // Ethereum bridge config pub ethereum_bridge_params: Option, @@ -134,12 +139,9 @@ pub mod genesis_config { // Maximum size of proposal in kibibytes (KiB) // XXX: u64 doesn't work with toml-rs! pub max_proposal_code_size: u64, - // Minimum proposal period length in epochs + // Proposal period length in epoch // XXX: u64 doesn't work with toml-rs! pub min_proposal_period: u64, - // Maximum proposal period length in epochs - // XXX: u64 doesn't work with toml-rs! - pub max_proposal_period: u64, // Maximum number of characters in the proposal content // XXX: u64 doesn't work with toml-rs! pub max_proposal_content_size: u64, @@ -148,6 +150,13 @@ pub mod genesis_config { pub min_proposal_grace_epochs: u64, } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TreasuryParamasConfig { + // Maximum funds that can be moved from treasury in a single transfer + // XXX: u64 doesn't work with toml-rs! + pub max_proposal_fund_transfer: u64, + } + /// Validator pre-genesis configuration can be created with client utils /// `init-genesis-validator` command and added to a genesis for /// `init-network` cmd and that can be subsequently read by `join-network` @@ -190,6 +199,16 @@ pub mod genesis_config { pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, + /// Matchmaker account's alias, if any + pub matchmaker_account: Option, + /// Path to a matchmaker WASM program, if any + pub matchmaker_code: Option, + /// Path to a transaction WASM code used by the matchmaker, if any + pub matchmaker_tx: Option, + /// Is this validator running a seed intent gossip node? A seed node is + /// not part of the gossipsub where intents are being propagated and + /// hence cannot run matchmakers + pub intent_gossip_seed: Option, /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, @@ -553,7 +572,6 @@ pub mod genesis_config { min_proposal_fund: config.gov_params.min_proposal_fund, max_proposal_code_size: config.gov_params.max_proposal_code_size, min_proposal_period: config.gov_params.min_proposal_period, - max_proposal_period: config.gov_params.max_proposal_period, max_proposal_content_size: config .gov_params .max_proposal_content_size, @@ -562,6 +580,10 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; + let treasury_params = TreasuryParams { + max_proposal_fund_transfer: 10_000, + }; + let pos_params = PosParams { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, @@ -588,27 +610,16 @@ pub mod genesis_config { parameters, pos_params, gov_params, + treasury_params, + ethereum_bridge_params: config.ethereum_bridge_params, }; genesis.init(); genesis } - 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 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 write_genesis_config( @@ -620,7 +631,7 @@ pub mod genesis_config { } pub fn read_genesis_config(path: impl AsRef) -> Genesis { - load_genesis_config(open_genesis_config(path).unwrap()) + load_genesis_config(open_genesis_config(path)) } } @@ -635,6 +646,9 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, + pub treasury_params: TreasuryParams, + // Ethereum bridge config + pub ethereum_bridge_params: Option, } impl Genesis { @@ -826,6 +840,13 @@ pub fn genesis() -> Genesis { public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; + let matchmaker = EstablishedAccount { + address: wallet::defaults::matchmaker_address(), + vp_code_path: vp_user_path.into(), + vp_sha256: Default::default(), + public_key: Some(wallet::defaults::matchmaker_keypair().ref_to()), + storage: HashMap::default(), + }; let implicit_accounts = vec![ImplicitAccount { public_key: wallet::defaults::daewon_keypair().ref_to(), }]; @@ -852,6 +873,10 @@ pub fn genesis() -> Genesis { default_key_tokens, ), ((&validator.account_key).into(), default_key_tokens), + ( + matchmaker.public_key.as_ref().unwrap().into(), + default_key_tokens, + ), ]); let token_accounts = address::tokens() .into_iter() @@ -865,12 +890,14 @@ pub fn genesis() -> Genesis { Genesis { genesis_time: DateTimeUtc::now(), validators: vec![validator], - established_accounts: vec![albert, bertha, christel], + established_accounts: vec![albert, bertha, christel, matchmaker], implicit_accounts, token_accounts, parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), + treasury_params: TreasuryParams::default(), + ethereum_bridge_params: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 890e8a4513..6800dfde4b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,20 +2,18 @@ use std::collections::HashMap; use std::hash::Hash; -use namada::ledger::storage::traits::StorageHasher; -use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::{ibc, pos}; +use namada::ledger::parameters::Parameters; +use namada::ledger::pos::PosParams; use namada::types::key::*; -use namada::types::time::{DateTimeUtc, TimeZone, Utc}; -use namada::types::token; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; +use super::queries::QueriesExt; use super::*; +use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; -use crate::facade::tower_abci::{request, response}; use crate::wasm_loader; impl Shell @@ -26,11 +24,13 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens + /// 3. Validators + /// 4. The PoS system + /// 5. The Ethereum bridge parameters pub fn init_chain( &mut self, init: request::InitChain, ) -> Result { - let mut response = response::InitChain::default(); let (current_chain_id, _) = self.storage.get_chain_id(); if current_chain_id != init.chain_id { return Err(Error::ChainId(format!( @@ -65,6 +65,7 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); + genesis.treasury_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage @@ -79,24 +80,51 @@ where let mut vp_code_cache: HashMap> = HashMap::default(); // Initialize genesis established accounts + self.initialize_established_accounts( + genesis.established_accounts, + &mut vp_code_cache, + ); + + // Initialize genesis implicit + self.initialize_implicit_accounts(genesis.implicit_accounts); + + // Initialize genesis token accounts + self.initialize_token_accounts( + genesis.token_accounts, + &mut vp_code_cache, + ); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + self.configure_ethereuem_bridge(config); + } + // Initialize genesis validator accounts + self.initialize_validators(&genesis.validators, &mut vp_code_cache); + // set the initial validators set + Ok(self.set_initial_validators( + genesis.validators, + &genesis.parameters, + &genesis.pos_params, + )) + } + + /// Initialize genesis established accounts + fn initialize_established_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { for genesis::EstablishedAccount { address, vp_code_path, vp_sha256, public_key, storage, - } in genesis.established_accounts + } in accounts { - 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 - } - }; + let vp_code = vp_code_cache + .get_or_insert_with(vp_code_path.clone(), || { + wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) + }); // In dev, we don't check the hash #[cfg(feature = "dev")] @@ -129,29 +157,40 @@ where self.storage.write(&key, value).unwrap(); } } + } + /// Initialize genesis implicit accounts + fn initialize_implicit_accounts( + &mut self, + accounts: Vec, + ) { // Initialize genesis implicit - for genesis::ImplicitAccount { public_key } in genesis.implicit_accounts - { + for genesis::ImplicitAccount { public_key } in accounts { let address: address::Address = (&public_key).into(); let pk_storage_key = pk_key(&address); self.storage .write(&pk_storage_key, public_key.try_to_vec().unwrap()) .unwrap(); } + } + /// Initialize genesis token accounts + fn initialize_token_accounts( + &mut self, + accounts: Vec, + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis token accounts for genesis::TokenAccount { address, vp_code_path, vp_sha256, balances, - } in genesis.token_accounts + } in 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 @@ -183,9 +222,16 @@ where .unwrap(); } } + } + /// Initialize genesis validator accounts + fn initialize_validators( + &mut self, + validators: &[genesis::Validator], + vp_code_cache: &mut HashMap>, + ) { // Initialize genesis validator accounts - for validator in &genesis.validators { + for validator in validators { let vp_code = vp_code_cache.get_or_insert_with( validator.validator_vp_code_path.clone(), || { @@ -193,7 +239,6 @@ where &self.wasm_dir, &validator.validator_vp_code_path, ) - .unwrap() }, ); @@ -255,22 +300,35 @@ where ) .expect("Unable to set genesis user public DKG session key"); } + } + /// Initialize the PoS and set the initial validator set + fn set_initial_validators( + &mut self, + validators: Vec, + parameters: &Parameters, + pos_params: &PosParams, + ) -> response::InitChain { + let mut response = response::InitChain::default(); // PoS system depends on epoch being initialized let (current_epoch, _gas) = self.storage.get_current_epoch(); pos::init_genesis_storage( &mut self.storage, - &genesis.pos_params, - genesis - .validators - .iter() - .map(|validator| &validator.pos_data), + pos_params, + validators.iter().map(|validator| &validator.pos_data), current_epoch, ); ibc::init_genesis_storage(&mut self.storage); + let evidence_params = self + .storage + .get_evidence_params(¶meters.epoch_duration, 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 { + for validator in validators { let mut abci_validator = abci::ValidatorUpdate::default(); let consensus_key: common::PublicKey = validator.pos_data.consensus_key.clone(); @@ -278,14 +336,33 @@ where sum: Some(key_to_tendermint(&consensus_key).unwrap()), }; abci_validator.pub_key = Some(pub_key); - let power: u64 = - validator.pos_data.voting_power(&genesis.pos_params).into(); + let power: u64 = validator.pos_data.voting_power(pos_params).into(); abci_validator.power = power .try_into() .expect("unexpected validator's voting power"); response.validators.push(abci_validator); } - Ok(response) + response + } + + /// Set the parameters for the Ethereum bridge + fn configure_ethereuem_bridge( + &mut self, + config: ethereum_bridge::params::GenesisConfig, + ) { + let ethereum_bridge::params::GenesisConfig { + min_confirmations, + contracts: + ethereum_bridge::params::Contracts { + native_erc20, + bridge, + governance, + }, + } = config; + self.storage.min_confirmations = Some(min_confirmations); + self.storage.native_erc20 = Some(native_erc20); + self.storage.bridge_contract = Some(bridge); + self.storage.governance_contract = Some(governance); } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ae8aade9b4..cfed6feea3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,6 +1121,10 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 5085663c41..0bf05c1cbe 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,7 +5,13 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` +//! - `tx_queue` +//! - `min_confirmations`: Minimum number of confirmations needed for the +//! Ethereum bridge to consider an event final. +//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents +//! this chain's native token. +//! - `bridge_contract`: The Ethereum address of the bridge contract. +//! - `governance_contract`: The Ethereum address of the governance contract. //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -32,11 +38,15 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; +use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -300,6 +310,61 @@ impl DB for RocksDB { return Ok(None); } }; + let min_confirmations: Option = match self + .0 + .get("min_confirmations") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the minimum confirmations from the DB" + ); + return Ok(None); + } + }; + let native_erc20: Option = match self + .0 + .get("native_erc20") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum address for wrapped Nam from \ + the DB" + ); + return Ok(None); + } + }; + let bridge_contract: Option = match self + .0 + .get("bridge_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum bridge contract address from \ + the DB" + ); + return Ok(None); + } + }; + let governance_contract: Option = match self + .0 + .get("governance_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum governance contract from the \ + DB" + ); + return Ok(None); + } + }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -402,6 +467,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -424,6 +493,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -452,6 +525,10 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); + batch.put("min_confirmations", types::encode(&min_confirmations)); + batch.put("native_erc20", types::encode(&native_erc20)); + batch.put("bridge_contract", types::encode(&bridge_contract)); + batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -994,6 +1071,10 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs new file mode 100644 index 0000000000..1e54207f6e --- /dev/null +++ b/shared/src/ledger/parameters/ethereum_bridge.rs @@ -0,0 +1,74 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4891818dc0..0ec554dd80 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,4 +1,5 @@ //! Protocol parameters +pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 234cad4498..29a19a9760 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,7 +12,11 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,6 +77,34 @@ impl DB for MockDB { } None => return Ok(None), }; + let min_confirmations: Option = + match self.0.borrow().get("min_confirmations") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let native_erc20: Option = + match self.0.borrow().get("native_erc20") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let bridge_contract: Option = + match self.0.borrow().get("bridge_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let governance_contract: Option = + match self.0.borrow().get("governance_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -153,6 +185,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -175,6 +211,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -186,6 +226,20 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); + self.0.borrow_mut().insert( + "min_confirmations".into(), + types::encode(&min_confirmations), + ); + self.0 + .borrow_mut() + .insert("native_erc20".into(), types::encode(&native_erc20)); + self.0 + .borrow_mut() + .insert("bridge_contract".into(), types::encode(&bridge_contract)); + self.0.borrow_mut().insert( + "goverenance_contract".into(), + types::encode(&governance_contract), + ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 805caa6348..0411375912 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,6 +17,9 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -28,6 +31,7 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -69,6 +73,16 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block storage data @@ -128,6 +142,16 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block's state to write into the database. @@ -153,6 +177,16 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// A database backend. @@ -302,6 +336,10 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } @@ -319,6 +357,10 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract: bridge, + governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -335,6 +377,10 @@ where { self.tx_queue = tx_queue; } + self.min_confirmations = min_confirmations; + self.native_erc20 = native_erc20; + self.bridge_contract = bridge; + self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -366,6 +412,10 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, + min_confirmations: self.min_confirmations, + native_erc20: self.native_erc20.clone(), + bridge_contract: self.bridge_contract.clone(), + governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -922,6 +972,10 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } } diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 5a66906a49..c6c8c061a2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -62,6 +62,8 @@ impl From for Uint { BorshDeserialize, BorshSchema, )] +#[serde(try_from = "String")] +#[serde(into = "String")] pub struct EthAddress(pub [u8; 20]); impl EthAddress { @@ -91,6 +93,20 @@ impl FromStr for EthAddress { } } +impl TryFrom for EthAddress { + type Error = eyre::Error; + + fn try_from(string: String) -> Result { + Self::from_str(string.as_ref()) + } +} + +impl From for String { + fn from(addr: EthAddress) -> Self { + addr.to_string() + } +} + /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, @@ -278,6 +294,26 @@ pub mod tests { assert!(result.is_err()); } + + /// Test that serde correct serializes EthAddress types to/from lowercase + /// hex encodings + #[test] + fn test_eth_address_serde_roundtrip() { + let addr = + EthAddress::from_str(testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED) + .unwrap(); + let serialized = serde_json::to_string(&addr).expect("Test failed"); + assert_eq!( + serialized, + format!( + r#""{}""#, + testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_lowercase() + ) + ); + let deserialized: EthAddress = + serde_json::from_str(&serialized).expect("Test failed"); + assert_eq!(addr, deserialized); + } } #[allow(missing_docs)] From c4fc933132e43f22c5bc1df5b47c9a6c54e109b4 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 18:52:47 +0200 Subject: [PATCH 1292/1995] [feat]: Refactored the eth bridge params to live under the bridge vp storage --- apps/src/lib/config/ethereum_bridge/mod.rs | 37 ++++- apps/src/lib/config/ethereum_bridge/params.rs | 83 ---------- apps/src/lib/config/genesis.rs | 10 +- apps/src/lib/node/ledger/shell/init_chain.rs | 30 +--- apps/src/lib/node/ledger/shell/mod.rs | 4 - apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +--------- shared/src/ledger/eth_bridge/mod.rs | 1 + shared/src/ledger/eth_bridge/parameters.rs | 152 ++++++++++++++++++ shared/src/ledger/eth_bridge/storage/mod.rs | 51 +++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 --------- shared/src/ledger/parameters/mod.rs | 1 - shared/src/ledger/storage/mockdb.rs | 54 ------- shared/src/ledger/storage/mod.rs | 54 ------- 13 files changed, 249 insertions(+), 385 deletions(-) delete mode 100644 apps/src/lib/config/ethereum_bridge/params.rs create mode 100644 shared/src/ledger/eth_bridge/parameters.rs delete mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index ab72d58b42..d4606b0652 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,2 +1,37 @@ pub mod ledger; -pub mod params; + +#[cfg(test)] +mod tests { + use eyre::Result; + use namada::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use namada::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs deleted file mode 100644 index d76b6964fd..0000000000 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Blockchain-level parameters for the configuration of the Ethereum bridge. -use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; -use namada::types::ethereum_events::EthAddress; -use serde::{Deserialize, Serialize}; - -/// Represents chain parameters for the Ethereum bridge. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct GenesisConfig { - /// Minimum number of confirmations needed to trust an Ethereum branch. - /// This must be at least one. - pub min_confirmations: MinimumConfirmations, - /// The addresses of the Ethereum contracts that need to be directly known - /// by validators. - pub contracts: Contracts, -} - -/// Represents all the Ethereum contracts that need to be directly know about by -/// validators. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct Contracts { - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: EthAddress, - /// The Ethereum address of the bridge contract. - pub bridge: UpgradeableContract, - /// The Ethereum address of the governance contract. - pub governance: UpgradeableContract, -} - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::parameters::ethereum_bridge::ContractVersion; - - use super::*; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = GenesisConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: GenesisConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 7fa47bbc61..4498a345d2 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,6 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -18,8 +19,6 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; -use crate::config::ethereum_bridge; - /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -43,7 +42,7 @@ pub mod genesis_config { use thiserror::Error; use super::{ - ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; @@ -125,8 +124,7 @@ pub mod genesis_config { // Treasury parameters pub treasury_params: TreasuryParamasConfig, // Ethereum bridge config - pub ethereum_bridge_params: - Option, + pub ethereum_bridge_params: Option, // Wasm definitions pub wasm: HashMap, } @@ -648,7 +646,7 @@ pub struct Genesis { pub gov_params: GovParams, pub treasury_params: TreasuryParams, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 6800dfde4b..a65bccf60f 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -10,7 +10,6 @@ use sha2::{Digest, Sha256}; use super::queries::QueriesExt; use super::*; -use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -66,6 +65,10 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); genesis.treasury_params.init_storage(&mut self.storage); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + config.init_storage(&mut self.storage); + } // Depends on parameters being initialized self.storage @@ -93,10 +96,7 @@ where genesis.token_accounts, &mut vp_code_cache, ); - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - self.configure_ethereuem_bridge(config); - } + // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set @@ -344,26 +344,6 @@ where } response } - - /// Set the parameters for the Ethereum bridge - fn configure_ethereuem_bridge( - &mut self, - config: ethereum_bridge::params::GenesisConfig, - ) { - let ethereum_bridge::params::GenesisConfig { - min_confirmations, - contracts: - ethereum_bridge::params::Contracts { - native_erc20, - bridge, - governance, - }, - } = config; - self.storage.min_confirmations = Some(min_confirmations); - self.storage.native_erc20 = Some(native_erc20); - self.storage.bridge_contract = Some(bridge); - self.storage.governance_contract = Some(governance); - } } trait HashMapExt diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cfed6feea3..ae8aade9b4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,10 +1121,6 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 0bf05c1cbe..5085663c41 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,13 +5,7 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` -//! - `min_confirmations`: Minimum number of confirmations needed for the -//! Ethereum bridge to consider an event final. -//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents -//! this chain's native token. -//! - `bridge_contract`: The Ethereum address of the bridge contract. -//! - `governance_contract`: The Ethereum address of the governance contract. +//! - `tx_queue` //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -38,15 +32,11 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; -use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -310,61 +300,6 @@ impl DB for RocksDB { return Ok(None); } }; - let min_confirmations: Option = match self - .0 - .get("min_confirmations") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the minimum confirmations from the DB" - ); - return Ok(None); - } - }; - let native_erc20: Option = match self - .0 - .get("native_erc20") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum address for wrapped Nam from \ - the DB" - ); - return Ok(None); - } - }; - let bridge_contract: Option = match self - .0 - .get("bridge_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum bridge contract address from \ - the DB" - ); - return Ok(None); - } - }; - let governance_contract: Option = match self - .0 - .get("governance_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum governance contract from the \ - DB" - ); - return Ok(None); - } - }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -467,10 +402,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -493,10 +424,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -525,10 +452,6 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); - batch.put("min_confirmations", types::encode(&min_confirmations)); - batch.put("native_erc20", types::encode(&native_erc20)); - batch.put("bridge_contract", types::encode(&bridge_contract)); - batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -1071,10 +994,6 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs index 047bcda91e..a740fa16a2 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -1,5 +1,6 @@ //! Validity predicate and storage keys for the Ethereum bridge account pub mod bridge_pool_vp; +pub mod parameters; pub mod storage; pub mod vp; diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs new file mode 100644 index 0000000000..2d22730a8a --- /dev/null +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -0,0 +1,152 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::storage::types::encode; +use crate::ledger::storage::{self, Storage}; +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} + +/// Represents all the Ethereum contracts that need to be directly know about by +/// validators. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct Contracts { + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: EthAddress, + /// The Ethereum address of the bridge contract. + pub bridge: UpgradeableContract, + /// The Ethereum address of the governance contract. + pub governance: UpgradeableContract, +} + +/// Represents chain parameters for the Ethereum bridge. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct EthereumBridgeConfig { + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +impl EthereumBridgeConfig { + /// Initialize the Ethereum bridge parameters in storage + pub fn init_storage(&self, storage: &mut Storage) + where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::traits::StorageHasher, + { + let Self { + min_confirmations, + contracts: + Contracts { + native_erc20, + bridge, + governance, + }, + } = self; + let min_confirmations_key = bridge_storage::min_confirmations_key(); + let native_erc20_key = bridge_storage::native_erc20_key(); + let bridge_contract_key = bridge_storage::bridge_contract_key(); + let governance_contract_key = bridge_storage::governance_contract_key(); + storage + .write(&min_confirmations_key, encode(min_confirmations)) + .unwrap(); + storage + .write(&native_erc20_key, encode(native_erc20)) + .unwrap(); + storage.write(&bridge_contract_key, encode(bridge)).unwrap(); + storage + .write(&governance_contract_key, encode(governance)) + .unwrap(); + } +} diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 595f403034..b117f4b42d 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -4,7 +4,16 @@ pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; + +/// Sub-key for storing the minimum confirmations parameter +pub const MIN_CONFIRMATIONS_SUBKEY: &str = "min_confirmations"; +/// Sub-key for storing the Ethereum address for wNam. +pub const NATIVE_ERC20_SUBKEY: &str = "native_erc20"; +/// Sub-lkey for storing the Ethereum address of the bridge contract. +pub const BRIDGE_CONTRACT_SUBKEY: &str = "bridge_contract_address"; +/// Sub-key for storing the Ethereum address of the governance contract. +pub const GOVERNANCE_CONTRACT_SUBKEY: &str = "governance_contract_address"; /// Key prefix for the storage subspace pub fn prefix() -> Key { @@ -16,6 +25,46 @@ pub fn is_eth_bridge_key(key: &Key) -> bool { matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) } +/// Storage key for the minimum confirmations parameter. +pub fn min_confirmations_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(MIN_CONFIRMATIONS_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of wNam. +pub fn native_erc20_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(NATIVE_ERC20_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the bridge contract. +pub fn bridge_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(BRIDGE_CONTRACT_SUBKEY.into()), + ], + } +} + +/// Storage key for the Ethereum address of the governance contract. +pub fn governance_contract_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(GOVERNANCE_CONTRACT_SUBKEY.into()), + ], + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs deleted file mode 100644 index 1e54207f6e..0000000000 --- a/shared/src/ledger/parameters/ethereum_bridge.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Parameters for configuring the Ethereum bridge -use std::num::NonZeroU64; - -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; - -use crate::types::ethereum_events::EthAddress; - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: EthAddress, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 0ec554dd80..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,5 +1,4 @@ //! Protocol parameters -pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 29a19a9760..234cad4498 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,11 +12,7 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -77,34 +73,6 @@ impl DB for MockDB { } None => return Ok(None), }; - let min_confirmations: Option = - match self.0.borrow().get("min_confirmations") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let native_erc20: Option = - match self.0.borrow().get("native_erc20") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let bridge_contract: Option = - match self.0.borrow().get("bridge_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let governance_contract: Option = - match self.0.borrow().get("governance_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -185,10 +153,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -211,10 +175,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -226,20 +186,6 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); - self.0.borrow_mut().insert( - "min_confirmations".into(), - types::encode(&min_confirmations), - ); - self.0 - .borrow_mut() - .insert("native_erc20".into(), types::encode(&native_erc20)); - self.0 - .borrow_mut() - .insert("bridge_contract".into(), types::encode(&bridge_contract)); - self.0.borrow_mut().insert( - "goverenance_contract".into(), - types::encode(&governance_contract), - ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0411375912..805caa6348 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,9 +17,6 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -31,7 +28,6 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,16 +69,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block storage data @@ -142,16 +128,6 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block's state to write into the database. @@ -177,16 +153,6 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// A database backend. @@ -336,10 +302,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } @@ -357,10 +319,6 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract: bridge, - governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -377,10 +335,6 @@ where { self.tx_queue = tx_queue; } - self.min_confirmations = min_confirmations; - self.native_erc20 = native_erc20; - self.bridge_contract = bridge; - self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -412,10 +366,6 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, - min_confirmations: self.min_confirmations, - native_erc20: self.native_erc20.clone(), - bridge_contract: self.bridge_contract.clone(), - governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -972,10 +922,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } } From b1e4789e96e64cf7ed0d2f301e735cc793b472f6 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:46:31 +0200 Subject: [PATCH 1293/1995] [feat]: Added ability to escrow Nam to the bridge pool VP when wanting to mint wNam on Ethereum --- apps/src/lib/config/ethereum_bridge/params.rs | 114 ++++++ .../src/ledger/eth_bridge/bridge_pool_vp.rs | 351 ++++++++++++++---- shared/src/types/address.rs | 11 + 3 files changed, 406 insertions(+), 70 deletions(-) create mode 100644 apps/src/lib/config/ethereum_bridge/params.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs new file mode 100644 index 0000000000..0ab56b0b47 --- /dev/null +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -0,0 +1,114 @@ +//! Blockchain-level parameters for the configuration of the Ethereum bridge. +use std::num::NonZeroU64; + +use namada::types::ethereum_events::EthAddress; +use serde::{Deserialize, Serialize}; + +/// Represents a configuration value for an Ethereum address. +/// +/// For instance: +/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` +#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[repr(transparent)] +pub struct Address(String); + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents chain parameters for the Ethereum bridge. +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct GenesisConfig { + /// Minimum number of confirmations needed to trust an Ethereum branch. + /// This must be at least one. + pub min_confirmations: MinimumConfirmations, + /// The addresses of the Ethereum contracts that need to be directly known + /// by validators. + pub contracts: Contracts, +} + +/// Represents all the Ethereum contracts that need to be directly know about by +/// validators. +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct Contracts { + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: EthAddress, + /// The Ethereum address of the bridge contract. + pub bridge: UpgradeableContract, + /// The Ethereum address of the governance contract. + pub governance: UpgradeableContract, +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: Address, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} + +#[cfg(test)] +mod tests { + use eyre::Result; + + use super::*; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = GenesisConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: Address( + "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" + .to_string(), + ), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: Address( + "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" + .to_string(), + ), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: GenesisConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index e696051c72..fc968fbfcd 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -8,7 +8,8 @@ //! //! This VP checks that additions to the pool are handled //! correctly. This means that the appropriate data is -//! added to the pool and gas fees are submitted appropriately. +//! added to the pool and gas fees are submitted appropriately +//! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; use borsh::BorshDeserialize; @@ -23,7 +24,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{xan, Address, InternalAddress}; +use crate::types::address::{wnam, xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; @@ -79,11 +80,44 @@ where Some(SignedAmount::Positive(after - before)) } } -} -/// Check if a delta matches the delta given by a transfer -fn check_delta(delta: &(Address, Amount), transfer: &PendingTransfer) -> bool { - delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount + /// Check that the correct amount of Nam was sent + /// from the correct account into escrow + fn check_nam_escrowed( + &self, + payer_account: &Address, + escrow_account: &Address, + expected_debit: Amount, + expected_credit: Amount, + ) -> bool { + // check that the correct amount was deducted from the fee payer + if let Some(SignedAmount::Negative(amount)) = + self.account_balance_delta(payer_account) + { + if amount != expected_debit { + return false; + } + } else { + tracing::debug!("The account {} was not debited.", payer_account); + return false; + } + // check that the correct amount was credited to escrow + if let Some(SignedAmount::Positive(amount)) = + self.account_balance_delta(escrow_account) + { + if amount != expected_credit { + return false; + } + } else { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + return false; + } + true + } } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -124,7 +158,7 @@ where let pending_key = get_pending_key(&transfer); // check that transfer is not already in the pool - match (&self.ctx).read_pre_value::(&pending_key) { + match (&self.ctx).read_pre(&pending_key) { Ok(Some(_)) => { tracing::debug!( "Rejecting transaction as the transfer is already in the \ @@ -132,11 +166,10 @@ where ); return Ok(false); } - Err(e) => { + Err(_) => { return Err(eyre!( "Could not read the storage key associated with the \ - transfer: {:?}", - e + transfer." ) .into()); } @@ -169,36 +202,41 @@ where return Ok(false); } - // check that gas fees were put into escrow - - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(&transfer.gas_fee.payer) - { - if amount != transfer.gas_fee.amount { - return Ok(false); - } + // if we are going to mint wNam on Ethereum, the appropriate + // amount of Nam must be escrowed in the Ethereum bridge VP's storage. + // TODO: We should look this address up from storage + if transfer.transfer.asset == wnam() { + // check that correct amount of Nam was put into escrow. + return if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); + Ok(true) + }; } else { - tracing::debug!("The gas fee payers account was not debited."); - return Ok(false); - } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(&BRIDGE_POOL_ADDRESS) - { - if amount != transfer.gas_fee.amount { + // check that the correct amounnt of gas fees were escrowed + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount, + transfer.gas_fee.amount, + ) { return Ok(false); } - } else { - tracing::debug!( - "The Ethereum bridge pool's gas escrow was not credited." - ); - return Ok(false); } - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer {:?}.", - transfer - ); // check that the assets to be transferred were escrowed let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); @@ -212,12 +250,13 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some(delta)) if check_delta(&delta, &transfer) => {} - other => { + Ok(Some((addr, amt))) + if addr == transfer.transfer.sender + && amt == transfer.transfer.amount => {} + _ => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool: {:?}", - other + escrowed into the Ethereum bridge pool." ); return Ok(false); } @@ -230,6 +269,10 @@ where return Ok(false); } + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer {:?}.", + transfer + ); Ok(true) } } @@ -393,17 +436,12 @@ mod test_bridge_pool_vp { tx: &'a Tx, storage: &'a Storage, write_log: &'a WriteLog, - keys_changed: &'a BTreeSet, - verifiers: &'a BTreeSet
, ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { Ctx::new( - &BRIDGE_POOL_ADDRESS, storage, write_log, tx, VpGasMeter::new(0u64), - keys_changed, - verifiers, VpCache::new(temp_dir(), 100usize), ) } @@ -478,16 +516,10 @@ mod test_bridge_pool_vp { escrow_delta, ); keys_changed.append(&mut new_keys_changed); - let verifiers = BTreeSet::default(); + // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx( - &tx, - &storage, - &write_log, - &keys_changed, - &verifiers, - ), + ctx: setup_ctx(&tx, &storage, &write_log), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -499,6 +531,7 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); + let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); match expect { Expect::True => assert!(res.expect("Test failed")), @@ -827,17 +860,10 @@ mod test_bridge_pool_vp { SignedAmount::Positive(TOKENS.into()), ); keys_changed.append(&mut new_keys_changed); - let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx( - &tx, - &storage, - &write_log, - &keys_changed, - &verifiers, - ), + ctx: setup_ctx(&tx, &storage, &write_log), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -849,6 +875,7 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); + let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); assert!(!res.expect("Test failed")); } @@ -899,20 +926,204 @@ mod test_bridge_pool_vp { wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), ); - // inform the vp that the merkle root changed - let keys_changed = BTreeSet::default(); + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } + + /// Test that we can escrow Nam if we + /// want to mint wNam on Ethereum. + #[test] + fn test_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; let verifiers = BTreeSet::default(); + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(res); + } + + /// Test that we can rejecte a transfer that + /// mints wNam if we don't escrow the correct + /// amount of Nam. + #[test] + fn test_reject_mint_wnam() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: bertha_address(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 200) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx( - &tx, - &storage, - &write_log, - &keys_changed, - &verifiers, - ), + ctx: setup_ctx(&tx, &storage, &write_log), }; + let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 74ab4f53fe..6c525ca819 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::key::PublicKeyHash; @@ -533,6 +534,16 @@ pub fn kartoffel() -> Address { Address::decode("atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90").expect("The token address decoding shouldn't fail") } +/// Temporary helper for testing +pub const fn wnam() -> EthAddress { + // TODO: Replace this with the real wNam ERC20 address once it exists + // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" + EthAddress([ + 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, + 190, 239, 222, 173, 190, 239, + ]) +} + /// Temporary helper for testing, a hash map of tokens addresses with their /// informal currency codes. pub fn tokens() -> HashMap { From 4d869d9c95aa3f4c166581d2e0ac92c66d0bb86c Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:03:10 +0100 Subject: [PATCH 1294/1995] [fix]: Fixed bugs in escrowing Nam when minting wNam and checking balance deltas. Covered with a test --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 208 +++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index fc968fbfcd..6b63bd6ef2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -90,33 +90,47 @@ where expected_debit: Amount, expected_credit: Amount, ) -> bool { - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(payer_account) - { - if amount != expected_debit { - return false; - } - } else { - tracing::debug!("The account {} was not debited.", payer_account); + let debited = self.account_balance_delta(payer_account); + let credited = self.account_balance_delta(escrow_account); + if debited.is_none() && credited.is_none() { return false; } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(escrow_account) - { - if amount != expected_credit { - return false; + + match (debited, credited) { + ( + Some(SignedAmount::Negative(debit)), + Some(SignedAmount::Positive(credit)), + ) => debit == expected_debit && credit == expected_credit, + (Some(SignedAmount::Positive(_)), _) => { + tracing::debug!( + "The account {} was not debited.", + payer_account + ); + false + } + (_, Some(SignedAmount::Negative(_))) => { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + false + } + (None, _) => { + tracing::debug!( + "Could not calculate the balance delta for {}", + payer_account + ); + false + } + (_, None) => { + tracing::debug!( + "Could not calculate the balance delta for the Ethereum \ + bridge pool" + ); + false } - } else { - tracing::debug!( - "The Ethereum bridge pool's escrow was not credited from \ - account {}.", - payer_account - ); - return false; } - true } } @@ -207,15 +221,36 @@ where // TODO: We should look this address up from storage if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if !self.check_nam_escrowed( + return if transfer.gas_fee.payer == transfer.transfer.sender { + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer \ + {:?}.", + transfer + ); + Ok(true) + } + } else if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, transfer.gas_fee.amount, ) || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, transfer.transfer.amount, ) { Ok(false) @@ -227,7 +262,7 @@ where Ok(true) }; } else { - // check that the correct amounnt of gas fees were escrowed + // check that the correct amount of gas fees were escrowed if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, @@ -331,6 +366,12 @@ mod test_bridge_pool_vp { .expect("The token address decoding shouldn't fail") } + /// A sampled established address for tests + pub fn established_address_1() -> Address { + Address::decode("atest1v4ehgw36g56ngwpk8ppnzsf4xqeyvsf3xq6nxde5gseyys3nxgenvvfex5cnyd2rx9zrzwfctgx7sp") + .expect("The token address decoding shouldn't fail") + } + fn bertha_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -1139,4 +1180,117 @@ mod test_bridge_pool_vp { .expect("Test failed"); assert!(!res); } + + /// Test that we check escrowing Nam correctly when minting wNam + /// and the gas payer account is different from the transferring + /// account. + #[test] + fn test_mint_wnam_separate_gas_payer() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + // initialize the gas payers account + let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: established_address_1(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } } From 7f0bdc50cc55ee6688eaded8941b818e900bb7ac Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:32:23 +0100 Subject: [PATCH 1295/1995] [feat]: Changed the bridge pool vp to read the native erc20 ethereum address from storage rather than using a hardcoded address. --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 105 ++++++++++++------ 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6b63bd6ef2..644e33993a 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -15,6 +15,7 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; +use super::storage; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; @@ -24,8 +25,9 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{wnam, xan, Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::ethereum_events::EthAddress; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -132,6 +134,26 @@ where } } } + + /// Get the Ethereum address for wNam from storage, if possible + fn native_erc20_address(&self) -> Result { + match self.ctx.storage.read(&storage::native_erc20_key()) { + Ok((Some(bytes), _)) => { + Ok(EthAddress::try_from_slice(bytes.as_slice()).expect( + "Deserializing the Native ERC20 address from storage \ + shouldn't fail.", + )) + } + Ok(_) => Err(Error(eyre!( + "The Ethereum bridge storage is not initialized" + ))), + Err(e) => Err(Error(eyre!( + "Failed to read storage when fetching the native ERC20 \ + address with: {}", + e.to_string() + ))), + } + } } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -218,8 +240,8 @@ where // if we are going to mint wNam on Ethereum, the appropriate // amount of Nam must be escrowed in the Ethereum bridge VP's storage. - // TODO: We should look this address up from storage - if transfer.transfer.asset == wnam() { + let wnam_address = self.native_erc20_address()?; + if transfer.transfer.asset == wnam_address { // check that correct amount of Nam was put into escrow. return if transfer.gas_fee.payer == transfer.transfer.sender { if !self.check_nam_escrowed( @@ -319,6 +341,9 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use super::*; + use crate::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; @@ -326,6 +351,7 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::Storage; use crate::proto::Tx; + use crate::types::address::wnam; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use crate::types::ethereum_events::EthAddress; @@ -472,6 +498,32 @@ mod test_bridge_pool_vp { [account_key, token_key].into() } + /// Initialize some dummy storage for testing + fn setup_storage() -> Storage { + let mut storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + // a dummy config for testing + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + config.init_storage(&mut storage); + storage + } + /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, @@ -507,11 +559,7 @@ mod test_bridge_pool_vp { { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -855,11 +903,7 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -927,11 +971,7 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1004,11 +1044,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1101,11 +1137,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1198,19 +1230,18 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); // initialize the gas payers account - let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + let gas_payer_balance_key = + balance_key(&xan(), &established_address_1()); write_log .write( &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + Amount::from(BERTHA_WEALTH) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1285,8 +1316,8 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) From 3a4d967cbd3752e3c0dd6f9d467a074ca434fa80 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 14:46:21 +0100 Subject: [PATCH 1296/1995] [fix]: Removed settled todo comment --- shared/src/types/address.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 6c525ca819..e04bb44727 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -536,7 +536,6 @@ pub fn kartoffel() -> Address { /// Temporary helper for testing pub const fn wnam() -> EthAddress { - // TODO: Replace this with the real wNam ERC20 address once it exists // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" EthAddress([ 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, From 02edf48d7cc371821a9b388ae21c74b4c97c6eff Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 27 Oct 2022 16:46:31 +0200 Subject: [PATCH 1297/1995] [feat]: Added ability to escrow Nam to the bridge pool VP when wanting to mint wNam on Ethereum --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 353 +++++------------- shared/src/types/address.rs | 1 + 2 files changed, 94 insertions(+), 260 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 644e33993a..83f760e251 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -15,7 +15,6 @@ use std::collections::BTreeSet; use borsh::BorshDeserialize; use eyre::eyre; -use super::storage; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; @@ -25,9 +24,8 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; -use crate::types::address::{xan, Address, InternalAddress}; +use crate::types::address::{wnam, xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::ethereum_events::EthAddress; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -92,68 +90,39 @@ where expected_debit: Amount, expected_credit: Amount, ) -> bool { - let debited = self.account_balance_delta(payer_account); - let credited = self.account_balance_delta(escrow_account); - if debited.is_none() && credited.is_none() { + // check that the correct amount was deducted from the fee payer + if let Some(SignedAmount::Negative(amount)) = + self.account_balance_delta(payer_account) + { + if amount != expected_debit { + return false; + } + } else { + tracing::debug!("The account {} was not debited.", payer_account); return false; } - - match (debited, credited) { - ( - Some(SignedAmount::Negative(debit)), - Some(SignedAmount::Positive(credit)), - ) => debit == expected_debit && credit == expected_credit, - (Some(SignedAmount::Positive(_)), _) => { - tracing::debug!( - "The account {} was not debited.", - payer_account - ); - false - } - (_, Some(SignedAmount::Negative(_))) => { - tracing::debug!( - "The Ethereum bridge pool's escrow was not credited from \ - account {}.", - payer_account - ); - false - } - (None, _) => { - tracing::debug!( - "Could not calculate the balance delta for {}", - payer_account - ); - false - } - (_, None) => { - tracing::debug!( - "Could not calculate the balance delta for the Ethereum \ - bridge pool" - ); - false + // check that the correct amount was credited to escrow + if let Some(SignedAmount::Positive(amount)) = + self.account_balance_delta(escrow_account) + { + if amount != expected_credit { + return false; } + } else { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + return false; } + true } +} - /// Get the Ethereum address for wNam from storage, if possible - fn native_erc20_address(&self) -> Result { - match self.ctx.storage.read(&storage::native_erc20_key()) { - Ok((Some(bytes), _)) => { - Ok(EthAddress::try_from_slice(bytes.as_slice()).expect( - "Deserializing the Native ERC20 address from storage \ - shouldn't fail.", - )) - } - Ok(_) => Err(Error(eyre!( - "The Ethereum bridge storage is not initialized" - ))), - Err(e) => Err(Error(eyre!( - "Failed to read storage when fetching the native ERC20 \ - address with: {}", - e.to_string() - ))), - } - } +/// Check if a delta matches the delta given by a transfer +fn check_delta(delta: &(Address, Amount), transfer: &PendingTransfer) -> bool { + delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount } impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> @@ -194,7 +163,7 @@ where let pending_key = get_pending_key(&transfer); // check that transfer is not already in the pool - match (&self.ctx).read_pre(&pending_key) { + match (&self.ctx).read_pre_value::(&pending_key) { Ok(Some(_)) => { tracing::debug!( "Rejecting transaction as the transfer is already in the \ @@ -202,10 +171,11 @@ where ); return Ok(false); } - Err(_) => { + Err(e) => { return Err(eyre!( "Could not read the storage key associated with the \ - transfer." + transfer: {:?}", + e ) .into()); } @@ -240,39 +210,18 @@ where // if we are going to mint wNam on Ethereum, the appropriate // amount of Nam must be escrowed in the Ethereum bridge VP's storage. - let wnam_address = self.native_erc20_address()?; - if transfer.transfer.asset == wnam_address { + // TODO: We should look this address up from storage + if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if transfer.gas_fee.payer == transfer.transfer.sender { - if !self.check_nam_escrowed( - &transfer.gas_fee.payer, - &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, - transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( - &transfer.transfer.sender, - &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, - transfer.transfer.amount, - ) { - Ok(false) - } else { - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer \ - {:?}.", - transfer - ); - Ok(true) - } - } else if !self.check_nam_escrowed( + return if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount, + transfer.gas_fee.amount + transfer.transfer.amount, transfer.gas_fee.amount, ) || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), - transfer.transfer.amount, + transfer.gas_fee.amount + transfer.transfer.amount, transfer.transfer.amount, ) { Ok(false) @@ -284,7 +233,7 @@ where Ok(true) }; } else { - // check that the correct amount of gas fees were escrowed + // check that the correct amounnt of gas fees were escrowed if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, @@ -307,13 +256,12 @@ where (&escrow_key).try_into().expect("This should not fail"), (&owner_key).try_into().expect("This should not fail"), ) { - Ok(Some((addr, amt))) - if addr == transfer.transfer.sender - && amt == transfer.transfer.amount => {} - _ => { + Ok(Some(delta)) if check_delta(&delta, &transfer) => {} + other => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool." + escrowed into the Ethereum bridge pool: {:?}", + other ); return Ok(false); } @@ -341,9 +289,6 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use super::*; - use crate::ledger::eth_bridge::parameters::{ - Contracts, EthereumBridgeConfig, UpgradeableContract, - }; use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; @@ -351,7 +296,6 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::Storage; use crate::proto::Tx; - use crate::types::address::wnam; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use crate::types::ethereum_events::EthAddress; @@ -392,12 +336,6 @@ mod test_bridge_pool_vp { .expect("The token address decoding shouldn't fail") } - /// A sampled established address for tests - pub fn established_address_1() -> Address { - Address::decode("atest1v4ehgw36g56ngwpk8ppnzsf4xqeyvsf3xq6nxde5gseyys3nxgenvvfex5cnyd2rx9zrzwfctgx7sp") - .expect("The token address decoding shouldn't fail") - } - fn bertha_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -498,43 +436,22 @@ mod test_bridge_pool_vp { [account_key, token_key].into() } - /// Initialize some dummy storage for testing - fn setup_storage() -> Storage { - let mut storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); - // a dummy config for testing - let config = EthereumBridgeConfig { - min_confirmations: Default::default(), - contracts: Contracts { - native_erc20: wnam(), - bridge: UpgradeableContract { - address: EthAddress([42; 20]), - version: Default::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: Default::default(), - }, - }, - }; - config.init_storage(&mut storage); - storage - } - /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, storage: &'a Storage, write_log: &'a WriteLog, + keys_changed: &'a BTreeSet, + verifiers: &'a BTreeSet
, ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { Ctx::new( + &BRIDGE_POOL_ADDRESS, storage, write_log, tx, VpGasMeter::new(0u64), + keys_changed, + verifiers, VpCache::new(temp_dir(), 100usize), ) } @@ -559,7 +476,11 @@ mod test_bridge_pool_vp { { // setup let mut write_log = new_writelog(); - let storage = setup_storage(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -605,10 +526,16 @@ mod test_bridge_pool_vp { escrow_delta, ); keys_changed.append(&mut new_keys_changed); - + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -620,7 +547,6 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); - let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); match expect { Expect::True => assert!(res.expect("Test failed")), @@ -903,7 +829,11 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut write_log = new_writelog(); - let storage = setup_storage(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -945,10 +875,17 @@ mod test_bridge_pool_vp { SignedAmount::Positive(TOKENS.into()), ); keys_changed.append(&mut new_keys_changed); + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -960,7 +897,6 @@ mod test_bridge_pool_vp { .try_to_vec() .expect("Test failed"); - let verifiers = BTreeSet::default(); let res = vp.validate_tx(&signed, &keys_changed, &verifiers); assert!(!res.expect("Test failed")); } @@ -971,7 +907,11 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut write_log = new_writelog(); - let storage = setup_storage(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1006,12 +946,11 @@ mod test_bridge_pool_vp { keys_changed.insert( wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), ); - + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), }; - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); @@ -1044,7 +983,11 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = setup_storage(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1099,12 +1042,11 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), }; - let verifiers = BTreeSet::default(); - let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); let signed = SignedTxData { @@ -1137,7 +1079,11 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = setup_storage(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1191,124 +1137,11 @@ mod test_bridge_pool_vp { Amount::from(10).try_to_vec().expect("Test failed"), ) .expect("Test failed"); - - // create the data to be given to the vp - let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), - }; let verifiers = BTreeSet::default(); - - let to_sign = transfer.try_to_vec().expect("Test failed"); - let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); - let signed = SignedTxData { - data: Some(to_sign), - sig, - } - .try_to_vec() - .expect("Test failed"); - - let res = vp - .validate_tx(&signed, &keys_changed, &verifiers) - .expect("Test failed"); - assert!(!res); - } - - /// Test that we check escrowing Nam correctly when minting wNam - /// and the gas payer account is different from the transferring - /// account. - #[test] - fn test_mint_wnam_separate_gas_payer() { - // setup - let mut write_log = new_writelog(); - // initialize the eth bridge balance to 0 - let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); - write_log - .write( - &eb_account_key, - Amount::default().try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); - // initialize the gas payers account - let gas_payer_balance_key = - balance_key(&xan(), &established_address_1()); - write_log - .write( - &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH) - .try_to_vec() - .expect("Test failed"), - ) - .expect("Test failed"); - write_log.commit_tx(); - let storage = setup_storage(); - let tx = Tx::new(vec![], None); - - // the transfer to be added to the pool - let transfer = PendingTransfer { - transfer: TransferToEthereum { - asset: wnam(), - sender: bertha_address(), - recipient: EthAddress([1; 20]), - amount: 100.into(), - nonce: 1u64.into(), - }, - gas_fee: GasFee { - amount: 100.into(), - payer: established_address_1(), - }, - }; - - // add transfer to pool - let keys_changed = { - write_log - .write( - &get_pending_key(&transfer), - transfer.try_to_vec().unwrap(), - ) - .unwrap(); - BTreeSet::from([get_pending_key(&transfer)]) - }; - // We escrow 100 Nam into the bridge pool VP - // and 100 Nam in the Eth bridge VP - let account_key = balance_key(&xan(), &bertha_address()); - write_log - .write( - &account_key, - Amount::from(BERTHA_WEALTH - 100) - .try_to_vec() - .expect("Test failed"), - ) - .expect("Test failed"); - write_log - .write( - &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH - 100) - .try_to_vec() - .expect("Test failed"), - ) - .expect("Test failed"); - let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); - write_log - .write( - &bp_account_key, - Amount::from(ESCROWED_AMOUNT + 100) - .try_to_vec() - .expect("Test failed"), - ) - .expect("Test failed"); - write_log - .write( - &eb_account_key, - Amount::from(10).try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); - // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), }; - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index e04bb44727..6c525ca819 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -536,6 +536,7 @@ pub fn kartoffel() -> Address { /// Temporary helper for testing pub const fn wnam() -> EthAddress { + // TODO: Replace this with the real wNam ERC20 address once it exists // "DEADBEEF DEADBEEF DEADBEEF DEADBEEF DEADBEEF" EthAddress([ 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, 190, 239, 222, 173, From 242bb218b9cb96d2138a2918838db02b3a6af3a1 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 10:03:10 +0100 Subject: [PATCH 1298/1995] [fix]: Fixed bugs in escrowing Nam when minting wNam and checking balance deltas. Covered with a test --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 208 +++++++++++++++--- 1 file changed, 181 insertions(+), 27 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 83f760e251..02440f0cdd 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -90,33 +90,47 @@ where expected_debit: Amount, expected_credit: Amount, ) -> bool { - // check that the correct amount was deducted from the fee payer - if let Some(SignedAmount::Negative(amount)) = - self.account_balance_delta(payer_account) - { - if amount != expected_debit { - return false; - } - } else { - tracing::debug!("The account {} was not debited.", payer_account); + let debited = self.account_balance_delta(payer_account); + let credited = self.account_balance_delta(escrow_account); + if debited.is_none() && credited.is_none() { return false; } - // check that the correct amount was credited to escrow - if let Some(SignedAmount::Positive(amount)) = - self.account_balance_delta(escrow_account) - { - if amount != expected_credit { - return false; + + match (debited, credited) { + ( + Some(SignedAmount::Negative(debit)), + Some(SignedAmount::Positive(credit)), + ) => debit == expected_debit && credit == expected_credit, + (Some(SignedAmount::Positive(_)), _) => { + tracing::debug!( + "The account {} was not debited.", + payer_account + ); + false + } + (_, Some(SignedAmount::Negative(_))) => { + tracing::debug!( + "The Ethereum bridge pool's escrow was not credited from \ + account {}.", + payer_account + ); + false + } + (None, _) => { + tracing::debug!( + "Could not calculate the balance delta for {}", + payer_account + ); + false + } + (_, None) => { + tracing::debug!( + "Could not calculate the balance delta for the Ethereum \ + bridge pool" + ); + false } - } else { - tracing::debug!( - "The Ethereum bridge pool's escrow was not credited from \ - account {}.", - payer_account - ); - return false; } - true } } @@ -213,15 +227,36 @@ where // TODO: We should look this address up from storage if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if !self.check_nam_escrowed( + return if transfer.gas_fee.payer == transfer.transfer.sender { + if !self.check_nam_escrowed( + &transfer.gas_fee.payer, + &BRIDGE_POOL_ADDRESS, + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, + ) || !self.check_nam_escrowed( + &transfer.transfer.sender, + &Address::Internal(InternalAddress::EthBridge), + transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, + ) { + Ok(false) + } else { + tracing::info!( + "The Ethereum bridge pool VP accepted the transfer \ + {:?}.", + transfer + ); + Ok(true) + } + } else if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.gas_fee.amount, transfer.gas_fee.amount, ) || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, + transfer.transfer.amount, transfer.transfer.amount, ) { Ok(false) @@ -233,7 +268,7 @@ where Ok(true) }; } else { - // check that the correct amounnt of gas fees were escrowed + // check that the correct amount of gas fees were escrowed if !self.check_nam_escrowed( &transfer.gas_fee.payer, &BRIDGE_POOL_ADDRESS, @@ -336,6 +371,12 @@ mod test_bridge_pool_vp { .expect("The token address decoding shouldn't fail") } + /// A sampled established address for tests + pub fn established_address_1() -> Address { + Address::decode("atest1v4ehgw36g56ngwpk8ppnzsf4xqeyvsf3xq6nxde5gseyys3nxgenvvfex5cnyd2rx9zrzwfctgx7sp") + .expect("The token address decoding shouldn't fail") + } + fn bertha_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -1157,4 +1198,117 @@ mod test_bridge_pool_vp { .expect("Test failed"); assert!(!res); } + + /// Test that we check escrowing Nam correctly when minting wNam + /// and the gas payer account is different from the transferring + /// account. + #[test] + fn test_mint_wnam_separate_gas_payer() { + // setup + let mut write_log = new_writelog(); + // initialize the eth bridge balance to 0 + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + write_log + .write( + &eb_account_key, + Amount::default().try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + // initialize the gas payers account + let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + write_log.commit_tx(); + let storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + let tx = Tx::new(vec![], None); + + // the transfer to be added to the pool + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + sender: bertha_address(), + recipient: EthAddress([1; 20]), + amount: 100.into(), + nonce: 1u64.into(), + }, + gas_fee: GasFee { + amount: 100.into(), + payer: established_address_1(), + }, + }; + + // add transfer to pool + let keys_changed = { + write_log + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }; + // We escrow 100 Nam into the bridge pool VP + // and 100 Nam in the Eth bridge VP + let account_key = balance_key(&xan(), &bertha_address()); + write_log + .write( + &account_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &gas_payer_balance_key, + Amount::from(BERTHA_WEALTH - 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + write_log + .write( + &bp_account_key, + Amount::from(ESCROWED_AMOUNT + 100) + .try_to_vec() + .expect("Test failed"), + ) + .expect("Test failed"); + write_log + .write( + &eb_account_key, + Amount::from(10).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // create the data to be given to the vp + let vp = BridgePoolVp { + ctx: setup_ctx(&tx, &storage, &write_log), + }; + let verifiers = BTreeSet::default(); + + let to_sign = transfer.try_to_vec().expect("Test failed"); + let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); + let signed = SignedTxData { + data: Some(to_sign), + sig, + } + .try_to_vec() + .expect("Test failed"); + + let res = vp + .validate_tx(&signed, &keys_changed, &verifiers) + .expect("Test failed"); + assert!(!res); + } } From 958e60a53d2a028571195c766d7cd88f9004e301 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:33:25 +0100 Subject: [PATCH 1299/1995] [feat]: The ethereum bridge vp should allow nam to be escrowed in its xan account --- shared/src/ledger/eth_bridge/vp/mod.rs | 62 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0195e1ac20..5f79a42996 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,6 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -11,9 +12,9 @@ use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::storage::Key; -use crate::types::token::Amount; +use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; /// Validity predicate for the Ethereum bridge @@ -27,6 +28,56 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } +impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// If the bridge's escrow key was changed, we check + /// that the balance increased and that the bridge pool + /// VP has been triggered. The bridge pool VP will carry + /// out the rest of the checks. + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_pre: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the Ethereum bridge VP's balance from \ + storage" + ); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; + }; + + // The amount escrowed should increase. + if escrow_pre < escrow_post { + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + } else { + tracing::info!( + "A normal tx cannot decrease the amount of Nam escrowed in \ + the Ethereum bridge" + ); + false + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -50,6 +101,7 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount + /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -67,6 +119,12 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); + + // first check if Nam is being escrowed + if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { + return Ok(self.check_escrow(verifiers)); + } + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), From 5f0cd62c6a3a0f7974d2e15469f1cc63ad5238ea Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:11:19 +0100 Subject: [PATCH 1300/1995] [fix]: Formatting --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 02440f0cdd..66eeb37c5c 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1216,11 +1216,14 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); // initialize the gas payers account - let gas_payer_balance_key = balance_key(&xan(), &established_address_1()); + let gas_payer_balance_key = + balance_key(&xan(), &established_address_1()); write_log .write( &gas_payer_balance_key, - Amount::from(BERTHA_WEALTH).try_to_vec().expect("Test failed"), + Amount::from(BERTHA_WEALTH) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); write_log.commit_tx(); @@ -1303,8 +1306,8 @@ mod test_bridge_pool_vp { data: Some(to_sign), sig, } - .try_to_vec() - .expect("Test failed"); + .try_to_vec() + .expect("Test failed"); let res = vp .validate_tx(&signed, &keys_changed, &verifiers) From 354160012f31303bd1aa85c147ed7987cb6022bb Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 11:56:09 +0100 Subject: [PATCH 1301/1995] [fix]: Added some errors --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 39 ++++++----------- shared/src/ledger/eth_bridge/vp/mod.rs | 43 +++++++++++-------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 66eeb37c5c..322e63b1ea 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -89,24 +89,21 @@ where escrow_account: &Address, expected_debit: Amount, expected_credit: Amount, - ) -> bool { + ) -> Result { let debited = self.account_balance_delta(payer_account); let credited = self.account_balance_delta(escrow_account); - if debited.is_none() && credited.is_none() { - return false; - } match (debited, credited) { ( Some(SignedAmount::Negative(debit)), Some(SignedAmount::Positive(credit)), - ) => debit == expected_debit && credit == expected_credit, + ) => Ok(debit == expected_debit && credit == expected_credit), (Some(SignedAmount::Positive(_)), _) => { tracing::debug!( "The account {} was not debited.", payer_account ); - false + Ok(false) } (_, Some(SignedAmount::Negative(_))) => { tracing::debug!( @@ -114,22 +111,12 @@ where account {}.", payer_account ); - false - } - (None, _) => { - tracing::debug!( - "Could not calculate the balance delta for {}", - payer_account - ); - false - } - (_, None) => { - tracing::debug!( - "Could not calculate the balance delta for the Ethereum \ - bridge pool" - ); - false + Ok(false) } + (None, _) | (_, None) => Err(Error(eyre!( + "Could not calculate the balance delta for {}", + payer_account + ))), } } } @@ -233,12 +220,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount + transfer.transfer.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.gas_fee.amount + transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -253,12 +240,12 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) || !self.check_nam_escrowed( + )? || !self.check_nam_escrowed( &transfer.transfer.sender, &Address::Internal(InternalAddress::EthBridge), transfer.transfer.amount, transfer.transfer.amount, - ) { + )? { Ok(false) } else { tracing::info!( @@ -274,7 +261,7 @@ where &BRIDGE_POOL_ADDRESS, transfer.gas_fee.amount, transfer.gas_fee.amount, - ) { + )? { return Ok(false); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 5f79a42996..2d9f40ca3b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -38,42 +38,47 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + fn check_escrow( + &self, + verifiers: &BTreeSet
, + ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| Error(eyre!("Couldn't deserialize a balance from storage")), + )? } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return false; - }; - let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's balance \ - after applying tx" - ); - return false; + return Ok(false); }; + let escrow_post: Amount = + if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + BorshDeserialize::try_from_slice(bytes.as_slice()).expect( + "Deserializing the balance of the Ethereum bridge VP from \ + storage shouldn't fail", + ) + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's \ + balance after applying tx" + ); + return Ok(false); + }; // The amount escrowed should increase. if escrow_pre < escrow_post { - verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - false + Ok(false) } } } @@ -122,7 +127,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return Ok(self.check_escrow(verifiers)); + return self.check_escrow(verifiers); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { From d716f3d3b5072bde7c6f271ec2b3807d126bd35f Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 1 Nov 2022 11:15:38 +0100 Subject: [PATCH 1302/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2d9f40ca3b..d3c2b279a3 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -44,7 +44,7 @@ where ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) + self.ctx.read_bytes_pre(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( |_| Error(eyre!("Couldn't deserialize a balance from storage")), @@ -57,7 +57,7 @@ where return Ok(false); }; let escrow_post: Amount = - if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) { BorshDeserialize::try_from_slice(bytes.as_slice()).expect( "Deserializing the balance of the Ethereum bridge VP from \ storage shouldn't fail", From 19c69a7f9255d27bef723ad02d5f864c842967fe Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 14:27:50 +0100 Subject: [PATCH 1303/1995] [fix]: Upgrading to version v0.8 --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 35 +++++++++++++++---- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 322e63b1ea..6c02d60a60 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -977,7 +977,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -1073,7 +1079,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); @@ -1168,7 +1180,13 @@ mod test_bridge_pool_vp { let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log, &keys_changed, &verifiers), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; let to_sign = transfer.try_to_vec().expect("Test failed"); @@ -1280,12 +1298,17 @@ mod test_bridge_pool_vp { Amount::from(10).try_to_vec().expect("Test failed"), ) .expect("Test failed"); - + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { - ctx: setup_ctx(&tx, &storage, &write_log), + ctx: setup_ctx( + &tx, + &storage, + &write_log, + &keys_changed, + &verifiers, + ), }; - let verifiers = BTreeSet::default(); let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index d3c2b279a3..34d09e03c1 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -9,7 +9,7 @@ use eyre::{eyre, Result}; use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; -use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{xan, Address, InternalAddress}; From 0ff46274d5d5bbaec993c6cc7e0f9e9ac0df32c4 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:33:25 +0100 Subject: [PATCH 1304/1995] [feat]: The ethereum bridge vp should allow nam to be escrowed in its xan account --- shared/src/ledger/eth_bridge/vp/mod.rs | 50 ++++++++++++-------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 34d09e03c1..2079cea87c 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -9,7 +9,7 @@ use eyre::{eyre, Result}; use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; -use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{xan, Address, InternalAddress}; @@ -38,47 +38,42 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow( - &self, - verifiers: &BTreeSet
, - ) -> Result { + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = - self.ctx.read_bytes_pre(&escrow_key) + self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( - |_| Error(eyre!("Couldn't deserialize a balance from storage")), - )? + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return Ok(false); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; }; - let escrow_post: Amount = - if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()).expect( - "Deserializing the balance of the Ethereum bridge VP from \ - storage shouldn't fail", - ) - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's \ - balance after applying tx" - ); - return Ok(false); - }; // The amount escrowed should increase. if escrow_pre < escrow_post { - Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - Ok(false) + false } } } @@ -127,7 +122,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return self.check_escrow(verifiers); + return Ok(self.check_escrow(verifiers)); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { @@ -217,8 +212,7 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// and by how much it decreased, which should be authorizing the balance -/// change. +/// as by how much it decreased, which should be authorizing the balance change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, From 76c00be620c8a203f9a4f934dfee101da621b55b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 11:56:09 +0100 Subject: [PATCH 1305/1995] [fix]: Added some errors --- shared/src/ledger/eth_bridge/vp/mod.rs | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2079cea87c..0b27b03fc0 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -38,42 +38,47 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { + fn check_escrow( + &self, + verifiers: &BTreeSet
, + ) -> Result { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| Error(eyre!("Couldn't deserialize a balance from storage")), + )? } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return false; - }; - let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's balance \ - after applying tx" - ); - return false; + return Ok(false); }; + let escrow_post: Amount = + if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { + BorshDeserialize::try_from_slice(bytes.as_slice()).expect( + "Deserializing the balance of the Ethereum bridge VP from \ + storage shouldn't fail", + ) + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's \ + balance after applying tx" + ); + return Ok(false); + }; // The amount escrowed should increase. if escrow_pre < escrow_post { - verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) + Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - false + Ok(false) } } } @@ -122,7 +127,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return Ok(self.check_escrow(verifiers)); + return self.check_escrow(verifiers); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { From f376cabaa86246f3a92db1d232db4d4e684cf432 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 1 Nov 2022 11:15:38 +0100 Subject: [PATCH 1306/1995] Update shared/src/ledger/eth_bridge/vp/mod.rs Co-authored-by: James --- shared/src/ledger/eth_bridge/vp/mod.rs | 43 ++++++++++++-------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 0b27b03fc0..f166dd5d87 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -38,47 +38,42 @@ where /// that the balance increased and that the bridge pool /// VP has been triggered. The bridge pool VP will carry /// out the rest of the checks. - fn check_escrow( - &self, - verifiers: &BTreeSet
, - ) -> Result { + fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { let escrow_key = balance_key(&xan(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_pre(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( - |_| Error(eyre!("Couldn't deserialize a balance from storage")), - )? + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") } else { tracing::debug!( "Could not retrieve the Ethereum bridge VP's balance from \ storage" ); - return Ok(false); + return false; + }; + let escrow_post: Amount = if let Ok(Some(bytes)) = + self.ctx.read_post(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .expect("Deserializing a balance from storage shouldn't fail") + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's balance \ + after applying tx" + ); + return false; }; - let escrow_post: Amount = - if let Ok(Some(bytes)) = self.ctx.read_post(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()).expect( - "Deserializing the balance of the Ethereum bridge VP from \ - storage shouldn't fail", - ) - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's \ - balance after applying tx" - ); - return Ok(false); - }; // The amount escrowed should increase. if escrow_pre < escrow_post { - Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) + verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) } else { tracing::info!( "A normal tx cannot decrease the amount of Nam escrowed in \ the Ethereum bridge" ); - Ok(false) + false } } } @@ -127,7 +122,7 @@ where // first check if Nam is being escrowed if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return self.check_escrow(verifiers); + return Ok(self.check_escrow(verifiers)); } let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { From 5203d8bcef9d531777073971cc4829e918ae05be Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 15:14:09 +0200 Subject: [PATCH 1307/1995] [feat]: Write the ethereum bridge config params on genesis --- apps/src/lib/config/ethereum_bridge/params.rs | 85 ++++++------------ apps/src/lib/config/genesis.rs | 88 ++++++++----------- apps/src/lib/node/ledger/shell/init_chain.rs | 65 +++++++++----- apps/src/lib/node/ledger/shell/mod.rs | 4 + apps/src/lib/node/ledger/storage/rocksdb.rs | 83 ++++++++++++++++- .../src/ledger/parameters/ethereum_bridge.rs | 74 ++++++++++++++++ shared/src/ledger/parameters/mod.rs | 1 + shared/src/ledger/storage/mockdb.rs | 54 ++++++++++++ shared/src/ledger/storage/mod.rs | 54 ++++++++++++ 9 files changed, 377 insertions(+), 131 deletions(-) create mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs index 0ab56b0b47..d76b6964fd 100644 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ b/apps/src/lib/config/ethereum_bridge/params.rs @@ -1,33 +1,22 @@ //! Blockchain-level parameters for the configuration of the Ethereum bridge. -use std::num::NonZeroU64; - +use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -/// Represents a configuration value for an Ethereum address. -/// -/// For instance: -/// `0x6B175474E89094C44Da98b954EedeAC495271d0F` -#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct Address(String); - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - /// Represents chain parameters for the Ethereum bridge. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct GenesisConfig { /// Minimum number of confirmations needed to trust an Ethereum branch. /// This must be at least one. @@ -39,7 +28,16 @@ pub struct GenesisConfig { /// Represents all the Ethereum contracts that need to be directly know about by /// validators. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] pub struct Contracts { /// The Ethereum address of the ERC20 contract that represents this chain's /// native token. @@ -50,33 +48,10 @@ pub struct Contracts { pub governance: UpgradeableContract, } -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive(Clone, Copy, Eq, PartialEq, Debug, Deserialize, Serialize)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: Address, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} - #[cfg(test)] mod tests { use eyre::Result; + use namada::ledger::parameters::ethereum_bridge::ContractVersion; use super::*; @@ -90,17 +65,11 @@ mod tests { contracts: Contracts { native_erc20: EthAddress([42; 20]), bridge: UpgradeableContract { - address: Address( - "0x237d915037A1ba79365E84e2b8574301B6D25Ea0" - .to_string(), - ), + address: EthAddress([23; 20]), version: ContractVersion::default(), }, governance: UpgradeableContract { - address: Address( - "0x308728EEa73538d0edEfd95EF148Eb678F71c71D" - .to_string(), - ), + address: EthAddress([18; 20]), version: ContractVersion::default(), }, }, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 4498a345d2..792d39bac1 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,11 +6,9 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; -use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::ledger::treasury::parameters::TreasuryParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -19,6 +17,8 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; +use crate::config::ethereum_bridge; + /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -27,12 +27,12 @@ pub mod genesis_config { use std::path::Path; use std::str::FromStr; - use hex; + use data_encoding::HEXLOWER; + use eyre::Context; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::ledger::treasury::parameters::TreasuryParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -42,7 +42,7 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, + ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; @@ -52,12 +52,12 @@ pub mod genesis_config { impl HexString { pub fn to_bytes(&self) -> Result, HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; Ok(bytes) } pub fn to_sha256_bytes(&self) -> Result<[u8; 32], HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; let slice = bytes.as_slice(); let array: [u8; 32] = slice.try_into()?; Ok(array) @@ -78,15 +78,15 @@ pub mod genesis_config { #[derive(Error, Debug)] pub enum HexKeyError { #[error("Invalid hex string: {0:?}")] - InvalidHexString(hex::FromHexError), + InvalidHexString(data_encoding::DecodeError), #[error("Invalid sha256 checksum: {0}")] InvalidSha256(TryFromSliceError), #[error("Invalid public key: {0}")] InvalidPublicKey(ParsePublicKeyError), } - impl From for HexKeyError { - fn from(err: hex::FromHexError) -> Self { + impl From for HexKeyError { + fn from(err: data_encoding::DecodeError) -> Self { Self::InvalidHexString(err) } } @@ -121,10 +121,9 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, - // Treasury parameters - pub treasury_params: TreasuryParamasConfig, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: + Option, // Wasm definitions pub wasm: HashMap, } @@ -137,9 +136,12 @@ pub mod genesis_config { // Maximum size of proposal in kibibytes (KiB) // XXX: u64 doesn't work with toml-rs! pub max_proposal_code_size: u64, - // Proposal period length in epoch + // Minimum proposal period length in epochs // XXX: u64 doesn't work with toml-rs! pub min_proposal_period: u64, + // Maximum proposal period length in epochs + // XXX: u64 doesn't work with toml-rs! + pub max_proposal_period: u64, // Maximum number of characters in the proposal content // XXX: u64 doesn't work with toml-rs! pub max_proposal_content_size: u64, @@ -148,13 +150,6 @@ pub mod genesis_config { pub min_proposal_grace_epochs: u64, } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct TreasuryParamasConfig { - // Maximum funds that can be moved from treasury in a single transfer - // XXX: u64 doesn't work with toml-rs! - pub max_proposal_fund_transfer: u64, - } - /// Validator pre-genesis configuration can be created with client utils /// `init-genesis-validator` command and added to a genesis for /// `init-network` cmd and that can be subsequently read by `join-network` @@ -197,16 +192,6 @@ pub mod genesis_config { pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, - /// Matchmaker account's alias, if any - pub matchmaker_account: Option, - /// Path to a matchmaker WASM program, if any - pub matchmaker_code: Option, - /// Path to a transaction WASM code used by the matchmaker, if any - pub matchmaker_tx: Option, - /// Is this validator running a seed intent gossip node? A seed node is - /// not part of the gossipsub where intents are being propagated and - /// hence cannot run matchmakers - pub intent_gossip_seed: Option, /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, @@ -570,6 +555,7 @@ pub mod genesis_config { min_proposal_fund: config.gov_params.min_proposal_fund, max_proposal_code_size: config.gov_params.max_proposal_code_size, min_proposal_period: config.gov_params.min_proposal_period, + max_proposal_period: config.gov_params.max_proposal_period, max_proposal_content_size: config .gov_params .max_proposal_content_size, @@ -578,10 +564,6 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; - let treasury_params = TreasuryParams { - max_proposal_fund_transfer: 10_000, - }; - let pos_params = PosParams { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, @@ -615,9 +597,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( @@ -629,7 +624,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()) } } @@ -646,7 +641,7 @@ pub struct Genesis { pub gov_params: GovParams, pub treasury_params: TreasuryParams, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { @@ -838,13 +833,6 @@ pub fn genesis() -> Genesis { public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; - let matchmaker = EstablishedAccount { - address: wallet::defaults::matchmaker_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::matchmaker_keypair().ref_to()), - storage: HashMap::default(), - }; let implicit_accounts = vec![ImplicitAccount { public_key: wallet::defaults::daewon_keypair().ref_to(), }]; @@ -871,10 +859,6 @@ pub fn genesis() -> Genesis { default_key_tokens, ), ((&validator.account_key).into(), default_key_tokens), - ( - matchmaker.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), ]); let token_accounts = address::tokens() .into_iter() @@ -888,7 +872,7 @@ pub fn genesis() -> Genesis { Genesis { genesis_time: DateTimeUtc::now(), validators: vec![validator], - established_accounts: vec![albert, bertha, christel, matchmaker], + established_accounts: vec![albert, bertha, christel], implicit_accounts, token_accounts, parameters, diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a65bccf60f..adcc1aae6a 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,17 +2,23 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; +use namada::ledger::{ibc, pos}; use namada::ledger::parameters::Parameters; use namada::ledger::pos::PosParams; use namada::types::key::*; +use namada::types::time::{DateTimeUtc, TimeZone, Utc}; +use namada::types::token; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; -use super::queries::QueriesExt; use super::*; +use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; +use crate::facade::tower_abci::{request, response}; use crate::wasm_loader; impl Shell @@ -64,11 +70,6 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.treasury_params.init_storage(&mut self.storage); - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - config.init_storage(&mut self.storage); - } // Depends on parameters being initialized self.storage @@ -96,7 +97,10 @@ where genesis.token_accounts, &mut vp_code_cache, ); - + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + self.configure_ethereuem_bridge(config); + } // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set @@ -121,10 +125,16 @@ where storage, } in 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) - }); + 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")] @@ -188,9 +198,10 @@ where balances, } in 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 @@ -239,6 +250,7 @@ where &self.wasm_dir, &validator.validator_vp_code_path, ) + .unwrap() }, ); @@ -320,13 +332,6 @@ where ); ibc::init_genesis_storage(&mut self.storage); - let evidence_params = self - .storage - .get_evidence_params(¶meters.epoch_duration, 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 validators { let mut abci_validator = abci::ValidatorUpdate::default(); @@ -344,6 +349,26 @@ where } response } + + /// Set the parameters for the Ethereum bridge + fn configure_ethereuem_bridge( + &mut self, + config: ethereum_bridge::params::GenesisConfig, + ) { + let ethereum_bridge::params::GenesisConfig { + min_confirmations, + contracts: + ethereum_bridge::params::Contracts { + native_erc20, + bridge, + governance, + }, + } = config; + self.storage.min_confirmations = Some(min_confirmations); + self.storage.native_erc20 = Some(native_erc20); + self.storage.bridge_contract = Some(bridge); + self.storage.governance_contract = Some(governance); + } } trait HashMapExt diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ae8aade9b4..cfed6feea3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,6 +1121,10 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 5085663c41..0bf05c1cbe 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,7 +5,13 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` +//! - `tx_queue` +//! - `min_confirmations`: Minimum number of confirmations needed for the +//! Ethereum bridge to consider an event final. +//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents +//! this chain's native token. +//! - `bridge_contract`: The Ethereum address of the bridge contract. +//! - `governance_contract`: The Ethereum address of the governance contract. //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -32,11 +38,15 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; +use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -300,6 +310,61 @@ impl DB for RocksDB { return Ok(None); } }; + let min_confirmations: Option = match self + .0 + .get("min_confirmations") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the minimum confirmations from the DB" + ); + return Ok(None); + } + }; + let native_erc20: Option = match self + .0 + .get("native_erc20") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum address for wrapped Nam from \ + the DB" + ); + return Ok(None); + } + }; + let bridge_contract: Option = match self + .0 + .get("bridge_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum bridge contract address from \ + the DB" + ); + return Ok(None); + } + }; + let governance_contract: Option = match self + .0 + .get("governance_contract") + .map_err(|e| Error::DBError(e.into_string()))? + { + Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, + None => { + tracing::error!( + "Couldn't load the Ethereum governance contract from the \ + DB" + ); + return Ok(None); + } + }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -402,6 +467,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -424,6 +493,10 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -452,6 +525,10 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); + batch.put("min_confirmations", types::encode(&min_confirmations)); + batch.put("native_erc20", types::encode(&native_erc20)); + batch.put("bridge_contract", types::encode(&bridge_contract)); + batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -994,6 +1071,10 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs new file mode 100644 index 0000000000..1e54207f6e --- /dev/null +++ b/shared/src/ledger/parameters/ethereum_bridge.rs @@ -0,0 +1,74 @@ +//! Parameters for configuring the Ethereum bridge +use std::num::NonZeroU64; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::ethereum_events::EthAddress; + +/// Represents a configuration value for the minimum number of +/// confirmations an Ethereum event must reach before it can be acted on. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct MinimumConfirmations(NonZeroU64); + +impl Default for MinimumConfirmations { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be violated + // is if we construct values of this type using 0 as argument. + Self(unsafe { NonZeroU64::new_unchecked(100) }) + } +} + +/// Represents a configuration value for the version of a contract that can be +/// upgraded. Starts from 1. +#[derive( + Clone, + Copy, + Eq, + PartialEq, + Debug, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +#[repr(transparent)] +pub struct ContractVersion(NonZeroU64); + +impl Default for ContractVersion { + fn default() -> Self { + // SAFETY: The only way the API contract of `NonZeroU64` can be + // violated is if we construct values of this type using 0 as + // argument. + Self(unsafe { NonZeroU64::new_unchecked(1) }) + } +} + +/// Represents an Ethereum contract that may be upgraded. +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + BorshSerialize, + BorshDeserialize, +)] +pub struct UpgradeableContract { + /// The Ethereum address of the contract. + pub address: EthAddress, + /// The version of the contract. Starts from 1. + pub version: ContractVersion, +} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4891818dc0..0ec554dd80 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,4 +1,5 @@ //! Protocol parameters +pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 234cad4498..29a19a9760 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,7 +12,11 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,6 +77,34 @@ impl DB for MockDB { } None => return Ok(None), }; + let min_confirmations: Option = + match self.0.borrow().get("min_confirmations") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let native_erc20: Option = + match self.0.borrow().get("native_erc20") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let bridge_contract: Option = + match self.0.borrow().get("bridge_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; + let governance_contract: Option = + match self.0.borrow().get("governance_contract") { + Some(bytes) => { + types::decode(bytes).map_err(Error::CodingError)? + } + None => return Ok(None), + }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -153,6 +185,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, })) } _ => Err(Error::Temporary { @@ -175,6 +211,10 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract, + governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -186,6 +226,20 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); + self.0.borrow_mut().insert( + "min_confirmations".into(), + types::encode(&min_confirmations), + ); + self.0 + .borrow_mut() + .insert("native_erc20".into(), types::encode(&native_erc20)); + self.0 + .borrow_mut() + .insert("bridge_contract".into(), types::encode(&bridge_contract)); + self.0.borrow_mut().insert( + "goverenance_contract".into(), + types::encode(&governance_contract), + ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 805caa6348..0411375912 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,6 +17,9 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; +use crate::ledger::parameters::ethereum_bridge::{ + MinimumConfirmations, UpgradeableContract, +}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -28,6 +31,7 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -69,6 +73,16 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block storage data @@ -128,6 +142,16 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// The block's state to write into the database. @@ -153,6 +177,16 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, + /// Minimum number of confirmations needed for the + /// Ethereum bridge to consider an event final + pub min_confirmations: Option, + /// The Ethereum address of the ERC20 contract that represents this chain's + /// native token. + pub native_erc20: Option, + /// The Ethereum address of the bridge contract. + pub bridge_contract: Option, + /// The Ethereum address of the governance contract. + pub governance_contract: Option, } /// A database backend. @@ -302,6 +336,10 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } @@ -319,6 +357,10 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, + min_confirmations, + native_erc20, + bridge_contract: bridge, + governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -335,6 +377,10 @@ where { self.tx_queue = tx_queue; } + self.min_confirmations = min_confirmations; + self.native_erc20 = native_erc20; + self.bridge_contract = bridge; + self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -366,6 +412,10 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, + min_confirmations: self.min_confirmations, + native_erc20: self.native_erc20.clone(), + bridge_contract: self.bridge_contract.clone(), + governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -922,6 +972,10 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + min_confirmations: None, + native_erc20: None, + bridge_contract: None, + governance_contract: None, } } } From cde386f10343a73dedf0f469f73b787d1b8235c5 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 28 Oct 2022 18:52:47 +0200 Subject: [PATCH 1308/1995] [feat]: Refactored the eth bridge params to live under the bridge vp storage --- apps/src/lib/config/ethereum_bridge/params.rs | 83 ------------------- apps/src/lib/config/genesis.rs | 10 +-- apps/src/lib/node/ledger/shell/init_chain.rs | 31 ++----- apps/src/lib/node/ledger/shell/mod.rs | 4 - apps/src/lib/node/ledger/storage/rocksdb.rs | 83 +------------------ .../src/ledger/parameters/ethereum_bridge.rs | 74 ----------------- shared/src/ledger/parameters/mod.rs | 1 - shared/src/ledger/storage/mockdb.rs | 54 ------------ shared/src/ledger/storage/mod.rs | 54 ------------ 9 files changed, 11 insertions(+), 383 deletions(-) delete mode 100644 apps/src/lib/config/ethereum_bridge/params.rs delete mode 100644 shared/src/ledger/parameters/ethereum_bridge.rs diff --git a/apps/src/lib/config/ethereum_bridge/params.rs b/apps/src/lib/config/ethereum_bridge/params.rs deleted file mode 100644 index d76b6964fd..0000000000 --- a/apps/src/lib/config/ethereum_bridge/params.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Blockchain-level parameters for the configuration of the Ethereum bridge. -use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; -use namada::types::ethereum_events::EthAddress; -use serde::{Deserialize, Serialize}; - -/// Represents chain parameters for the Ethereum bridge. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct GenesisConfig { - /// Minimum number of confirmations needed to trust an Ethereum branch. - /// This must be at least one. - pub min_confirmations: MinimumConfirmations, - /// The addresses of the Ethereum contracts that need to be directly known - /// by validators. - pub contracts: Contracts, -} - -/// Represents all the Ethereum contracts that need to be directly know about by -/// validators. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct Contracts { - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: EthAddress, - /// The Ethereum address of the bridge contract. - pub bridge: UpgradeableContract, - /// The Ethereum address of the governance contract. - pub governance: UpgradeableContract, -} - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::parameters::ethereum_bridge::ContractVersion; - - use super::*; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = GenesisConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: GenesisConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 792d39bac1..717d4bf01f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,6 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; @@ -17,8 +18,6 @@ use namada::types::key::*; use namada::types::time::DateTimeUtc; use namada::types::{storage, token}; -use crate::config::ethereum_bridge; - /// Genesis configuration file format pub mod genesis_config { use std::array::TryFromSliceError; @@ -42,7 +41,7 @@ pub mod genesis_config { use thiserror::Error; use super::{ - ethereum_bridge, EstablishedAccount, Genesis, ImplicitAccount, + EstablishedAccount, EthereumBridgeConfig, Genesis, ImplicitAccount, TokenAccount, Validator, }; use crate::cli; @@ -122,8 +121,7 @@ pub mod genesis_config { // Governance parameters pub gov_params: GovernanceParamsConfig, // Ethereum bridge config - pub ethereum_bridge_params: - Option, + pub ethereum_bridge_params: Option, // Wasm definitions pub wasm: HashMap, } @@ -641,7 +639,7 @@ pub struct Genesis { pub gov_params: GovParams, pub treasury_params: TreasuryParams, // Ethereum bridge config - pub ethereum_bridge_params: Option, + pub ethereum_bridge_params: Option, } impl Genesis { diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index adcc1aae6a..394d151855 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -14,7 +14,6 @@ use namada::types::token; use sha2::{Digest, Sha256}; use super::*; -use crate::config::ethereum_bridge; use crate::facade::tendermint_proto::abci; use crate::facade::tendermint_proto::crypto::PublicKey as TendermintPublicKey; use crate::facade::tendermint_proto::google::protobuf; @@ -70,6 +69,11 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); + genesis.treasury_params.init_storage(&mut self.storage); + // configure the Ethereum bridge if the configuration is set. + if let Some(config) = genesis.ethereum_bridge_params { + config.init_storage(&mut self.storage); + } // Depends on parameters being initialized self.storage @@ -97,10 +101,7 @@ where genesis.token_accounts, &mut vp_code_cache, ); - // configure the Ethereum bridge if the configuration is set. - if let Some(config) = genesis.ethereum_bridge_params { - self.configure_ethereuem_bridge(config); - } + // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set @@ -349,26 +350,6 @@ where } response } - - /// Set the parameters for the Ethereum bridge - fn configure_ethereuem_bridge( - &mut self, - config: ethereum_bridge::params::GenesisConfig, - ) { - let ethereum_bridge::params::GenesisConfig { - min_confirmations, - contracts: - ethereum_bridge::params::Contracts { - native_erc20, - bridge, - governance, - }, - } = config; - self.storage.min_confirmations = Some(min_confirmations); - self.storage.native_erc20 = Some(native_erc20); - self.storage.bridge_contract = Some(bridge); - self.storage.governance_contract = Some(governance); - } } trait HashMapExt diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index cfed6feea3..ae8aade9b4 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1121,10 +1121,6 @@ mod test_utils { next_epoch_min_start_time: DateTimeUtc::now(), address_gen: &address_gen, tx_queue: &shell.storage.tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }) .expect("Test failed"); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 0bf05c1cbe..5085663c41 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -5,13 +5,7 @@ //! - `height`: the last committed block height //! - `tx_queue`: txs to be decrypted in the next block //! - `pred`: predecessor values of the top-level keys of the same name -//! - `tx_queue` -//! - `min_confirmations`: Minimum number of confirmations needed for the -//! Ethereum bridge to consider an event final. -//! - `native_erc20`: The Ethereum address of the ERC20 contract that represents -//! this chain's native token. -//! - `bridge_contract`: The Ethereum address of the bridge contract. -//! - `governance_contract`: The Ethereum address of the governance contract. +//! - `tx_queue` //! - `next_epoch_min_start_height`: minimum block height from which the next //! epoch can start //! - `next_epoch_min_start_time`: minimum block time from which the next epoch @@ -38,15 +32,11 @@ use std::path::Path; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use namada::ledger::storage::types::PrefixIterator; use namada::ledger::storage::{ types, BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, MerkleTreeStoresRead, Result, StoreType, DB, }; -use namada::types::ethereum_events::EthAddress; use namada::types::storage::{ BlockHeight, Header, Key, KeySeg, TxQueue, KEY_SEGMENT_SEPARATOR, }; @@ -310,61 +300,6 @@ impl DB for RocksDB { return Ok(None); } }; - let min_confirmations: Option = match self - .0 - .get("min_confirmations") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the minimum confirmations from the DB" - ); - return Ok(None); - } - }; - let native_erc20: Option = match self - .0 - .get("native_erc20") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum address for wrapped Nam from \ - the DB" - ); - return Ok(None); - } - }; - let bridge_contract: Option = match self - .0 - .get("bridge_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum bridge contract address from \ - the DB" - ); - return Ok(None); - } - }; - let governance_contract: Option = match self - .0 - .get("governance_contract") - .map_err(|e| Error::DBError(e.into_string()))? - { - Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, - None => { - tracing::error!( - "Couldn't load the Ethereum governance contract from the \ - DB" - ); - return Ok(None); - } - }; let tx_queue: TxQueue = match self .0 .get("tx_queue") @@ -467,10 +402,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -493,10 +424,6 @@ impl DB for RocksDB { next_epoch_min_start_time, address_gen, tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -525,10 +452,6 @@ impl DB for RocksDB { "next_epoch_min_start_time", types::encode(&next_epoch_min_start_time), ); - batch.put("min_confirmations", types::encode(&min_confirmations)); - batch.put("native_erc20", types::encode(&native_erc20)); - batch.put("bridge_contract", types::encode(&bridge_contract)); - batch.put("governance_contract", types::encode(&governance_contract)); // Tx queue if let Some(pred_tx_queue) = self .0 @@ -1071,10 +994,6 @@ mod test { next_epoch_min_start_time, address_gen: &address_gen, tx_queue: &tx_queue, - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, }; db.write_block(block).unwrap(); diff --git a/shared/src/ledger/parameters/ethereum_bridge.rs b/shared/src/ledger/parameters/ethereum_bridge.rs deleted file mode 100644 index 1e54207f6e..0000000000 --- a/shared/src/ledger/parameters/ethereum_bridge.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Parameters for configuring the Ethereum bridge -use std::num::NonZeroU64; - -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; - -use crate::types::ethereum_events::EthAddress; - -/// Represents a configuration value for the minimum number of -/// confirmations an Ethereum event must reach before it can be acted on. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct MinimumConfirmations(NonZeroU64); - -impl Default for MinimumConfirmations { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be violated - // is if we construct values of this type using 0 as argument. - Self(unsafe { NonZeroU64::new_unchecked(100) }) - } -} - -/// Represents a configuration value for the version of a contract that can be -/// upgraded. Starts from 1. -#[derive( - Clone, - Copy, - Eq, - PartialEq, - Debug, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -#[repr(transparent)] -pub struct ContractVersion(NonZeroU64); - -impl Default for ContractVersion { - fn default() -> Self { - // SAFETY: The only way the API contract of `NonZeroU64` can be - // violated is if we construct values of this type using 0 as - // argument. - Self(unsafe { NonZeroU64::new_unchecked(1) }) - } -} - -/// Represents an Ethereum contract that may be upgraded. -#[derive( - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - BorshSerialize, - BorshDeserialize, -)] -pub struct UpgradeableContract { - /// The Ethereum address of the contract. - pub address: EthAddress, - /// The version of the contract. Starts from 1. - pub version: ContractVersion, -} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 0ec554dd80..4891818dc0 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -1,5 +1,4 @@ //! Protocol parameters -pub mod ethereum_bridge; pub mod storage; use std::collections::BTreeSet; diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 29a19a9760..234cad4498 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -12,11 +12,7 @@ use super::merkle_tree::{MerkleTreeStoresRead, StoreType}; use super::{ BlockStateRead, BlockStateWrite, DBIter, DBWriteBatch, Error, Result, DB, }; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::storage::types::{self, KVBytes, PrefixIterator}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -77,34 +73,6 @@ impl DB for MockDB { } None => return Ok(None), }; - let min_confirmations: Option = - match self.0.borrow().get("min_confirmations") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let native_erc20: Option = - match self.0.borrow().get("native_erc20") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let bridge_contract: Option = - match self.0.borrow().get("bridge_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; - let governance_contract: Option = - match self.0.borrow().get("governance_contract") { - Some(bytes) => { - types::decode(bytes).map_err(Error::CodingError)? - } - None => return Ok(None), - }; #[cfg(feature = "ferveo-tpke")] let tx_queue: TxQueue = match self.0.borrow().get("tx_queue") { Some(bytes) => types::decode(bytes).map_err(Error::CodingError)?, @@ -185,10 +153,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, })) } _ => Err(Error::Temporary { @@ -211,10 +175,6 @@ impl DB for MockDB { address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract, - governance_contract, }: BlockStateWrite = state; // Epoch start height and time @@ -226,20 +186,6 @@ impl DB for MockDB { "next_epoch_min_start_time".into(), types::encode(&next_epoch_min_start_time), ); - self.0.borrow_mut().insert( - "min_confirmations".into(), - types::encode(&min_confirmations), - ); - self.0 - .borrow_mut() - .insert("native_erc20".into(), types::encode(&native_erc20)); - self.0 - .borrow_mut() - .insert("bridge_contract".into(), types::encode(&bridge_contract)); - self.0.borrow_mut().insert( - "goverenance_contract".into(), - types::encode(&governance_contract), - ); #[cfg(feature = "ferveo-tpke")] { self.0 diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0411375912..805caa6348 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -17,9 +17,6 @@ use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; -use crate::ledger::parameters::ethereum_bridge::{ - MinimumConfirmations, UpgradeableContract, -}; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, @@ -31,7 +28,6 @@ use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; -use crate::types::ethereum_events::EthAddress; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -73,16 +69,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block storage data @@ -142,16 +128,6 @@ pub struct BlockStateRead { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// The block's state to write into the database. @@ -177,16 +153,6 @@ pub struct BlockStateWrite<'a> { /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: &'a TxQueue, - /// Minimum number of confirmations needed for the - /// Ethereum bridge to consider an event final - pub min_confirmations: Option, - /// The Ethereum address of the ERC20 contract that represents this chain's - /// native token. - pub native_erc20: Option, - /// The Ethereum address of the bridge contract. - pub bridge_contract: Option, - /// The Ethereum address of the governance contract. - pub governance_contract: Option, } /// A database backend. @@ -336,10 +302,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } @@ -357,10 +319,6 @@ where address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue, - min_confirmations, - native_erc20, - bridge_contract: bridge, - governance_contract: governance, }) = self.db.read_last_block()? { self.block.tree = MerkleTree::new(merkle_tree_stores); @@ -377,10 +335,6 @@ where { self.tx_queue = tx_queue; } - self.min_confirmations = min_confirmations; - self.native_erc20 = native_erc20; - self.bridge_contract = bridge; - self.governance_contract = governance; tracing::debug!("Loaded storage from DB"); } else { tracing::info!("No state could be found"); @@ -412,10 +366,6 @@ where address_gen: &self.address_gen, #[cfg(feature = "ferveo-tpke")] tx_queue: &self.tx_queue, - min_confirmations: self.min_confirmations, - native_erc20: self.native_erc20.clone(), - bridge_contract: self.bridge_contract.clone(), - governance_contract: self.governance_contract.clone(), }; self.db.write_block(state)?; self.last_height = self.block.height; @@ -972,10 +922,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - min_confirmations: None, - native_erc20: None, - bridge_contract: None, - governance_contract: None, } } } From 47a9848f127d5743cfc1b3bfb2b69ab9e52d12eb Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:28:31 +0100 Subject: [PATCH 1309/1995] [feat]: Initialized VP substorage --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 27 +++++++++++++++++-- shared/src/ledger/eth_bridge/parameters.rs | 12 +++++++-- shared/src/ledger/eth_bridge/vp/mod.rs | 24 +++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6c02d60a60..44d0942c4d 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -12,7 +12,7 @@ //! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; use crate::ledger::eth_bridge::storage::bridge_pool::{ @@ -22,7 +22,7 @@ use crate::ledger::eth_bridge::storage::wrapped_erc20s; use crate::ledger::eth_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; use crate::types::address::{wnam, xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -41,6 +41,29 @@ enum SignedAmount { Negative(Amount), } +/// Initialize the storage owned by the Bridge Pool VP. +/// +/// This means that the amount of escrowed gas fees is +/// initialized to 0. +pub fn init_storage(storage: &mut Storage) +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Bridge pool VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct BridgePoolVp<'ctx, D, H, CA> where diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 2d22730a8a..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -4,7 +4,8 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; -use crate::ledger::eth_bridge::storage as bridge_storage; +use crate::ledger::eth_bridge; +use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::ethereum_events::EthAddress; @@ -119,7 +120,10 @@ pub struct EthereumBridgeConfig { } impl EthereumBridgeConfig { - /// Initialize the Ethereum bridge parameters in storage + /// Initialize the Ethereum bridge parameters in storage. + /// + /// If these parameters are initialized, the storage subspaces + /// for the Ethereum bridge VPs are also initialized. pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -148,5 +152,9 @@ impl EthereumBridgeConfig { storage .write(&governance_contract_key, encode(governance)) .unwrap(); + // Initialize the storage for the Ethereum Bridge VP. + eth_bridge::vp::init_storage(storage); + // Initialize the storage for the Bridge Pool VP. + bridge_pool_vp::init_storage(storage); } } diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index f166dd5d87..ea5bd5328a 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -17,6 +17,30 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; + +/// Initialize the storage owned by the Ethereum Bridge VP. +/// +/// This means that the amount of escrowed Nam is +/// initialized to 0. +pub fn init_storage(storage: &mut ledger_storage::Storage) + where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&xan(), &super::ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ + fail.", + ); +} + /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where From 5472445e2275e3479d17c4211f132375beb45a46 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:12:44 +0100 Subject: [PATCH 1310/1995] [fix]: Linting --- shared/src/ledger/eth_bridge/vp/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index ea5bd5328a..56786fefd0 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshDeserialize; +use borsh::BorshSerialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,15 +17,14 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; - /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) - where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -36,8 +35,8 @@ pub fn init_storage(storage: &mut ledger_storage::Storage) .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ - fail.", + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", ); } From 581ef8a282408110b33834410d243c584f6d335e Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 10:47:50 +0100 Subject: [PATCH 1311/1995] [fix]: Moved a test to a move appropriate location --- Cargo.lock | 1 + apps/src/lib/config/ethereum_bridge/mod.rs | 36 --------------------- shared/Cargo.toml | 1 + shared/src/ledger/eth_bridge/parameters.rs | 37 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e081e3e4bd..0df7db2ceb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3364,6 +3364,7 @@ dependencies = [ "test-log", "thiserror", "tiny-keccak", + "toml", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", diff --git a/apps/src/lib/config/ethereum_bridge/mod.rs b/apps/src/lib/config/ethereum_bridge/mod.rs index d4606b0652..370e1150a2 100644 --- a/apps/src/lib/config/ethereum_bridge/mod.rs +++ b/apps/src/lib/config/ethereum_bridge/mod.rs @@ -1,37 +1 @@ pub mod ledger; - -#[cfg(test)] -mod tests { - use eyre::Result; - use namada::ledger::eth_bridge::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, - }; - use namada::types::ethereum_events::EthAddress; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..3ba24a3bf0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -136,6 +136,7 @@ pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} +toml = "0.5.8" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} [build-dependencies] diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index f16292223b..46ff52c0a3 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,3 +158,40 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use crate::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use crate::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} From 7ae6a0c50a1aae36b6de6a28b1ff4876c7c51d38 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 14:14:45 +0100 Subject: [PATCH 1312/1995] [fix] Upgrading to v0.8 --- apps/src/lib/config/genesis.rs | 3 --- apps/src/lib/node/ledger/shell/init_chain.rs | 21 ++++++++++---------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 717d4bf01f..e8794545ac 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -588,7 +588,6 @@ pub mod genesis_config { parameters, pos_params, gov_params, - treasury_params, ethereum_bridge_params: config.ethereum_bridge_params, }; genesis.init(); @@ -637,7 +636,6 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub treasury_params: TreasuryParams, // Ethereum bridge config pub ethereum_bridge_params: Option, } @@ -876,7 +874,6 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - treasury_params: TreasuryParams::default(), ethereum_bridge_params: None, } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 394d151855..91ab6e7ee2 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,11 +2,10 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::pos::PosParams; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::{ibc, pos}; -use namada::ledger::parameters::Parameters; -use namada::ledger::pos::PosParams; use namada::types::key::*; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; use namada::types::token; @@ -69,7 +68,6 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.treasury_params.init_storage(&mut self.storage); // configure the Ethereum bridge if the configuration is set. if let Some(config) = genesis.ethereum_bridge_params { config.init_storage(&mut self.storage); @@ -91,7 +89,7 @@ where self.initialize_established_accounts( genesis.established_accounts, &mut vp_code_cache, - ); + )?; // Initialize genesis implicit self.initialize_implicit_accounts(genesis.implicit_accounts); @@ -105,11 +103,12 @@ where // Initialize genesis validator accounts self.initialize_validators(&genesis.validators, &mut vp_code_cache); // set the initial validators set - Ok(self.set_initial_validators( - genesis.validators, - &genesis.parameters, - &genesis.pos_params, - )) + Ok( + self.set_initial_validators( + genesis.validators, + &genesis.pos_params, + ), + ) } /// Initialize genesis established accounts @@ -117,7 +116,7 @@ where &mut self, accounts: Vec, vp_code_cache: &mut HashMap>, - ) { + ) -> Result<()> { for genesis::EstablishedAccount { address, vp_code_path, @@ -168,6 +167,7 @@ where self.storage.write(&key, value).unwrap(); } } + Ok(()) } /// Initialize genesis implicit accounts @@ -319,7 +319,6 @@ where fn set_initial_validators( &mut self, validators: Vec, - parameters: &Parameters, pos_params: &PosParams, ) -> response::InitChain { let mut response = response::InitChain::default(); From 636db42525e7c531a022f6b058f9120f13015a81 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 14:28:31 +0100 Subject: [PATCH 1313/1995] [feat]: Initialized VP substorage --- shared/src/ledger/eth_bridge/parameters.rs | 37 --------- shared/src/ledger/eth_bridge/vp/mod.rs | 97 ++++------------------ 2 files changed, 14 insertions(+), 120 deletions(-) diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 46ff52c0a3..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,40 +158,3 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } - -#[cfg(test)] -mod tests { - use eyre::Result; - - use crate::ledger::eth_bridge::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, - }; - use crate::types::ethereum_events::EthAddress; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 56786fefd0..b9bb4237cc 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshSerialize; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,14 +17,15 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; + /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) -where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, + where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -35,8 +36,8 @@ where .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP \ - shouldn't fail.", + "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ + fail.", ); } @@ -51,56 +52,6 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } -impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// If the bridge's escrow key was changed, we check - /// that the balance increased and that the bridge pool - /// VP has been triggered. The bridge pool VP will carry - /// out the rest of the checks. - fn check_escrow(&self, verifiers: &BTreeSet
) -> bool { - let escrow_key = balance_key(&xan(), &super::ADDRESS); - let escrow_pre: Amount = if let Ok(Some(bytes)) = - self.ctx.read_pre(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the Ethereum bridge VP's balance from \ - storage" - ); - return false; - }; - let escrow_post: Amount = if let Ok(Some(bytes)) = - self.ctx.read_post(&escrow_key) - { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .expect("Deserializing a balance from storage shouldn't fail") - } else { - tracing::debug!( - "Could not retrieve the modified Ethereum bridge VP's balance \ - after applying tx" - ); - return false; - }; - - // The amount escrowed should increase. - if escrow_pre < escrow_post { - verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS) - } else { - tracing::info!( - "A normal tx cannot decrease the amount of Nam escrowed in \ - the Ethereum bridge" - ); - false - } - } -} - #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -124,7 +75,6 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount - /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -142,18 +92,11 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); - - // first check if Nam is being escrowed - if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return Ok(self.check_escrow(verifiers)); - } - let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), }; - let (sender, _) = match check_balance_changes(&self.ctx, key_a, key_b)? - { + let sender = match check_balance_changes(&self.ctx, key_a, key_b)? { Some(sender) => sender, None => return Ok(false), }; @@ -234,13 +177,13 @@ fn extract_valid_keys_changed( /// Checks that the balances at both `key_a` and `key_b` have changed by some /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return the `Address` of the owner of the balance which is decreasing, -/// as by how much it decreased, which should be authorizing the balance change. -pub(super) fn check_balance_changes( +/// return the `Address` of the owner of the balance which is decreasing, which +/// should be authorizing the balance change. +fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, -) -> Result> { +) -> Result> { let (balance_a, balance_b) = match (key_a.suffix.clone(), key_b.suffix.clone()) { ( @@ -346,26 +289,14 @@ pub(super) fn check_balance_changes( if balance_a_delta < 0 { if let wrapped_erc20s::KeyType::Balance { owner } = key_a.suffix { - Ok(Some(( - owner, - Amount::from( - u64::try_from(balance_b_delta) - .expect("This should not fail"), - ), - ))) + Ok(Some(owner)) } else { unreachable!() } } else { assert!(balance_b_delta < 0); if let wrapped_erc20s::KeyType::Balance { owner } = key_b.suffix { - Ok(Some(( - owner, - Amount::from( - u64::try_from(balance_a_delta) - .expect("This should not fail"), - ), - ))) + Ok(Some(owner)) } else { unreachable!() } From e54cc5e6e978215ac4fa558fd57e961a116f36a4 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 31 Oct 2022 15:12:44 +0100 Subject: [PATCH 1314/1995] [fix]: Linting --- shared/src/ledger/eth_bridge/vp/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index b9bb4237cc..1e72b8d49b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,7 +4,7 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshDeserialize; +use borsh::BorshSerialize; use eyre::{eyre, Result}; use itertools::Itertools; @@ -17,15 +17,14 @@ use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; - /// Initialize the storage owned by the Ethereum Bridge VP. /// /// This means that the amount of escrowed Nam is /// initialized to 0. pub fn init_storage(storage: &mut ledger_storage::Storage) - where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, { let escrow_key = balance_key(&xan(), &super::ADDRESS); storage @@ -36,8 +35,8 @@ pub fn init_storage(storage: &mut ledger_storage::Storage) .expect("Serializing an amount shouldn't fail."), ) .expect( - "Initializing the escrow balance of the Ethereum Bridge VP shouldn't \ - fail.", + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", ); } From 5af08b9e57715d78904fa84cf711a9c1435c41ce Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 1 Nov 2022 10:47:50 +0100 Subject: [PATCH 1315/1995] [fix]: Moved a test to a move appropriate location --- shared/src/ledger/eth_bridge/parameters.rs | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index f16292223b..46ff52c0a3 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,3 +158,40 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use crate::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use crate::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} From b2b605ccad09dfcdcf85845e152a2a756d0c9098 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 15:03:19 +0100 Subject: [PATCH 1316/1995] [fix]: Fixing a bad rebase I did --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 114 ++++++++++-------- shared/src/ledger/eth_bridge/vp/mod.rs | 93 ++++++++++++-- 2 files changed, 148 insertions(+), 59 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 44d0942c4d..03dbfd34b6 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -15,6 +15,7 @@ use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; +use crate::ledger::eth_bridge::storage; use crate::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; @@ -24,8 +25,9 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; -use crate::types::address::{wnam, xan, Address, InternalAddress}; +use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::ethereum_events::EthAddress; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -142,6 +144,26 @@ where ))), } } + + /// Get the Ethereum address for wNam from storage, if possible + fn native_erc20_address(&self) -> Result { + match self.ctx.storage.read(&storage::native_erc20_key()) { + Ok((Some(bytes), _)) => { + Ok(EthAddress::try_from_slice(bytes.as_slice()).expect( + "Deserializing the Native ERC20 address from storage \ + shouldn't fail.", + )) + } + Ok(_) => Err(Error(eyre!( + "The Ethereum bridge storage is not initialized" + ))), + Err(e) => Err(Error(eyre!( + "Failed to read storage when fetching the native ERC20 \ + address with: {}", + e.to_string() + ))), + } + } } /// Check if a delta matches the delta given by a transfer @@ -234,8 +256,8 @@ where // if we are going to mint wNam on Ethereum, the appropriate // amount of Nam must be escrowed in the Ethereum bridge VP's storage. - // TODO: We should look this address up from storage - if transfer.transfer.asset == wnam() { + let wnam_address = self.native_erc20_address()?; + if transfer.transfer.asset == wnam_address { // check that correct amount of Nam was put into escrow. return if transfer.gas_fee.payer == transfer.transfer.sender { if !self.check_nam_escrowed( @@ -334,6 +356,9 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use super::*; + use crate::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; @@ -341,6 +366,7 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::Storage; use crate::proto::Tx; + use crate::types::address::wnam; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use crate::types::ethereum_events::EthAddress; @@ -487,6 +513,32 @@ mod test_bridge_pool_vp { [account_key, token_key].into() } + /// Initialize some dummy storage for testing + fn setup_storage() -> Storage { + let mut storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + // a dummy config for testing + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + config.init_storage(&mut storage); + storage + } + /// Setup a ctx for running native vps fn setup_ctx<'a>( tx: &'a Tx, @@ -527,11 +579,7 @@ mod test_bridge_pool_vp { { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -880,11 +928,7 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -958,11 +1002,7 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut write_log = new_writelog(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1030,21 +1070,10 @@ mod test_bridge_pool_vp { fn test_mint_wnam() { // setup let mut write_log = new_writelog(); - // initialize the eth bridge balance to 0 let eb_account_key = balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); - write_log - .write( - &eb_account_key, - Amount::default().try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool @@ -1125,29 +1154,18 @@ mod test_bridge_pool_vp { assert!(res); } - /// Test that we can rejecte a transfer that + /// Test that we can reject a transfer that /// mints wNam if we don't escrow the correct /// amount of Nam. #[test] fn test_reject_mint_wnam() { // setup let mut write_log = new_writelog(); - // initialize the eth bridge balance to 0 - let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); - write_log - .write( - &eb_account_key, - Amount::default().try_to_vec().expect("Test failed"), - ) - .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); + let eb_account_key = + balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1255,11 +1273,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); write_log.commit_tx(); - let storage = Storage::::open( - std::path::Path::new(""), - ChainId::default(), - None, - ); + let storage = setup_storage(); let tx = Tx::new(vec![], None); // the transfer to be added to the pool diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 1e72b8d49b..4cc7b85f50 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -4,12 +4,12 @@ mod authorize; use std::collections::{BTreeSet, HashSet}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{eyre, Result}; use itertools::Itertools; use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; -use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; +use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; use crate::types::address::{xan, Address, InternalAddress}; @@ -51,6 +51,61 @@ where pub ctx: Ctx<'ctx, DB, H, CA>, } +impl<'ctx, DB, H, CA> EthBridge<'ctx, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// If the bridge's escrow key was changed, we check + /// that the balance increased and that the bridge pool + /// VP has been triggered. The bridge pool VP will carry + /// out the rest of the checks. + fn check_escrow( + &self, + verifiers: &BTreeSet
, + ) -> Result { + let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_pre: Amount = if let Ok(Some(bytes)) = + self.ctx.read_bytes_pre(&escrow_key) + { + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| Error(eyre!("Couldn't deserialize a balance from storage")), + )? + } else { + tracing::debug!( + "Could not retrieve the Ethereum bridge VP's balance from \ + storage" + ); + return Ok(false); + }; + let escrow_post: Amount = + if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) { + BorshDeserialize::try_from_slice(bytes.as_slice()).expect( + "Deserializing the balance of the Ethereum bridge VP from \ + storage shouldn't fail", + ) + } else { + tracing::debug!( + "Could not retrieve the modified Ethereum bridge VP's \ + balance after applying tx" + ); + return Ok(false); + }; + + // The amount escrowed should increase. + if escrow_pre < escrow_post { + Ok(verifiers.contains(&storage::bridge_pool::BRIDGE_POOL_ADDRESS)) + } else { + tracing::info!( + "A normal tx cannot decrease the amount of Nam escrowed in \ + the Ethereum bridge" + ); + Ok(false) + } + } +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -74,6 +129,7 @@ where /// decreased by the same amount /// - a wrapped ERC20's balance key to decrease iff another one of its /// balance keys increased by the same amount + /// - Escrowing Nam in order to mint wrapped Nam on Ethereum /// /// Some other changes to the storage subspace of this account are expected /// to happen natively i.e. bypassing this validity predicate. For example, @@ -91,11 +147,18 @@ where verifiers_len = verifiers.len(), "Ethereum Bridge VP triggered", ); + + // first check if Nam is being escrowed + if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { + return self.check_escrow(verifiers); + } + let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { Some((key_a, key_b)) => (key_a, key_b), None => return Ok(false), }; - let sender = match check_balance_changes(&self.ctx, key_a, key_b)? { + let (sender, _) = match check_balance_changes(&self.ctx, key_a, key_b)? + { Some(sender) => sender, None => return Ok(false), }; @@ -176,13 +239,13 @@ fn extract_valid_keys_changed( /// Checks that the balances at both `key_a` and `key_b` have changed by some /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return the `Address` of the owner of the balance which is decreasing, which -/// should be authorizing the balance change. -fn check_balance_changes( +/// return the `Address` of the owner of the balance which is decreasing, +/// as by how much it decreased, which should be authorizing the balance change. +pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, key_b: wrapped_erc20s::Key, -) -> Result> { +) -> Result> { let (balance_a, balance_b) = match (key_a.suffix.clone(), key_b.suffix.clone()) { ( @@ -288,14 +351,26 @@ fn check_balance_changes( if balance_a_delta < 0 { if let wrapped_erc20s::KeyType::Balance { owner } = key_a.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_b_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } } else { assert!(balance_b_delta < 0); if let wrapped_erc20s::KeyType::Balance { owner } = key_b.suffix { - Ok(Some(owner)) + Ok(Some(( + owner, + Amount::from( + u64::try_from(balance_a_delta) + .expect("This should not fail"), + ), + ))) } else { unreachable!() } From 2a88eb11d48cfead3497438b2ab3857b9c35976f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 14:05:49 +0000 Subject: [PATCH 1317/1995] Update client to use new RPC router for accepted/applied queries --- apps/src/lib/client/rpc.rs | 41 +++++++++++++++--------------- shared/src/ledger/queries/shell.rs | 8 +++--- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8c77ad0636..7b1017fbda 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,11 +30,11 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, VotePower, }; +use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use tendermint::abci::Code; use tendermint_config::net::Address as TendermintAddress; use tendermint_rpc::error::Error as TError; use tendermint_rpc::query::Query; @@ -74,11 +74,8 @@ pub async fn query_tx_status( let mut backoff = ONE_SECOND; loop { - let data = vec![]; tracing::debug!(query = ?status, "Querying tx status"); - let response = match client - .abci_query(Some(status.into()), data, None, false) - .await + let raw_response = match send_tx_event_query(&client, status).await { Ok(response) => response, Err(err) => { @@ -87,22 +84,11 @@ pub async fn query_tx_status( continue; } }; - let mut events = match response.code { - Code::Ok => { - match Vec::::try_from_slice(&response.value[..]) { - Ok(events) => events, - Err(err) => { - eprintln!("Error decoding the event value: {err}"); - break Err(()); - } - } - } - Code::Err(err) => { - eprintln!( - "Error in the query {} (error code {})", - response.info, err - ); - break Err(()); + let mut events = match Vec::::try_from_slice(&raw_response) { + Ok(events) => events, + Err(err) => { + eprintln!("Error decoding the event value: {err}"); + cli::safe_exit(1); } }; if let Some(e) = events.pop() { @@ -1462,6 +1448,19 @@ impl<'a> From> for Query { } } +pub async fn send_tx_event_query( + client: &HttpClient, + tx_query: TxEventQuery<'_>, +) -> Result, queries::tm::Error> { + let tx_hash: Hash = tx_query.tx_hash().try_into().unwrap(); + match tx_query { + TxEventQuery::Accepted(_) => { + RPC.shell().accepted(client, &tx_hash).await + } + TxEventQuery::Applied(_) => RPC.shell().applied(client, &tx_hash).await, + } +} + /// Lookup the full response accompanying the specified transaction event pub async fn query_tx_response( ledger_address: &TendermintAddress, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 3c658584de..299a3773fd 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -33,10 +33,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash] ) -> bool = accepted, + ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash] ) -> bool = applied, + ( "applied" / [tx_hash: Hash] ) -> Vec = applied, } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -57,10 +57,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash]) -> bool = accepted, + ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash]) -> bool = applied, + ( "applied" / [tx_hash: Hash]) -> Vec = applied, } // Handlers: From c7dd8753bba1f101bd359937914038b0b2cc13d2 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1318/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index e696051c72..c810a1db5e 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,7 +25,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; From 13d62255c67baeb3de0f702720f940ea287eb202 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1319/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c810a1db5e..02eb7ac141 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,8 +25,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; @@ -673,6 +672,15 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, + |transfer, log| { + log.write( + &get_pending_key(&transfer), + transfer.try_to_vec().unwrap(), + ) + .unwrap(); + BTreeSet::from([get_pending_key(&transfer)]) + }, + Expect::False, ); } From 38a65e6ae196fe6ad42fd5d9a91f97d7fb147bf8 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1320/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 + shared/src/types/storage.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 02eb7ac141..fa23e0abe2 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,6 +25,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 3d1c3f772d..c9ccbf4825 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -787,6 +787,22 @@ impl_int_key_seg!(u32, i32, 4); impl_int_key_seg!(u64, i64, 8); impl_int_key_seg!(u128, i128, 16); +impl KeySeg for KeccakHash { + fn parse(seg: String) -> Result { + seg.clone() + .try_into() + .map_err(|_| Error::ParseError(seg, "Hash".into())) + } + + fn raw(&self) -> String { + self.to_string() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From 3c7f5750c10d7f046903b172feb11af88d790f6d Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 17:25:52 +0200 Subject: [PATCH 1321/1995] [feat]: Changed the bridge pool vp to used the merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index fa23e0abe2..29fb30480f 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -673,15 +673,6 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }, Expect::False, - |transfer, log| { - log.write( - &get_pending_key(&transfer), - transfer.try_to_vec().unwrap(), - ) - .unwrap(); - BTreeSet::from([get_pending_key(&transfer)]) - }, - Expect::False, ); } From 32603eb8526500362fa8d8f5ac8bdd512728b09d Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 24 Oct 2022 17:12:09 +0200 Subject: [PATCH 1322/1995] [chore]: Rebased onto PR #573 --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 29fb30480f..e696051c72 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,7 +25,6 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; From 2baa3f564b3c331209ecb75f004f4805d28acd12 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 11 Oct 2022 15:06:05 +0200 Subject: [PATCH 1323/1995] [feat]: Updated bridge pool vp with the the new merklized storage --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index e696051c72..c810a1db5e 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,7 +25,8 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::storage::Key; +use crate::types::keccak::encode::Encode; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; From 49a9b530ed9732aa472ad4049fb2321b54614010 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1324/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- apps/src/lib/node/ledger/rpc.rs | 31 +++- apps/src/lib/node/ledger/shell/queries.rs | 141 ++++++++++++++++++ .../ledger/eth_bridge/storage/bridge_pool.rs | 56 +++++-- shared/src/ledger/storage/merkle_tree.rs | 21 ++- shared/src/ledger/storage/mod.rs | 3 +- shared/src/ledger/storage/traits.rs | 2 +- shared/src/types/eth_bridge_pool.rs | 54 ++++++- 7 files changed, 284 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 4cbedca8bc..25816cc426 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -17,7 +17,10 @@ pub enum Path { DryRunTx, /// Epoch of the last committed block. Epoch, - /// Read a storage value with exact storage key. + /// The pool of transfers waiting to be + /// relayed to Ethereum. + EthereumBridgePool(BridgePoolSubpath), + /// Read a storage value with exact storage key Value(storage::Key), /// Read a range of storage values with a matching key prefix. Prefix(storage::Key), @@ -29,6 +32,16 @@ pub enum Path { Applied { tx_hash: Hash }, } +#[derive(Debug, Clone)] +pub enum BridgePoolSubpath { + /// For queries that wish to see the contents of the + /// Ethereum bridge pool. + Contents, + /// For queries that want to get a merkle proof of + /// inclusion of transfers in the Ethereum bridge pool. + Proof, +} + #[derive(Debug, Clone)] pub struct BalanceQuery { #[allow(dead_code)] @@ -39,6 +52,9 @@ pub struct BalanceQuery { const DRY_RUN_TX_PATH: &str = "dry_run_tx"; const EPOCH_PATH: &str = "epoch"; +const ETH_BRIDGE_POOL_PATH: &str = "eth_bridge_pool"; +const EBP_INFO_SUBPATH: &str = "contents"; +const EBP_PROOF_SUBPATH: &str = "proof"; const VALUE_PREFIX: &str = "value"; const PREFIX_PREFIX: &str = "prefix"; const HAS_KEY_PREFIX: &str = "has_key"; @@ -59,6 +75,13 @@ impl Display for Path { Path::HasKey(storage_key) => { write!(f, "{}/{}", HAS_KEY_PREFIX, storage_key) } + Path::EthereumBridgePool(subpath) => { + let subpath = match subpath { + BridgePoolSubpath::Contents => EBP_INFO_SUBPATH, + BridgePoolSubpath::Proof => EBP_PROOF_SUBPATH, + }; + write!(f, "{}/{}", ETH_BRIDGE_POOL_PATH, subpath) + } Path::Accepted { tx_hash } => { write!(f, "{ACCEPTED_PREFIX}/{tx_hash}") } @@ -77,6 +100,12 @@ impl FromStr for Path { DRY_RUN_TX_PATH => Ok(Self::DryRunTx), EPOCH_PATH => Ok(Self::Epoch), _ => match s.split_once('/') { + Some((ETH_BRIDGE_POOL_PATH, EBP_INFO_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Contents)) + } + Some((ETH_BRIDGE_POOL_PATH, EBP_PROOF_SUBPATH)) => { + Ok(Self::EthereumBridgePool(BridgePoolSubpath::Proof)) + } Some((VALUE_PREFIX, storage_key)) => { let key = storage::Key::parse(storage_key) .map_err(PathParseError::InvalidStorageKey)?; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 1fad92eb9d..7ad4849ec9 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,14 +1,23 @@ //! Shell methods for querying state use std::cmp::max; +use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, +}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; +use namada::ledger::storage::{StoreRef, StoreType}; use namada::types::address::Address; +use namada::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; +use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -22,6 +31,7 @@ use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::events::log::dumb_queries; use crate::node::ledger::response; +use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -88,6 +98,14 @@ where self.read_storage_prefix(&storage_key, height, query.prove) } Path::HasKey(storage_key) => self.has_storage_key(&storage_key), + Path::EthereumBridgePool(subpath) => match subpath { + BridgePoolSubpath::Contents => { + self.read_ethereum_bridge_pool() + } + BridgePoolSubpath::Proof => { + self.generate_bridge_pool_proof(query.data) + } + }, Path::Accepted { tx_hash } => { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); self.query_event_log(matcher) @@ -338,6 +356,129 @@ where }, } } + + /// Read the current contents of the Ethereum bridge + /// pool. + fn read_ethereum_bridge_pool(&self) -> response::Query { + if let Ok(Some(stores)) = self + .storage + .db + .read_merkle_tree_stores(self.storage.last_height) + { + let transfers = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + _ => unreachable!(), + }; + response::Query { + code: 0, + value: transfers, + ..Default::default() + } + } else { + response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + info: "Could not retrieve the Ethereum bridge pool for the \ + latest height" + .into(), + ..Default::default() + } + } + } + + /// Generate a merkle proof for the inclusion of then + /// requested transfers in the Ethereum bridge pool. + fn generate_bridge_pool_proof( + &self, + request_bytes: Vec, + ) -> response::Query { + if let Ok(transfers) = + >::try_from_slice(request_bytes.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = + match self.storage.read(&get_signed_root_key()) { + Ok((Some(bytes), _)) => { + BorshDeserialize::try_from_slice(bytes.as_slice()) + .unwrap() + } + _ => { + return response::Query { + code: 1, + log: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + info: "Could not deserialize the signed Ethereum \ + bridge pool merkle root" + .into(), + ..Default::default() + }; + } + }; + // get the merkle tree corresponding to the above root. + let tree = if let Ok(Some(stores)) = + self.storage.db.read_merkle_tree_stores(signed_root.height) + { + match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => { + BridgePoolTree::new(store.clone()) + } + _ => unreachable!(), + } + } else { + return response::Query { + code: 1, + log: "Could not retrieve the Ethereum bridge pool for the \ + latest signed root" + .into(), + info: "Could not retrieve the Ethereum bridge pool for \ + the latest signed root" + .into(), + ..Default::default() + }; + }; + if tree.root() != signed_root.root { + return response::Query { + code: 1, + log: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + info: "The latest signed root does not equal the root of \ + corresponding Merkle tree" + .into(), + ..Default::default() + }; + } + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_membership_proof(&keys, transfers) { + Ok(proof) => response::Query { + code: 0, + value: RelayProof { + root: signed_root, + proof, + } + .encode(), + ..Default::default() + }, + Err(e) => response::Query { + code: 1, + log: e.to_string(), + info: e.to_string(), + ..Default::default() + }, + } + } else { + response::Query { + code: 1, + log: "Could not deserialize transfers".into(), + info: "Could not deserialize transfers".into(), + ..Default::default() + } + } + } } /// API for querying the blockchain state. diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index befe3d044a..8e9029a023 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; @@ -88,7 +89,7 @@ impl BridgePoolTree { let hash = Self::parse_key(key)?; _ = self.leaves.insert(hash); self.root = self.compute_root(); - Ok(self.root()) + Ok(self.root().into()) } /// Delete a key from storage and update the root @@ -324,6 +325,31 @@ impl BridgePoolProof { } } +impl Encode for BridgePoolProof { + fn tokenize(&self) -> Vec { + let BridgePoolProof { + proof, + leaves, + flags, + } = self; + let proof = Token::Array( + proof + .iter() + .map(|hash| Token::FixedBytes(hash.0.to_vec())) + .collect(), + ); + let transfers = Token::Array( + leaves + .iter() + .map(|t| Token::FixedArray(t.tokenize())) + .collect(), + ); + let flags = + Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); + vec![proof, transfers, flags] + } +} + #[cfg(test)] mod test_bridge_pool_tree { @@ -387,9 +413,8 @@ mod test_bridge_pool_tree { transfers.push(transfer); let _ = tree.insert_key(&key).expect("Test failed"); } - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[1].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); assert_eq!(tree.root(), expected); } @@ -425,7 +450,7 @@ mod test_bridge_pool_tree { hash_pair(transfers[0].keccak256(), transfers[1].keccak256()); let right_hash = hash_pair(transfers[2].keccak256(), Default::default()); - let expected: Hash = hash_pair(left_hash, right_hash).into(); + let expected = hash_pair(left_hash, right_hash); assert_eq!(tree.root(), expected); } @@ -483,9 +508,8 @@ mod test_bridge_pool_tree { tree.delete_key(&Key::from(&transfers[1])) .expect("Test failed"); - let expected: Hash = - hash_pair(transfers[0].keccak256(), transfers[2].keccak256()) - .into(); + let expected = + hash_pair(transfers[0].keccak256(), transfers[2].keccak256()); assert_eq!(tree.root(), expected); } @@ -621,7 +645,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfer]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Check proofs for membership of single transfer @@ -652,7 +676,7 @@ mod test_bridge_pool_tree { let proof = tree .get_membership_proof(vec![transfers.remove(0)]) .expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that a multiproof works for leaves who are siblings @@ -682,7 +706,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values = vec![transfers[0].clone(), transfers[1].clone()]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test that proving an empty subset of leaves always works @@ -710,7 +734,7 @@ mod test_bridge_pool_tree { } let values = vec![]; let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())) + assert!(proof.verify(tree.root())) } /// Test a proof for all the leaves @@ -738,7 +762,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test a proof for all the leaves when the number of leaves is odd @@ -766,7 +790,7 @@ mod test_bridge_pool_tree { } transfers.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(transfers).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Test proofs of large trees @@ -795,7 +819,7 @@ mod test_bridge_pool_tree { transfers.sort_by_key(|t| t.keccak256()); let values: Vec<_> = transfers.iter().step_by(2).cloned().collect(); let proof = tree.get_membership_proof(values).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } /// Create a random set of transfers. @@ -857,7 +881,7 @@ mod test_bridge_pool_tree { to_prove.sort_by_key(|t| t.keccak256()); let proof = tree.get_membership_proof(to_prove).expect("Test failed"); - assert!(proof.verify(tree.root().into())); + assert!(proof.verify(tree.root())); } } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 3add9dd7f0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -130,6 +130,7 @@ pub enum StoreRef<'a> { } impl<'a> StoreRef<'a> { + /// Get owned copies of backing stores of our Merkle tree. pub fn to_owned(&self) -> Store { match *self { Self::Base(store) => Store::Base(store.to_owned()), @@ -140,6 +141,7 @@ impl<'a> StoreRef<'a> { } } + /// Borsh Seriliaze the backing stores of our Merkle tree. pub fn encode(&self) -> Vec { match self { Self::Base(store) => store.try_to_vec(), @@ -275,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, @@ -363,7 +364,10 @@ impl MerkleTree { account: (self.account.root().into(), self.account.store()), ibc: (self.ibc.root().into(), self.ibc.store()), pos: (self.pos.root().into(), self.pos.store()), - bridge_pool: (self.bridge_pool.root(), self.bridge_pool.store()), + bridge_pool: ( + self.bridge_pool.root().into(), + self.bridge_pool.store(), + ), } } @@ -535,6 +539,17 @@ impl MerkleTreeStoresRead { Store::BridgePool(store) => self.bridge_pool.1 = store, } } + + /// Read the backing store of the requested type + pub fn get_store(&self, store_type: StoreType) -> StoreRef { + match store_type { + 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), + StoreType::BridgePool => StoreRef::BridgePool(&self.bridge_pool.1), + } + } } /// The root and store pairs to be persistent diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 805caa6348..79779011a9 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -22,7 +22,8 @@ use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; pub use crate::ledger::storage::merkle_tree::{ - MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreType, + MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, + StoreType, }; use crate::ledger::storage::traits::StorageHasher; use crate::tendermint::merkle::proof::Proof; diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index ee9805a8ee..5ff9f7bb59 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -206,7 +206,7 @@ impl<'a> SubTreeWrite for &'a mut BridgePoolTree { fn subtree_delete(&mut self, key: &Key) -> Result { self.delete_key(key) .map_err(|err| Error::MerkleTree(err.to_string()))?; - Ok(self.root()) + Ok(self.root().into()) } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 6ae5be841c..26ed58274a 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -3,10 +3,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; +use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::Encode; -use crate::types::storage::{DbKeySeg, Key}; +use crate::types::keccak::KeccakHash; +use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; /// A transfer message to be submitted to Ethereum @@ -58,13 +60,15 @@ pub struct PendingTransfer { impl Encode for PendingTransfer { fn tokenize(&self) -> Vec { + let version = Token::Uint(1.into()); + let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![from, fee, to, amount, fee_from, nonce] + vec![version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -98,3 +102,49 @@ pub struct GasFee { /// The account of fee payer. pub payer: Address, } + +/// A Merkle root (Keccak hash) of the Ethereum +/// bridge pool that has been signed by validators' +/// Ethereum keys. +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] +pub struct MultiSignedMerkleRoot { + /// The signatures from validators + pub sigs: Vec, + /// The Merkle root being signed + pub root: KeccakHash, + /// The block height at which this root was valid + pub height: BlockHeight, +} + +impl Encode for MultiSignedMerkleRoot { + fn tokenize(&self) -> Vec { + let MultiSignedMerkleRoot { sigs, root, .. } = self; + // TODO: check the tokenization of the signatures + let sigs = Token::Array( + sigs.iter() + .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) + .collect(), + ); + let root = Token::FixedBytes(root.0.to_vec()); + vec![sigs, root] + } +} + +/// All the information to relay to Ethereum +/// that a set of transfers exist in the Ethereum +/// bridge pool. +pub struct RelayProof { + /// A merkle root signed by ta quorum of validators + pub root: MultiSignedMerkleRoot, + /// A membership proof + pub proof: BridgePoolProof, +} + +impl Encode for RelayProof { + fn tokenize(&self) -> Vec { + vec![ + Token::Array(self.root.tokenize()), + Token::Array(self.proof.tokenize()), + ] + } +} From 4c57c0e8ff8d22f7d589835f8ba4befb445403a6 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1325/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- apps/src/lib/node/ledger/shell/queries.rs | 232 ++++++++++++++++-- shared/Cargo.toml | 1 + .../ledger/eth_bridge/storage/bridge_pool.rs | 7 +- shared/src/ledger/storage/merkle_tree.rs | 3 +- shared/src/ledger/storage/mod.rs | 14 +- shared/src/types/eth_bridge_pool.rs | 3 + shared/src/types/storage.rs | 11 + 7 files changed, 235 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 7ad4849ec9..3efd5c4d54 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -6,13 +6,13 @@ use std::default::Default; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_pending_key, get_signed_root_key, BridgePoolTree, + get_key_from_hash, get_pending_key, get_signed_root_key, }; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; -use namada::ledger::storage::{StoreRef, StoreType}; +use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -21,7 +21,8 @@ use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::{Epoch, Key, PrefixValue}; +use namada::types::storage::MembershipProof::BridgePool; +use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -365,13 +366,25 @@ where .db .read_merkle_tree_stores(self.storage.last_height) { - let transfers = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store.try_to_vec().unwrap(), + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, _ => unreachable!(), }; + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); response::Query { code: 0, - value: transfers, + value: transfers.try_to_vec().unwrap(), ..Default::default() } } else { @@ -421,12 +434,7 @@ where let tree = if let Ok(Some(stores)) = self.storage.db.read_merkle_tree_stores(signed_root.height) { - match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => { - BridgePoolTree::new(store.clone()) - } - _ => unreachable!(), - } + MerkleTree::::new(stores) } else { return response::Query { code: 1, @@ -439,22 +447,14 @@ where ..Default::default() }; }; - if tree.root() != signed_root.root { - return response::Query { - code: 1, - log: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - info: "The latest signed root does not equal the root of \ - corresponding Merkle tree" - .into(), - ..Default::default() - }; - } + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_membership_proof(&keys, transfers) { - Ok(proof) => response::Query { + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { root: signed_root, @@ -469,6 +469,7 @@ where info: e.to_string(), ..Default::default() }, + _ => unreachable!(), } } else { response::Query { @@ -870,10 +871,20 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { + use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; + use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; + use namada::types::ethereum_events::EthAddress; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -1048,4 +1059,173 @@ mod test_queries { (2, 28, Err(true)), ], } + + /// Test that reading the bridge pool works + #[test] + fn test_read_bridge_pool() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[test] + fn test_bridge_pool_updates() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + shell + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // check the response + let resp = shell.read_ethereum_bridge_pool(); + assert_eq!(resp.code, 0); + let pool = + BTreeSet::::try_from_slice(resp.value.as_slice()) + .expect("Test failed"); + assert_eq!(pool, BTreeSet::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[test] + fn test_get_merkle_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write( + &get_signed_root_key(), + signed_root.clone().try_to_vec().unwrap(), + ) + .expect("Test failed"); + + // commit the changes and increase block height + let _ = shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + let resp = shell.generate_bridge_pool_proof( + vec![transfer.clone()].try_to_vec().expect("Test failed"), + ); + assert_eq!(resp.code, 0); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof( + std::array::from_ref(&Key::from(&transfer)), + vec![transfer], + ) + .expect("Test failed"); + + let proof = RelayProof { + root: signed_root, + proof, + } + .encode(); + assert_eq!(proof, resp.value); + } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 8e9029a023..6464d5b394 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -27,10 +27,15 @@ pub struct Error(#[from] eyre::Error); /// Get the storage key for the transfers in the pool pub fn get_pending_key(transfer: &PendingTransfer) -> Key { + get_key_from_hash(&transfer.keccak256()) +} + +/// Get the storage key for the transfers using the hash +pub fn get_key_from_hash(hash: &KeccakHash) -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(BRIDGE_POOL_ADDRESS), - transfer.keccak256().to_db_key(), + hash.to_db_key(), ], } } diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 79779011a9..6bd6910b7f 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -445,18 +445,16 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]>, + value: impl Into, ) -> Result<(u64, i64)> { - // Note that this method is the same as `StorageWrite::write_bytes`, - // but with gas and storage bytes len diff accounting + let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); - let value = value.as_ref(); - self.block.tree.update(key, &value)?; + self.block.tree.update(key, value.clone())?; - let len = value.len(); - let gas = key.len() + len; + let bytes = value.to_bytes(); + let gas = key.len() + bytes.len(); let size_diff = - self.db.write_subspace_val(self.last_height, key, value)?; + self.db.write_subspace_val(self.last_height, key, bytes)?; Ok((gas as _, size_diff)) } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 26ed58274a..91512051ea 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -19,6 +19,7 @@ use crate::types::token::Amount; Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -45,6 +46,7 @@ pub struct TransferToEthereum { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, @@ -91,6 +93,7 @@ impl From<&PendingTransfer> for Key { Hash, PartialOrd, PartialEq, + Ord, Eq, BorshSerialize, BorshDeserialize, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index c9ccbf4825..56e474b05d 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -256,6 +256,7 @@ impl FromStr for Key { /// several Merkle trees, each of which is /// responsible for understanding how to parse /// this value. +#[derive(Debug, Clone)] pub enum MerkleValue { /// raw bytes Bytes(Vec), @@ -278,6 +279,16 @@ impl From for MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From be752ff8a1933518bcbeb8a0b16582f47072218b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 14:14:01 +0000 Subject: [PATCH 1326/1995] Use Vec instead of raw Vec --- apps/src/lib/client/rpc.rs | 20 ++++++-------------- shared/src/ledger/queries/shell.rs | 13 +++++++------ shared/src/ledger/storage/mod.rs | 14 +++++++++----- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7b1017fbda..364f9bf325 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -75,8 +75,7 @@ pub async fn query_tx_status( loop { tracing::debug!(query = ?status, "Querying tx status"); - let raw_response = match send_tx_event_query(&client, status).await - { + let mut events = match query_tx_events(&client, status).await { Ok(response) => response, Err(err) => { tracing::debug!(%err, "ABCI query failed"); @@ -84,13 +83,6 @@ pub async fn query_tx_status( continue; } }; - let mut events = match Vec::::try_from_slice(&raw_response) { - Ok(events) => events, - Err(err) => { - eprintln!("Error decoding the event value: {err}"); - cli::safe_exit(1); - } - }; if let Some(e) = events.pop() { // we should only have one event matching the query break Ok(e); @@ -1448,12 +1440,12 @@ impl<'a> From> for Query { } } -pub async fn send_tx_event_query( +pub async fn query_tx_events( client: &HttpClient, - tx_query: TxEventQuery<'_>, -) -> Result, queries::tm::Error> { - let tx_hash: Hash = tx_query.tx_hash().try_into().unwrap(); - match tx_query { + tx_event_query: TxEventQuery<'_>, +) -> Result, queries::tm::Error> { + let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); + match tx_event_query { TxEventQuery::Accepted(_) => { RPC.shell().accepted(client, &tx_hash).await } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 299a3773fd..cbe1638b25 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -2,6 +2,7 @@ use borsh::BorshSerialize; use tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::ledger::events::log::dumb_queries; +use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; @@ -33,10 +34,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash] ) -> Vec = applied, + ( "applied" / [tx_hash: Hash] ) -> Vec = applied, } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -57,10 +58,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash]) -> Vec = applied, + ( "applied" / [tx_hash: Hash]) -> Vec = applied, } // Handlers: @@ -226,7 +227,7 @@ where fn accepted( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -238,7 +239,7 @@ where fn applied( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index d88c8e906c..ffaff8b347 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -11,10 +11,10 @@ pub mod write_log; use core::fmt::Debug; use std::array; -use borsh::BorshSerialize; use thiserror::Error; use super::events::log::{dumb_queries, EventLog}; +use super::events::Event; use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; @@ -277,7 +277,10 @@ pub trait DBWriteBatch { /// Methods for querying and mutating an event log. pub trait EventLogExt { /// Query events in the event log matching the given query. - fn query_event_log(&self, matcher: dumb_queries::QueryMatcher) -> Vec; + fn query_event_log( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> Vec; /// Return a reference to the [`EventLog`]. fn event_log(&self) -> &EventLog; /// Return a mutable reference to the [`EventLog`]. @@ -290,13 +293,14 @@ where H: StorageHasher, { /// Query events in the event log matching the given query. - fn query_event_log(&self, matcher: dumb_queries::QueryMatcher) -> Vec { + fn query_event_log( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> Vec { self.event_log() .iter_with_matcher(matcher) .cloned() .collect::>() - .try_to_vec() - .unwrap() } /// Return a reference to the [`EventLog`]. From a900dc5a9077eb90591a07f6ba21dff4ca8149a6 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1327/1995] [feat]: Added recovery ids to secp256k1 signatures --- apps/src/lib/node/ledger/shell/queries.rs | 18 ++++++++---------- shared/Cargo.toml | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3efd5c4d54..b8ed11a93e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -17,6 +17,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::ethereum_events::EthAddress; use namada::types::key; @@ -1084,7 +1085,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1121,7 +1122,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1129,7 +1130,7 @@ mod test_queries { .storage .delete(&get_pending_key(&transfer)) .expect("Test failed"); - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage @@ -1137,7 +1138,7 @@ mod test_queries { .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // check the response @@ -1181,7 +1182,7 @@ mod test_queries { }; // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; // update the pool @@ -1195,14 +1196,11 @@ mod test_queries { // add the signature for the pool at the previous block height shell .storage - .write( - &get_signed_root_key(), - signed_root.clone().try_to_vec().unwrap(), - ) + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) .expect("Test failed"); // commit the changes and increase block height - let _ = shell.storage.commit().expect("Test failed"); + shell.storage.commit().expect("Test failed"); shell.storage.block.height = shell.storage.block.height + 1; let resp = shell.generate_bridge_pool_proof( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From bbd81f0a0610621fb830cb8daf548a2248af1db0 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:00:01 +0200 Subject: [PATCH 1328/1995] [fix]: Fixed bug that produced proof for values not in the bridge pool and covered with test --- apps/src/lib/node/ledger/shell/queries.rs | 64 ++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index b8ed11a93e..134e42a72a 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -402,7 +402,7 @@ where } } - /// Generate a merkle proof for the inclusion of then + /// Generate a merkle proof for the inclusion of the /// requested transfers in the Ethereum bridge pool. fn generate_bridge_pool_proof( &self, @@ -1226,4 +1226,66 @@ mod test_queries { .encode(); assert_eq!(proof, resp.value); } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[test] + fn test_cannot_get_proof() { + let (mut shell, _, _) = test_utils::setup(); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + shell + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: vec![], + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + shell + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + shell + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + shell.storage.commit().expect("Test failed"); + shell.storage.block.height = shell.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = shell.generate_bridge_pool_proof( + vec![transfer2].try_to_vec().expect("Test failed"), + ); + // thus proof generation should fail + assert_eq!(resp.code, 1); + } } From 1e504afcef9893797ef7a4530c2caa8d084c4116 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 16:51:21 +0200 Subject: [PATCH 1329/1995] [feat]: Corrected the abi encodings of bridge proofs --- apps/src/lib/node/ledger/shell/queries.rs | 6 +++- .../ledger/eth_bridge/storage/bridge_pool.rs | 14 +++++--- shared/src/types/eth_bridge_pool.rs | 34 +++++++++++-------- shared/src/types/keccak.rs | 12 +++---- shared/src/types/key/secp256k1.rs | 12 +++++++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 134e42a72a..9efc2a0b16 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -460,6 +460,8 @@ where value: RelayProof { root: signed_root, proof, + // TODO: Use real nonce + nonce: 0.into(), } .encode(), ..Default::default() @@ -1222,6 +1224,8 @@ mod test_queries { let proof = RelayProof { root: signed_root, proof, + // TODO: Use a real nonce + nonce: 0.into(), } .encode(); assert_eq!(proof, resp.value); @@ -1264,7 +1268,7 @@ mod test_queries { shell.storage.block.height = shell.storage.block.height + 1; // update the pool - let mut transfer2 = transfer.clone(); + let mut transfer2 = transfer; transfer2.transfer.amount = 1.into(); shell .storage diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 6464d5b394..f017e2cf1e 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -146,7 +146,11 @@ impl BridgePoolTree { // get the leaf hashes let leaves: BTreeSet = values.iter().map(|v| v.keccak256()).collect(); - + if !leaves.is_subset(&self.leaves) { + return Err(eyre!( + "Cannot generate proof for values that aren't in the tree" + ).into()); + } let mut proof_hashes = vec![]; let mut flags = vec![]; let mut hashes: Vec<_> = self @@ -330,8 +334,8 @@ impl BridgePoolProof { } } -impl Encode for BridgePoolProof { - fn tokenize(&self) -> Vec { +impl Encode<3> for BridgePoolProof { + fn tokenize(&self) -> [Token; 3] { let BridgePoolProof { proof, leaves, @@ -346,12 +350,12 @@ impl Encode for BridgePoolProof { let transfers = Token::Array( leaves .iter() - .map(|t| Token::FixedArray(t.tokenize())) + .map(|t| Token::FixedArray(t.tokenize().to_vec())) .collect(), ); let flags = Token::Array(flags.iter().map(|flag| Token::Bool(*flag)).collect()); - vec![proof, transfers, flags] + [proof, transfers, flags] } } diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 91512051ea..293b398eef 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -60,8 +60,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode for PendingTransfer { - fn tokenize(&self) -> Vec { +impl Encode<7> for PendingTransfer { + fn tokenize(&self) -> [Token; 7] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); @@ -70,7 +70,7 @@ impl Encode for PendingTransfer { let amount = Token::Uint(u64::from(self.transfer.amount).into()); let fee_from = Token::String(self.gas_fee.payer.to_string()); let nonce = Token::Uint(self.transfer.nonce.clone().into()); - vec![version, namespace, from, to, amount, fee, fee_from, nonce] + [version, namespace, from, to, amount, fee, fee_from, nonce] } } @@ -119,17 +119,15 @@ pub struct MultiSignedMerkleRoot { pub height: BlockHeight, } -impl Encode for MultiSignedMerkleRoot { - fn tokenize(&self) -> Vec { +impl Encode<2> for MultiSignedMerkleRoot { + fn tokenize(&self) -> [Token; 2] { let MultiSignedMerkleRoot { sigs, root, .. } = self; // TODO: check the tokenization of the signatures let sigs = Token::Array( - sigs.iter() - .map(|sig| Token::FixedBytes(sig.0.serialize().to_vec())) - .collect(), + sigs.iter().map(|sig| sig.tokenize()[0].clone()).collect(), ); let root = Token::FixedBytes(root.0.to_vec()); - vec![sigs, root] + [sigs, root] } } @@ -141,13 +139,21 @@ pub struct RelayProof { pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, + /// A nonce for the batch for replay protection + pub nonce: Uint, } -impl Encode for RelayProof { - fn tokenize(&self) -> Vec { - vec![ - Token::Array(self.root.tokenize()), - Token::Array(self.proof.tokenize()), +impl Encode<6> for RelayProof { + fn tokenize(&self) -> [Token; 6] { + let [sigs, root] = self.root.tokenize(); + let [proof, transfers, flags] = self.proof.tokenize(); + [ + sigs, + transfers, + root, + proof, + flags, + Token::Uint(self.nonce.clone().into()), ] } } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 4173e5714a..8f3bbfc505 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -111,15 +111,15 @@ pub mod encode { use super::*; /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. - fn tokenize(&self) -> Vec; + fn tokenize(&self) -> [Token; N]; /// Returns the encoded [`Token`] instances. fn encode(&self) -> Vec { let tokens = self.tokenize(); - ethabi::encode(&tokens) + ethabi::encode(tokens.as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -156,10 +156,10 @@ pub mod encode { /// to `abi.encode`. pub type AbiEncode = [Token; N]; - impl Encode for AbiEncode { + impl Encode for AbiEncode { #[inline] - fn tokenize(&self) -> Vec { - self.to_vec() + fn tokenize(&self) -> [Token; N] { + self.clone() } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index d83f2ea953..5e5278b06e 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; +use ethabi::Token; use libsecp256k1::RecoveryId; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; @@ -20,6 +21,7 @@ use super::{ SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; use crate::types::ethereum_events::EthAddress; +use crate::types::keccak::encode::Encode; /// The provided constant is for a traditional /// signature on this curve. For Ethereum, an extra byte is included @@ -422,6 +424,16 @@ impl BorshSchema for Signature { } } +impl Encode<1> for Signature { + fn tokenize(&self) -> [Token; 1] { + let sig_serialized = libsecp256k1::Signature::serialize(&self.0); + let r = Token::FixedBytes(sig_serialized[..32].to_vec()); + let s = Token::FixedBytes(sig_serialized[32..].to_vec()); + let v = Token::FixedBytes(vec![self.1.serialize()]); + [Token::FixedArray(vec![r, s, v])] + } +} + #[allow(clippy::derive_hash_xor_eq)] impl Hash for Signature { fn hash(&self, state: &mut H) { From 933ab7b93dbdb5b6ce9d3e91b8b09864d4bba2c3 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 17 Oct 2022 17:25:03 +0200 Subject: [PATCH 1330/1995] [feat]: Added val set args for relaying to ethereum --- apps/src/lib/node/ledger/shell/queries.rs | 3 ++ shared/src/types/eth_bridge_pool.rs | 9 +++-- shared/src/types/ethereum_events.rs | 1 + .../vote_extensions/validator_set_update.rs | 34 ++++++++++++++++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 9efc2a0b16..d63ee53915 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -458,6 +458,8 @@ where Ok(BridgePool(proof)) => response::Query { code: 0, value: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce @@ -1222,6 +1224,7 @@ mod test_queries { .expect("Test failed"); let proof = RelayProof { + validator_args: Default::default(), root: signed_root, proof, // TODO: Use a real nonce diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 293b398eef..a6db278f2b 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -10,6 +10,7 @@ use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; +use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. @@ -135,6 +136,8 @@ impl Encode<2> for MultiSignedMerkleRoot { /// that a set of transfers exist in the Ethereum /// bridge pool. pub struct RelayProof { + /// Information about the signing validators + pub validator_args: ValidatorSetArgs, /// A merkle root signed by ta quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof @@ -143,11 +146,13 @@ pub struct RelayProof { pub nonce: Uint, } -impl Encode<6> for RelayProof { - fn tokenize(&self) -> [Token; 6] { +impl Encode<7> for RelayProof { + fn tokenize(&self) -> [Token; 7] { + let [val_set_args] = self.validator_args.tokenize(); let [sigs, root] = self.root.tokenize(); let [proof, transfers, flags] = self.proof.tokenize(); [ + val_set_args, sigs, transfers, root, diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index ed582c4911..8e7092efe2 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -16,6 +16,7 @@ use crate::types::token::Amount; #[derive( Clone, Debug, + Default, Hash, PartialEq, Eq, diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 4c3c75fab1..1312371927 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,7 +9,7 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; +use crate::types::ethereum_events::{EthAddress, Uint}; use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; @@ -268,6 +268,38 @@ fn compute_hash( ]) } +/// Struct for serializing validator set +/// arguments with ABI for Ethereum smart +/// contracts. +#[derive(Debug, Clone, Default)] +pub struct ValidatorSetArgs { + /// Ethereum address of validators + pub validators: Vec, + /// Voting powers of validators + pub powers: Vec, + /// A nonce + pub nonce: Uint, +} + +impl Encode<1> for ValidatorSetArgs { + fn tokenize(&self) -> [Token; 1] { + let addrs = Token::Array( + self.validators + .iter() + .map(|addr| Token::Address(addr.0.into())) + .collect(), + ); + let powers = Token::Array( + self.powers + .iter() + .map(|power| Token::Uint(power.clone().into())) + .collect(), + ); + let nonce = Token::Uint(self.nonce.clone().into()); + [Token::FixedArray(vec![addrs, powers, nonce])] + } +} + // this is only here so we don't pollute the // outer namespace with serde traits mod tag { From 274e6791e933a76f4b221dc3dd9741e54a344189 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1331/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d63ee53915..e5f5b9995b 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -19,7 +19,6 @@ use namada::types::eth_bridge_pool::{ }; use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 9910d4f8195ddc526a03cbc4031261b050e4c4e7 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1332/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- shared/src/types/eth_bridge_pool.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index a6db278f2b..23fe162ab6 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -61,8 +61,8 @@ pub struct PendingTransfer { pub gas_fee: GasFee, } -impl Encode<7> for PendingTransfer { - fn tokenize(&self) -> [Token; 7] { +impl Encode<8> for PendingTransfer { + fn tokenize(&self) -> [Token; 8] { let version = Token::Uint(1.into()); let namespace = Token::String("transfer".into()); let from = Token::Address(self.transfer.asset.0.into()); From ee34c8848d6314e2e9800d185e8f22b144894366 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1333/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 56e474b05d..0557d368e1 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -289,6 +289,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 8bf1f4e4fd00f1b69bcc800e7fc1158a4c593658 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1334/1995] [feat]: Added recovery ids to secp256k1 signatures --- shared/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From d087ee0f8fc47e814ba4fd98de23bd3c32ad1615 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:44:52 +0200 Subject: [PATCH 1335/1995] [chore]: rebasing on changes from previous feature prs --- shared/src/types/storage.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 0557d368e1..1fec5c7479 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -284,17 +284,9 @@ impl MerkleValue { pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + Self::BridgePoolTransfer(transfer) => { + transfer.try_to_vec().unwrap() + } } } } From f687a4da05eb92711c6a81faf87ed68563a518ff Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1336/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From c869278efcc6bb7b0b537adeabfbc0edff121799 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 14:21:13 +0000 Subject: [PATCH 1337/1995] Remove no longer used From impl --- apps/src/lib/client/rpc.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 364f9bf325..f7e56d83d8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1418,14 +1418,6 @@ impl<'a> TxEventQuery<'a> { } } -impl<'a> From> for crate::facade::tendermint::abci::Path { - fn from(tx_query: TxEventQuery<'a>) -> Self { - format!("{}/{}", tx_query.event_type(), tx_query.tx_hash()) - .parse() - .expect("This operation is infallible") - } -} - /// Transaction event queries are semantically a subset of general queries impl<'a> From> for Query { fn from(tx_query: TxEventQuery<'a>) -> Self { From 73d9989692c22f10de60cca1d4acfadfae27ac00 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 15:23:52 +0100 Subject: [PATCH 1338/1995] [chore]: Upgrading to v0.8 --- Cargo.lock | 23 +++++++++++++++++-- apps/src/lib/node/ledger/shell/queries.rs | 9 ++++---- shared/Cargo.toml | 1 + .../src/ledger/eth_bridge/bridge_pool_vp.rs | 3 +-- .../ledger/eth_bridge/storage/bridge_pool.rs | 9 ++++---- shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 16 ------------- wasm/Cargo.lock | 19 +++++++++++++++ 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e081e3e4bd..826a4e745b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,7 +1058,7 @@ dependencies = [ "num-bigint 0.4.3", "num-traits 0.2.15", "num256", - "secp256k1", + "secp256k1 0.24.1", "serde 1.0.145", "serde-rlp", "serde_bytes", @@ -3352,6 +3352,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rust_decimal", + "secp256k1 0.21.3", "serde 1.0.145", "serde_json", "sha2 0.9.9", @@ -5084,13 +5085,31 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys 0.4.2", +] + [[package]] name = "secp256k1" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.6.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", ] [[package]] diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e5f5b9995b..0c7ad256ab 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1072,6 +1072,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1109,6 +1110,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1162,6 +1164,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, @@ -1216,10 +1219,7 @@ mod test_queries { BTreeSet::from([transfer.keccak256()]), ); let proof = tree - .get_membership_proof( - std::array::from_ref(&Key::from(&transfer)), - vec![transfer], - ) + .get_membership_proof(vec![transfer]) .expect("Test failed"); let proof = RelayProof { @@ -1243,6 +1243,7 @@ mod test_queries { transfer: TransferToEthereum { asset: EthAddress([0; 20]), recipient: EthAddress([0; 20]), + sender: bertha_address(), amount: 0.into(), nonce: 0.into(), }, diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index c810a1db5e..e696051c72 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,8 +25,7 @@ use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{xan, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::types::keccak::encode::Encode; -use crate::types::storage::{Key, KeySeg}; +use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index f017e2cf1e..73fcab868b 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -125,9 +125,9 @@ impl BridgePoolTree { } } - /// Return the root as a [struct@`Hash`] type. - pub fn root(&self) -> Hash { - self.root.clone().into() + /// Return the root as a [`struct@Hash`] type. + pub fn root(&self) -> KeccakHash { + self.root.clone() } /// Get a reference to the backing store @@ -149,7 +149,8 @@ impl BridgePoolTree { if !leaves.is_subset(&self.leaves) { return Err(eyre!( "Cannot generate proof for values that aren't in the tree" - ).into()); + ) + .into()); } let mut proof_hashes = vec![]; let mut flags = vec![]; diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 1fec5c7479..37e96fb9b6 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -800,22 +800,6 @@ impl_int_key_seg!(u32, i32, 4); impl_int_key_seg!(u64, i64, 8); impl_int_key_seg!(u128, i128, 16); -impl KeySeg for KeccakHash { - fn parse(seg: String) -> Result { - seg.clone() - .try_into() - .map_err(|_| Error::ParseError(seg, "Hash".into())) - } - - fn raw(&self) -> String { - self.to_string() - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index e8a72f19d2..34c1f76a8c 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1502,6 +1502,7 @@ dependencies = [ "rand", "rand_core 0.6.4", "rust_decimal", + "secp256k1", "serde", "serde_json", "sha2 0.9.9", @@ -2305,6 +2306,24 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "0.11.0" From 065b6d8eed6d34ab0ab27369b5158d8b4f147ef5 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1339/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 3339 ++++++++++++++++++++++++++++++++------------- shared/Cargo.toml | 1 - 2 files changed, 2379 insertions(+), 961 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 826a4e745b..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,21 +9,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" dependencies = [ "bitflags", - "bytes 1.2.1", + "bytes 1.1.0", "futures-core", "futures-sink", "log 0.4.17", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.3", ] [[package]] name = "actix-http" -version = "3.2.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" +checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" dependencies = [ "actix-codec", "actix-rt", @@ -32,7 +32,7 @@ dependencies = [ "ahash", "base64 0.13.0", "bitflags", - "bytes 1.2.1", + "bytes 1.1.0", "bytestring", "derive_more", "encoding_rs", @@ -45,13 +45,13 @@ dependencies = [ "itoa", "language-tags 0.3.2", "local-channel", + "log 0.4.17", "mime 0.3.16", - "percent-encoding 2.2.0", - "pin-project-lite", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", "rand 0.8.5", - "sha1", - "smallvec 1.10.0", - "tracing 0.1.37", + "sha-1 0.10.0", + "smallvec 1.8.0", "zstd", ] @@ -73,7 +73,7 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", - "pin-project-lite", + "pin-project-lite 0.2.9", ] [[package]] @@ -90,19 +90,19 @@ dependencies = [ "http", "log 0.4.17", "openssl", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio-openssl", - "tokio-util 0.7.4", + "tokio-util 0.7.3", ] [[package]] name = "actix-utils" -version = "3.0.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" dependencies = [ "local-waker", - "pin-project-lite", + "pin-project-lite 0.2.9", ] [[package]] @@ -111,7 +111,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.2", + "gimli 0.26.1", ] [[package]] @@ -120,22 +120,57 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array 0.14.5", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures 0.2.2", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle 2.4.1", +] + [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", "once_cell", "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -160,9 +195,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" [[package]] name = "ark-bls12-381" @@ -306,9 +341,15 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ascii" -version = "1.1.0" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" + +[[package]] +name = "asn1_der" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" [[package]] name = "assert_cmd" @@ -332,9 +373,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.7.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ "concurrent-queue", "event-listener", @@ -357,25 +398,26 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.3.0" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" dependencies = [ "async-channel", "async-executor", "async-io", - "async-lock", + "async-mutex", "blocking", "futures-lite", + "num_cpus", "once_cell", ] [[package]] name = "async-io" -version = "1.9.0" -source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ - "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -383,9 +425,8 @@ dependencies = [ "once_cell", "parking", "polling", - "rustversion", "slab", - "socket2", + "socket2 0.4.4", "waker-fn", "winapi 0.3.9", ] @@ -399,20 +440,28 @@ 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.5.0" -source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", - "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", "libc", "once_cell", - "rustversion", "signal-hook", "winapi 0.3.9", ] @@ -428,7 +477,7 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", "futures-channel", "futures-core", "futures-io", @@ -439,12 +488,26 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.9", "pin-utils", "slab", "wasm-bindgen-futures", ] +[[package]] +name = "async-std-resolver" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" +dependencies = [ + "async-std", + "async-trait", + "futures-io", + "futures-util", + "pin-utils", + "trust-dns-resolver", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -468,15 +531,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.3.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" dependencies = [ "proc-macro2", "quote", @@ -492,13 +555,35 @@ dependencies = [ "futures-io", "futures-util", "log 0.4.17", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", "tokio-rustls", "tungstenite 0.12.0", "webpki-roots", ] +[[package]] +name = "asynchronous-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" +dependencies = [ + "bytes 1.1.0", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite 0.2.9", +] + +[[package]] +name = "atomic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -507,12 +592,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" -version = "0.2.14" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" dependencies = [ - "hermit-abi", "libc", + "termion", "winapi 0.3.9", ] @@ -545,7 +630,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.13.0", - "bytes 1.2.1", + "bytes 1.1.0", "cfg-if 1.0.0", "derive_more", "futures-core", @@ -556,10 +641,10 @@ dependencies = [ "log 0.4.17", "mime 0.3.16", "openssl", - "percent-encoding 2.2.0", - "pin-project-lite", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", "rand 0.8.5", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", @@ -567,16 +652,16 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.29.0", + "object", "rustc-demangle", ] @@ -599,6 +684,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.0" @@ -611,34 +702,25 @@ 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.145", -] - [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", ] [[package]] name = "bindgen" -version = "0.60.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static", + "lazy_static 1.4.0", "lazycell", "peeking_take_while", "proc-macro2", @@ -650,9 +732,9 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ "bit-vec", ] @@ -681,13 +763,24 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + [[package]] name = "blake2" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.5", + "digest 0.10.3", ] [[package]] @@ -722,7 +815,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.5", + "digest 0.10.3", ] [[package]] @@ -744,16 +837,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.6", + "generic-array 0.14.5", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.5", ] [[package]] @@ -826,13 +919,19 @@ dependencies = [ "syn", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "memchr", "regex-automata", ] @@ -849,9 +948,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-slice-cast" @@ -876,9 +975,9 @@ dependencies = [ [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -886,9 +985,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" dependencies = [ "proc-macro2", "quote", @@ -913,9 +1012,15 @@ dependencies = [ [[package]] name = "bytes" -version = "1.2.1" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "bytestring" @@ -923,7 +1028,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", ] [[package]] @@ -943,11 +1048,24 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "cargo-watch" +version = "7.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" +dependencies = [ + "clap 2.34.0", + "log 0.4.17", + "shell-escape", + "stderrlog", + "watchexec", +] + [[package]] name = "cc" -version = "1.0.73" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" dependencies = [ "jobserver", ] @@ -975,13 +1093,38 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.2" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures 0.1.5", + "zeroize", +] + +[[package]] +name = "chacha20" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.2.2", +] + +[[package]] +name = "chacha20poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +dependencies = [ + "aead", + "chacha20 0.7.1", + "cipher", + "poly1305", + "zeroize", ] [[package]] @@ -991,8 +1134,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", + "js-sys", "num-integer", "num-traits 0.2.15", + "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -1008,7 +1154,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.5", ] [[package]] @@ -1022,15 +1168,31 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", "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", + "term_size", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + [[package]] name = "clap" version = "3.0.0-beta.2" @@ -1039,31 +1201,31 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static", + "lazy_static 1.4.0", "os_str_bytes", - "strsim", + "strsim 0.10.0", "termcolor", - "textwrap", + "textwrap 0.12.1", "unicode-width", "vec_map", ] [[package]] name = "clarity" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" +checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num-bigint 0.4.3", "num-traits 0.2.15", "num256", - "secp256k1 0.24.1", - "serde 1.0.145", + "secp256k1", + "serde 1.0.137", "serde-rlp", "serde_bytes", "serde_derive", - "sha3 0.10.6", + "sha3 0.10.2", ] [[package]] @@ -1080,16 +1242,6 @@ name = "clru" version = "0.5.0" source = "git+https://github.com/marmeladema/clru-rs.git?rev=71ca566#71ca566915f21f3c308091ca7756a91b0f8b5afc" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "color-eyre" version = "0.5.11" @@ -1113,7 +1265,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.30", + "tracing-core 0.1.27", "tracing-error", ] @@ -1129,9 +1281,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] @@ -1142,10 +1294,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "nom 5.1.2", "rust-ini", - "serde 1.0.145", + "serde 1.0.137", "serde-hjson", "serde_json", "toml", @@ -1191,9 +1343,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.5" +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" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -1220,7 +1381,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.10.0", + "smallvec 1.8.0", "target-lexicon", ] @@ -1254,7 +1415,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.10.0", + "smallvec 1.8.0", "target-lexicon", ] @@ -1269,34 +1430,35 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", ] [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", + "lazy_static 1.4.0", "memoffset", "scopeguard", ] @@ -1309,16 +1471,17 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static", + "lazy_static 1.4.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", + "lazy_static 1.4.0", ] [[package]] @@ -1329,22 +1492,32 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.5", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + [[package]] name = "crypto-mac" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", - "subtle", + "generic-array 0.14.5", + "subtle 2.4.1", ] [[package]] @@ -1364,20 +1537,40 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.26" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" dependencies = [ "quote", "syn", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "cuckoofilter" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" +dependencies = [ + "byteorder", + "fnv", + "rand 0.7.3", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1387,7 +1580,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle", + "subtle 2.4.1", "zeroize", ] @@ -1399,85 +1592,76 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.4", + "rand_core 0.6.3", "subtle-ng", "zeroize", ] [[package]] -name = "cxx" -version = "1.0.79" +name = "darling" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" +checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "darling_core 0.12.4", + "darling_macro 0.12.4", ] [[package]] -name = "cxx-build" -version = "1.0.79" +name = "darling" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn", + "darling_core 0.13.4", + "darling_macro 0.13.4", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.79" +name = "darling_core" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" +checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "strsim 0.10.0", "syn", ] [[package]] -name = "darling" -version = "0.14.1" +name = "darling_core" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ - "darling_core", - "darling_macro", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "darling_core" -version = "0.14.1" +name = "darling_macro" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", + "darling_core 0.12.4", "quote", "syn", ] [[package]] name = "darling_macro" -version = "0.14.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", "syn", ] @@ -1500,23 +1684,54 @@ dependencies = [ ] [[package]] -name = "derive_more" -version = "0.99.17" +name = "derive_builder" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30" dependencies = [ - "convert_case", + "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", - "rustc_version 0.4.0", "syn", ] [[package]] -name = "diff" -version = "0.1.13" +name = "derive_builder_macro" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "difflib" @@ -1539,18 +1754,48 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.5", ] [[package]] name = "digest" -version = "0.10.5" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.2", "crypto-common", - "subtle", + "subtle 2.4.1", +] + +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "dns-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" +dependencies = [ + "byteorder", + "quick-error 1.2.3", ] [[package]] @@ -1573,7 +1818,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static", + "lazy_static 1.4.0", "proc-macro-error", "proc-macro2", "quote", @@ -1597,7 +1842,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", "signature", ] @@ -1609,8 +1854,8 @@ checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core 0.6.4", - "serde 1.0.145", + "rand_core 0.6.3", + "serde 1.0.137", "sha2 0.9.9", "thiserror", "zeroize", @@ -1626,7 +1871,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1634,9 +1879,22 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "embed-resource" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" +dependencies = [ + "cc", + "rustc_version 0.4.0", + "toml", + "vswhom", + "winreg 0.10.1", +] [[package]] name = "encoding_rs" @@ -1647,6 +1905,18 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "enum-as-inner" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -1669,25 +1939,34 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.12" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log 0.4.17", +] + [[package]] name = "error" version = "0.1.9" @@ -1706,7 +1985,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.145", + "serde 1.0.137", "serde_json", ] @@ -1720,9 +1999,9 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.145", + "serde 1.0.137", "serde_json", - "sha3 0.10.6", + "sha3 0.10.2", "thiserror", "uint", ] @@ -1756,9 +2035,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" [[package]] name = "expectrl" @@ -1796,9 +2075,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -1806,7 +2085,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" dependencies = [ "anyhow", "ark-bls12-381", @@ -1817,39 +2096,39 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", - "blake2", + "blake2 0.10.4", "blake2b_simd", "borsh", - "digest 0.10.5", + "digest 0.10.3", "ed25519-dalek", "either", "ferveo-common", "group-threshold-cryptography", "hex", - "itertools", + "itertools 0.10.3", "measure_time", "miracl_core", "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", "serde_json", "subproductdomain", - "subtle", + "subtle 2.4.1", "zeroize", ] [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" dependencies = [ "anyhow", "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", ] @@ -1862,14 +2141,14 @@ dependencies = [ "cc", "libc", "mktemp", - "nix 0.24.2", + "nix 0.24.1", ] [[package]] name = "file-serve" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" +checksum = "67a09c8127b1a49f66ac56a7c9efe420e2ab23e00266ea4144cc2b905076a7f1" dependencies = [ "log 0.4.17", "mime_guess", @@ -1878,14 +2157,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", - "windows-sys 0.36.1", + "redox_syscall 0.2.13", + "winapi 0.3.9", ] [[package]] @@ -1902,9 +2181,15 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "fixedbitset" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" @@ -1913,6 +2198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", + "libz-sys", "miniz_oxide", ] @@ -1949,11 +2235,12 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ - "percent-encoding 2.2.0", + "matches", + "percent-encoding 2.1.0", ] [[package]] @@ -1962,6 +2249,25 @@ 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" @@ -1998,9 +2304,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -2013,9 +2319,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -2023,26 +2329,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" @@ -2055,38 +2362,55 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite", + "pin-project-lite 0.2.9", "waker-fn", ] [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "futures-rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" +dependencies = [ + "futures-io", + "rustls", + "webpki", +] + [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-timer" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -2095,7 +2419,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -2111,9 +2435,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check 0.9.4", @@ -2132,13 +2456,23 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug 0.3.0", + "polyval", ] [[package]] @@ -2154,9 +2488,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.2" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "git2" @@ -2170,7 +2504,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.3.1", + "url 2.2.2", ] [[package]] @@ -2179,6 +2513,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log 0.4.17", + "regex", +] + [[package]] name = "gloo-timers" version = "0.2.4" @@ -2194,7 +2541,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" dependencies = [ "anyhow", "ark-bls12-381", @@ -2204,12 +2551,12 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20", + "chacha20 0.8.1", "hex", - "itertools", + "itertools 0.10.3", "miracl_core", "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.6.3", "rayon", "subproductdomain", "thiserror", @@ -2237,11 +2584,11 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "fnv", "futures-core", "futures-sink", @@ -2250,8 +2597,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.4", - "tracing 0.1.37", + "tokio-util 0.7.3", + "tracing 0.1.35", ] [[package]] @@ -2265,37 +2612,41 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" dependencies = [ "ahash", ] [[package]] name = "hdrhistogram" -version = "7.5.2" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" dependencies = [ + "base64 0.13.0", "byteorder", + "crossbeam-channel", + "flate2", + "nom 7.1.1", "num-traits 0.2.15", ] [[package]] name = "headers" -version = "0.3.8" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.2.1", + "bytes 1.1.0", "headers-core", "http", "httpdate", "mime 0.3.16", - "sha1", + "sha-1 0.10.0", ] [[package]] @@ -2337,16 +2688,43 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac 0.7.0", + "digest 0.8.1", +] + [[package]] name = "hmac" version = "0.8.1" 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-drbg" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +dependencies = [ + "digest 0.8.1", + "generic-array 0.12.4", + "hmac 0.7.1", +] + [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2354,8 +2732,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", - "hmac", + "generic-array 0.14.5", + "hmac 0.8.1", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", ] [[package]] @@ -2364,7 +2753,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "fnv", "itoa", ] @@ -2375,16 +2764,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "http", - "pin-project-lite", + "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -2413,11 +2802,11 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "futures-channel", "futures-core", "futures-util", @@ -2427,11 +2816,11 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite", - "socket2", + "pin-project-lite 0.2.9", + "socket2 0.4.4", "tokio", "tower-service", - "tracing 0.1.37", + "tracing 0.1.35", "want", ] @@ -2441,11 +2830,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", + "bytes 1.1.0", + "futures 0.3.21", "headers", "http", - "hyper 0.14.20", + "hyper 0.14.19", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2462,7 +2851,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.20", + "hyper 0.14.19", "log 0.4.17", "rustls", "rustls-native-certs", @@ -2478,8 +2867,8 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.20", - "pin-project-lite", + "hyper 0.14.19", + "pin-project-lite 0.2.9", "tokio", "tokio-io-timeout", ] @@ -2490,8 +2879,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.2.1", - "hyper 0.14.20", + "bytes 1.1.0", + "hyper 0.14.19", "native-tls", "tokio", "tokio-native-tls", @@ -2499,53 +2888,42 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.51" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", - "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "winapi 0.3.9", ] -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] - [[package]] name = "ibc" version = "0.14.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "derive_more", "flex-error", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "safe-regex", - "serde 1.0.145", + "serde 1.0.137", "serde_derive", "serde_json", - "sha2 0.10.6", + "sha2 0.10.2", "subtle-encoding", "tendermint 0.23.5", "tendermint-light-client-verifier 0.23.5", "tendermint-proto 0.23.5", "tendermint-testgen 0.23.5", - "time 0.3.15", - "tracing 0.1.37", + "time 0.3.9", + "tracing 0.1.35", ] [[package]] @@ -2553,26 +2931,26 @@ name = "ibc" version = "0.14.0" source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "derive_more", "flex-error", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "safe-regex", - "serde 1.0.145", + "serde 1.0.137", "serde_derive", "serde_json", - "sha2 0.10.6", + "sha2 0.10.2", "subtle-encoding", "tendermint 0.23.6", "tendermint-light-client-verifier 0.23.6", "tendermint-proto 0.23.6", "tendermint-testgen 0.23.6", - "time 0.3.15", - "tracing 0.1.37", + "time 0.3.9", + "tracing 0.1.35", ] [[package]] @@ -2581,10 +2959,10 @@ version = "0.17.1" source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d#9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", - "prost", - "prost-types", - "serde 1.0.145", + "bytes 1.1.0", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.137", "tendermint-proto 0.23.5", ] @@ -2594,10 +2972,10 @@ version = "0.17.1" source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2#f4703dfe2c1f25cc431279ab74f10f3e0f6827e2" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", - "prost", - "prost-types", - "serde 1.0.145", + "bytes 1.1.0", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.137", "tendermint-proto 0.23.6", ] @@ -2608,9 +2986,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d454cc0a22bd556cc3d3c69f9d75a392a36244634840697a4b9eb81bc5c8ae0" dependencies = [ "anyhow", - "bytes 1.2.1", + "bytes 1.1.0", "hex", - "prost", + "prost 0.9.0", "ripemd160", "sha2 0.9.9", "sha3 0.9.1", @@ -2636,14 +3014,52 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ + "matches", "unicode-bidi", "unicode-normalization", ] +[[package]] +name = "if-addrs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2273e421f7c4f0fc99e1934fe4776f59d8df2972f4199d703fc0da9f2a9f73de" +dependencies = [ + "if-addrs-sys", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "if-addrs-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "if-watch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" +dependencies = [ + "async-io", + "futures 0.3.21", + "futures-lite", + "if-addrs", + "ipnet", + "libc", + "log 0.4.17", + "winapi 0.3.9", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -2668,7 +3084,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -2690,13 +3106,33 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.12.3", - "serde 1.0.145", + "hashbrown 0.11.2", + "serde 1.0.137", +] + +[[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]] @@ -2705,7 +3141,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", ] [[package]] @@ -2722,9 +3158,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" [[package]] name = "iovec" @@ -2735,6 +3171,18 @@ dependencies = [ "libc", ] +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2 0.3.19", + "widestring", + "winapi 0.3.9", + "winreg 0.6.2", +] + [[package]] name = "ipnet" version = "2.5.0" @@ -2743,33 +3191,42 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" -version = "0.10.5" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" dependencies = [ "either", ] [[package]] -name = "itoa" -version = "1.0.4" +name = "itertools" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" dependencies = [ "wasm-bindgen", ] @@ -2811,6 +3268,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" +[[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" @@ -2844,9 +3307,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.135" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -2872,11 +3335,422 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "libp2p" +version = "0.38.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "atomic", + "bytes 1.1.0", + "futures 0.3.21", + "lazy_static 1.4.0", + "libp2p-core", + "libp2p-deflate", + "libp2p-dns", + "libp2p-floodsub", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-kad", + "libp2p-mdns", + "libp2p-mplex", + "libp2p-noise", + "libp2p-ping", + "libp2p-plaintext", + "libp2p-pnet", + "libp2p-relay", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-swarm-derive", + "libp2p-tcp", + "libp2p-uds", + "libp2p-wasm-ext", + "libp2p-websocket", + "libp2p-yamux", + "parity-multiaddr", + "parking_lot 0.11.2", + "pin-project 1.0.10", + "smallvec 1.8.0", + "wasm-timer", +] + +[[package]] +name = "libp2p-core" +version = "0.28.3" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "either", + "fnv", + "futures 0.3.21", + "futures-timer", + "lazy_static 1.4.0", + "libsecp256k1 0.3.5", + "log 0.4.17", + "multihash", + "multistream-select", + "parity-multiaddr", + "parking_lot 0.11.2", + "pin-project 1.0.10", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.7.3", + "ring", + "rw-stream-sink", + "sha2 0.9.9", + "smallvec 1.8.0", + "thiserror", + "unsigned-varint 0.7.1", + "void", + "zeroize", +] + +[[package]] +name = "libp2p-deflate" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "flate2", + "futures 0.3.21", + "libp2p-core", +] + +[[package]] +name = "libp2p-dns" +version = "0.28.1" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "async-std-resolver", + "futures 0.3.21", + "libp2p-core", + "log 0.4.17", + "smallvec 1.8.0", + "trust-dns-resolver", +] + +[[package]] +name = "libp2p-floodsub" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "cuckoofilter", + "fnv", + "futures 0.3.21", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.7.3", + "smallvec 1.8.0", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "asynchronous-codec", + "base64 0.13.0", + "byteorder", + "bytes 1.1.0", + "fnv", + "futures 0.3.21", + "hex_fmt", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.7.3", + "regex", + "sha2 0.9.9", + "smallvec 1.8.0", + "unsigned-varint 0.7.1", + "wasm-timer", +] + +[[package]] +name = "libp2p-identify" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "futures 0.3.21", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "smallvec 1.8.0", + "wasm-timer", +] + +[[package]] +name = "libp2p-kad" +version = "0.30.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "arrayvec 0.5.2", + "asynchronous-codec", + "bytes 1.1.0", + "either", + "fnv", + "futures 0.3.21", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.7.3", + "sha2 0.9.9", + "smallvec 1.8.0", + "uint", + "unsigned-varint 0.7.1", + "void", + "wasm-timer", +] + +[[package]] +name = "libp2p-mdns" +version = "0.30.2" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "async-io", + "data-encoding", + "dns-parser", + "futures 0.3.21", + "if-watch", + "lazy_static 1.4.0", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "rand 0.8.5", + "smallvec 1.8.0", + "socket2 0.4.4", + "void", +] + +[[package]] +name = "libp2p-mplex" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "asynchronous-codec", + "bytes 1.1.0", + "futures 0.3.21", + "libp2p-core", + "log 0.4.17", + "nohash-hasher", + "parking_lot 0.11.2", + "rand 0.7.3", + "smallvec 1.8.0", + "unsigned-varint 0.7.1", +] + +[[package]] +name = "libp2p-noise" +version = "0.31.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "bytes 1.1.0", + "curve25519-dalek", + "futures 0.3.21", + "lazy_static 1.4.0", + "libp2p-core", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.8.5", + "sha2 0.9.9", + "snow", + "static_assertions", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "futures 0.3.21", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "rand 0.7.3", + "void", + "wasm-timer", +] + +[[package]] +name = "libp2p-plaintext" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "asynchronous-codec", + "bytes 1.1.0", + "futures 0.3.21", + "libp2p-core", + "log 0.4.17", + "prost 0.7.0", + "prost-build 0.7.0", + "unsigned-varint 0.7.1", + "void", +] + +[[package]] +name = "libp2p-pnet" +version = "0.21.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "futures 0.3.21", + "log 0.4.17", + "pin-project 1.0.10", + "rand 0.7.3", + "salsa20", + "sha3 0.9.1", +] + +[[package]] +name = "libp2p-relay" +version = "0.2.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "asynchronous-codec", + "bytes 1.1.0", + "futures 0.3.21", + "futures-timer", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "pin-project 1.0.10", + "prost 0.7.0", + "prost-build 0.7.0", + "rand 0.7.3", + "smallvec 1.8.0", + "unsigned-varint 0.7.1", + "void", + "wasm-timer", +] + +[[package]] +name = "libp2p-request-response" +version = "0.11.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "async-trait", + "bytes 1.1.0", + "futures 0.3.21", + "libp2p-core", + "libp2p-swarm", + "log 0.4.17", + "lru", + "minicbor", + "rand 0.7.3", + "smallvec 1.8.0", + "unsigned-varint 0.7.1", + "wasm-timer", +] + +[[package]] +name = "libp2p-swarm" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "either", + "futures 0.3.21", + "libp2p-core", + "log 0.4.17", + "rand 0.7.3", + "smallvec 1.8.0", + "void", + "wasm-timer", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.23.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "async-io", + "futures 0.3.21", + "futures-timer", + "if-watch", + "ipnet", + "libc", + "libp2p-core", + "log 0.4.17", + "socket2 0.4.4", +] + +[[package]] +name = "libp2p-uds" +version = "0.28.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "async-std", + "futures 0.3.21", + "libp2p-core", + "log 0.4.17", +] + +[[package]] +name = "libp2p-wasm-ext" +version = "0.28.2" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "futures 0.3.21", + "js-sys", + "libp2p-core", + "parity-send-wrapper", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "libp2p-websocket" +version = "0.29.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "either", + "futures 0.3.21", + "futures-rustls", + "libp2p-core", + "log 0.4.17", + "quicksink", + "rw-stream-sink", + "soketto", + "url 2.2.2", + "webpki-roots", +] + +[[package]] +name = "libp2p-yamux" +version = "0.32.0" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "futures 0.3.21", + "libp2p-core", + "parking_lot 0.11.2", + "thiserror", + "yamux", +] + [[package]] name = "librocksdb-sys" -version = "0.8.0+7.4.4" +version = "0.6.1+6.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" +checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" dependencies = [ "bindgen", "bzip2-sys", @@ -2884,10 +3758,25 @@ dependencies = [ "glob", "libc", "libz-sys", - "tikv-jemalloc-sys", "zstd-sys", ] +[[package]] +name = "libsecp256k1" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +dependencies = [ + "arrayref", + "crunchy", + "digest 0.8.1", + "hmac-drbg 0.2.0", + "rand 0.7.3", + "sha2 0.8.2", + "subtle 2.4.1", + "typenum", +] + [[package]] name = "libsecp256k1" version = "0.7.0" @@ -2896,12 +3785,12 @@ dependencies = [ "arrayref", "base64 0.13.0", "digest 0.9.0", - "hmac-drbg", + "hmac-drbg 0.3.0", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.145", + "serde 1.0.137", "sha2 0.9.9", "typenum", ] @@ -2913,7 +3802,7 @@ source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d dependencies = [ "crunchy", "digest 0.9.0", - "subtle", + "subtle 2.4.1", ] [[package]] @@ -2958,22 +3847,14 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" -version = "0.5.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", + "serde_test", ] [[package]] @@ -3005,9 +3886,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3053,6 +3934,24 @@ dependencies = [ "syn", ] +[[package]] +name = "lru" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" +dependencies = [ + "hashbrown 0.11.2", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "mach" version = "0.3.2" @@ -3071,11 +3970,17 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.145", + "serde 1.0.137", "serde_derive", "serde_yaml", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -3115,9 +4020,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" dependencies = [ "libc", ] @@ -3150,15 +4055,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", "integer-encoding", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "mio 0.7.14", - "serde 1.0.145", + "serde 1.0.137", "strum", "tungstenite 0.16.0", - "url 2.3.1", + "url 2.2.2", ] [[package]] @@ -3186,6 +4091,26 @@ dependencies = [ "unicase 2.6.0", ] +[[package]] +name = "minicbor" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aa5bb0ca22415daca596a227b507f880ad1b2318a87fa9325312a5d285ca0d" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54999f917cd092b13904737e26631aa2b2b88d625db68e4bab461dcd8006c788" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3194,9 +4119,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] @@ -3235,14 +4160,26 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log 0.4.17", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.36.1", + "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]] @@ -3287,6 +4224,33 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multihash" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.5", + "multihash-derive", + "sha2 0.9.9", + "unsigned-varint 0.5.1", +] + +[[package]] +name = "multihash-derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "multimap" version = "0.8.3" @@ -3311,9 +4275,22 @@ dependencies = [ "twoway", ] +[[package]] +name = "multistream-select" +version = "0.10.3" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "bytes 1.1.0", + "futures 0.3.21", + "log 0.4.17", + "pin-project 1.0.10", + "smallvec 1.8.0", + "unsigned-varint 0.7.1", +] + [[package]] name = "namada" -version = "0.8.1" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3338,22 +4315,21 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", - "itertools", - "libsecp256k1", + "itertools 0.10.3", + "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "pwasm-utils", "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.6.3", "rust_decimal", - "secp256k1 0.21.3", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -3366,8 +4342,8 @@ dependencies = [ "thiserror", "tiny-keccak", "tonic-build", - "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing 0.1.35", + "tracing-subscriber 0.3.11", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -3380,7 +4356,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.8.1" +version = "0.7.1" dependencies = [ "ark-serialize", "ark-std", @@ -3389,20 +4365,20 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", - "bimap", "bit-set", "blake2b-rs", "borsh", "byte-unit", "byteorder", - "bytes 1.2.1", + "bytes 1.1.0", + "cargo-watch", "circular-queue", - "clap", + "clap 3.0.0-beta.2", "clarity", "color-eyre", "config", - "data-encoding", "derivative", + "directories", "ed25519-consensus", "ethabi", "eyre", @@ -3410,11 +4386,13 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.25", + "futures 0.3.21", "git2", - "itertools", + "hex", + "itertools 0.10.3", "libc", "libloading", + "libp2p", "message-io", "namada", "num-derive", @@ -3423,11 +4401,12 @@ dependencies = [ "num_cpus", "once_cell", "orion", + "pathdiff", "proptest", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "rand 0.8.5", - "rand_core 0.6.4", + "rand_core 0.6.3", "rayon", "regex", "reqwest", @@ -3435,7 +4414,7 @@ dependencies = [ "rocksdb", "rpassword", "semver 1.0.14", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", "serde_json", "serde_regex", @@ -3459,12 +4438,13 @@ dependencies = [ "tokio-test", "toml", "tonic", + "tonic-build", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", - "tracing 0.1.37", + "tracing 0.1.35", "tracing-log", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.11", "warp", "web30", "websocket", @@ -3473,18 +4453,18 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.8.1" +version = "0.7.1" dependencies = [ "borsh", - "itertools", - "lazy_static", + "itertools 0.10.3", + "lazy_static 1.4.0", "madato", "namada", ] [[package]] name = "namada_macros" -version = "0.8.1" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -3492,78 +4472,71 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.8.1" +version = "0.7.1" dependencies = [ "borsh", - "derivative", "proptest", "thiserror", ] [[package]] name = "namada_tests" -version = "0.8.1" +version = "0.7.1" dependencies = [ "assert_cmd", "borsh", "chrono", "color-eyre", "concat-idents", - "data-encoding", "derivative", "escargot", "expectrl", "eyre", "file-serve", "fs_extra", - "itertools", + "hex", + "itertools 0.10.3", + "libp2p", "namada", "namada_apps", - "namada_tx_prelude", - "namada_vp_prelude", + "namada_vm_env", "pretty_assertions", "proptest", - "prost", + "prost 0.9.0", "rand 0.8.5", "serde_json", "sha2 0.9.9", "tempfile", "test-log", "toml", - "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing 0.1.35", + "tracing-subscriber 0.3.11", ] [[package]] name = "namada_tx_prelude" -version = "0.8.1" +version = "0.7.1" dependencies = [ - "borsh", - "namada", - "namada_macros", "namada_vm_env", - "sha2 0.10.6", - "thiserror", + "sha2 0.10.2", ] [[package]] name = "namada_vm_env" -version = "0.8.1" +version = "0.7.1" dependencies = [ "borsh", + "hex", "namada", + "namada_macros", ] [[package]] name = "namada_vp_prelude" -version = "0.8.1" +version = "0.7.1" dependencies = [ - "borsh", - "namada", - "namada_macros", "namada_vm_env", - "sha2 0.10.6", - "thiserror", + "sha2 0.10.2", ] [[package]] @@ -3572,7 +4545,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "libc", "log 0.4.17", "openssl", @@ -3586,15 +4559,28 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.38" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" dependencies = [ "cfg-if 0.1.10", "libc", "winapi 0.3.9", ] +[[package]] +name = "nix" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nix" version = "0.21.2" @@ -3623,15 +4609,21 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" dependencies = [ "bitflags", "cfg-if 1.0.0", "libc", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "5.1.2" @@ -3654,21 +4646,29 @@ dependencies = [ ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "notify" +version = "4.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", "winapi 0.3.9", ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "ntapi" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ - "overload", "winapi 0.3.9", ] @@ -3693,7 +4693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.2", + "num-complex 0.4.1", "num-integer", "num-iter", "num-rational 0.4.1", @@ -3720,7 +4720,7 @@ dependencies = [ "autocfg 1.1.0", "num-integer", "num-traits 0.2.15", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -3735,9 +4735,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" dependencies = [ "num-traits 0.2.15", ] @@ -3822,11 +4822,11 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.145", + "serde 1.0.137", "serde_derive", ] @@ -3849,6 +4849,12 @@ 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" @@ -3861,20 +4867,11 @@ 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.15.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "opaque-debug" @@ -3890,9 +4887,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -3922,9 +4919,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.76" +version = "0.9.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" +checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" dependencies = [ "autocfg 1.1.0", "cc", @@ -3940,8 +4937,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.7", - "subtle", + "getrandom 0.2.6", + "subtle 2.4.1", "zeroize", ] @@ -3960,30 +4957,41 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +[[package]] +name = "parity-multiaddr" +version = "0.11.2" +source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" +dependencies = [ + "arrayref", + "bs58", + "byteorder", + "data-encoding", + "multihash", + "percent-encoding 2.1.0", + "serde 1.0.137", + "static_assertions", + "unsigned-varint 0.7.1", + "url 2.2.2", +] + [[package]] name = "parity-scale-codec" -version = "3.2.1" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" dependencies = [ "arrayvec 0.7.2", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -3992,12 +5000,18 @@ version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ - "proc-macro-crate 1.2.1", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", ] +[[package]] +name = "parity-send-wrapper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" + [[package]] name = "parity-wasm" version = "0.42.2" @@ -4021,14 +5035,25 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api 0.4.7", + "parking_lot_core 0.8.5", +] + [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.9", - "parking_lot_core 0.9.4", + "lock_api 0.4.7", + "parking_lot_core 0.9.3", ] [[package]] @@ -4048,22 +5073,42 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.13", + "smallvec 1.8.0", + "winapi 0.3.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", - "smallvec 1.10.0", - "windows-sys 0.42.0", + "redox_syscall 0.2.13", + "smallvec 1.8.0", + "windows-sys", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pathdiff" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "peeking_take_while" @@ -4106,9 +5151,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" @@ -4119,36 +5164,72 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset 0.2.0", + "indexmap", +] + [[package]] name = "petgraph" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset", + "fixedbitset 0.4.1", "indexmap", ] [[package]] name = "pin-project" -version = "1.0.12" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +dependencies = [ + "pin-project-internal 0.4.29", +] + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal 1.0.10", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" dependencies = [ - "pin-project-internal", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -4169,18 +5250,40 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.3.0" -source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", - "rustversion", "wepoll-ffi", "winapi 0.3.9", ] +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures 0.2.2", + "opaque-debug 0.3.0", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures 0.2.2", + "opaque-debug 0.3.0", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -4194,7 +5297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools", + "itertools 0.10.3", "predicates-core", ] @@ -4250,11 +5353,10 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", "thiserror", "toml", ] @@ -4285,9 +5387,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ "unicode-ident", ] @@ -4300,7 +5402,7 @@ dependencies = [ "bit-set", "bitflags", "byteorder", - "lazy_static", + "lazy_static 1.4.0", "num-traits 0.2.15", "quick-error 2.0.1", "rand 0.8.5", @@ -4311,14 +5413,42 @@ dependencies = [ "tempfile", ] +[[package]] +name = "prost" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" +dependencies = [ + "bytes 1.1.0", + "prost-derive 0.7.0", +] + [[package]] name = "prost" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.2.1", - "prost-derive", + "bytes 1.1.0", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" +dependencies = [ + "bytes 1.1.0", + "heck 0.3.3", + "itertools 0.9.0", + "log 0.4.17", + "multimap", + "petgraph 0.5.1", + "prost 0.7.0", + "prost-types 0.7.0", + "tempfile", + "which", ] [[package]] @@ -4327,20 +5457,33 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "heck 0.3.3", - "itertools", - "lazy_static", + "itertools 0.10.3", + "lazy_static 1.4.0", "log 0.4.17", "multimap", - "petgraph", - "prost", - "prost-types", + "petgraph 0.6.2", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which", ] +[[package]] +name = "prost-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" +dependencies = [ + "anyhow", + "itertools 0.9.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-derive" version = "0.9.0" @@ -4348,20 +5491,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.3", "proc-macro2", "quote", "syn", ] +[[package]] +name = "prost-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" +dependencies = [ + "bytes 1.1.0", + "prost 0.7.0", +] + [[package]] name = "prost-types" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.2.1", - "prost", + "bytes 1.1.0", + "prost 0.9.0", ] [[package]] @@ -4416,11 +5569,22 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quicksink" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite 0.1.12", +] + [[package]] name = "quote" -version = "1.0.21" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -4471,7 +5635,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand_core 0.6.3", ] [[package]] @@ -4501,7 +5665,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core 0.6.3", ] [[package]] @@ -4530,11 +5694,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", ] [[package]] @@ -4614,7 +5778,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.6.3", ] [[package]] @@ -4637,7 +5801,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.8", "num_cpus", ] @@ -4658,13 +5822,33 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 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", + "thiserror", +] + [[package]] name = "regalloc" version = "0.0.31" @@ -4673,14 +5857,14 @@ checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log 0.4.17", "rustc-hash", - "smallvec 1.10.0", + "smallvec 1.8.0", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -4698,9 +5882,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "region" @@ -4734,39 +5918,48 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64 0.13.0", - "bytes 1.2.1", + "bytes 1.1.0", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.20", + "hyper 0.14.19", "hyper-tls", "ipnet", "js-sys", + "lazy_static 1.4.0", "log 0.4.17", "mime 0.3.16", "native-tls", - "once_cell", - "percent-encoding 2.2.0", - "pin-project-lite", - "serde 1.0.145", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.9", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tower-service", - "url 2.3.1", + "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg", + "winreg 0.10.1", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", ] [[package]] @@ -4797,12 +5990,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" dependencies = [ "bytecheck", - "hashbrown 0.12.3", + "hashbrown 0.12.1", "ptr_meta", "rend", "rkyv_derive", @@ -4811,9 +6004,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" dependencies = [ "proc-macro2", "quote", @@ -4831,19 +6024,19 @@ dependencies = [ [[package]] name = "rlp" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "rustc-hex", ] [[package]] name = "rocksdb" -version = "0.19.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" dependencies = [ "libc", "librocksdb-sys", @@ -4867,13 +6060,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.26.1" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" +checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -4946,20 +6139,11 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64 0.13.0", -] - [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rusty-fork" @@ -4973,11 +6157,22 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rw-stream-sink" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" +dependencies = [ + "futures 0.3.21", + "pin-project 0.4.29", + "static_assertions", +] + [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "safe-proc-macro2" @@ -5032,6 +6227,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "salsa20" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -5047,8 +6251,8 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "lazy_static 1.4.0", + "windows-sys", ] [[package]] @@ -5063,12 +6267,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - [[package]] name = "sct" version = "0.6.1" @@ -5091,16 +6289,7 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" dependencies = [ - "secp256k1-sys 0.4.2", -] - -[[package]] -name = "secp256k1" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" -dependencies = [ - "secp256k1-sys 0.6.1", + "secp256k1-sys", ] [[package]] @@ -5112,15 +6301,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.3.1" @@ -5191,9 +6371,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.145" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -5204,7 +6384,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "num-traits 0.1.43", "regex", "serde 0.8.23", @@ -5219,23 +6399,23 @@ dependencies = [ "byteorder", "error", "num 0.2.1", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", ] [[package]] name = "serde_derive" -version = "1.0.145" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -5244,13 +6424,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" dependencies = [ "itoa", "ryu", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -5260,20 +6440,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" 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" @@ -5283,7 +6472,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -5294,7 +6483,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.145", + "serde 1.0.137", "yaml-rust", ] @@ -5318,7 +6507,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", ] @@ -5330,19 +6519,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.5", + "cpufeatures 0.2.2", + "digest 0.10.3", ] [[package]] -name = "sha1" -version = "0.10.5" +name = "sha2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.5", + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", ] [[package]] @@ -5353,20 +6543,20 @@ 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", ] [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.5", + "cpufeatures 0.2.2", + "digest 0.10.3", ] [[package]] @@ -5383,11 +6573,11 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" dependencies = [ - "digest 0.10.5", + "digest 0.10.3", "keccak", ] @@ -5397,9 +6587,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", ] +[[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" @@ -5427,9 +6623,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" [[package]] name = "simple-error" @@ -5439,12 +6635,9 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg 1.1.0", -] +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" @@ -5457,18 +6650,63 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "snow" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" +dependencies = [ + "aes-gcm", + "blake2 0.9.2", + "chacha20poly1305", + "rand 0.8.5", + "rand_core 0.6.3", + "ring", + "rustc_version 0.3.3", + "sha2 0.9.9", + "subtle 2.4.1", + "x25519-dalek", +] + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "socket2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi 0.3.9", +] [[package]] -name = "socket2" -version = "0.4.7" +name = "soketto" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" dependencies = [ - "libc", - "winapi 0.3.9", + "base64 0.12.3", + "bytes 0.5.6", + "flate2", + "futures 0.3.21", + "httparse", + "log 0.4.17", + "rand 0.7.3", + "sha-1 0.9.8", ] [[package]] @@ -5480,7 +6718,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#04ad1eeb28901b57a7599bbe433b3822965dabe8" dependencies = [ "blake2b-rs", "borsh", @@ -5507,6 +6745,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stderrlog" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" +dependencies = [ + "atty", + "chrono", + "log 0.4.17", + "termcolor", + "thread_local 0.3.4", +] + +[[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" @@ -5515,18 +6772,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -5538,7 +6795,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" dependencies = [ "anyhow", "ark-ec", @@ -5548,6 +6805,12 @@ dependencies = [ "ark-std", ] +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "subtle" version = "2.4.1" @@ -5571,9 +6834,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.102" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" dependencies = [ "proc-macro2", "quote", @@ -5638,7 +6901,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.2.13", "remove_dir_all", "winapi 0.3.9", ] @@ -5649,25 +6912,25 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.2.1", + "bytes 1.1.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.25", + "futures 0.3.21", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", - "serde 1.0.145", + "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", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.5", - "time 0.3.15", + "time 0.3.9", "zeroize", ] @@ -5677,25 +6940,25 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "async-trait", - "bytes 1.2.1", + "bytes 1.1.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.25", + "futures 0.3.21", "num-traits 0.2.15", "once_cell", - "prost", - "prost-types", - "serde 1.0.145", + "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", + "subtle 2.4.1", "subtle-encoding", "tendermint-proto 0.23.6", - "time 0.3.15", + "time 0.3.9", "zeroize", ] @@ -5705,11 +6968,11 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "tendermint 0.23.5", "toml", - "url 2.3.1", + "url 2.2.2", ] [[package]] @@ -5718,11 +6981,11 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ "flex-error", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "tendermint 0.23.6", "toml", - "url 2.3.1", + "url 2.2.2", ] [[package]] @@ -5732,10 +6995,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.145", + "serde 1.0.137", "tendermint 0.23.5", "tendermint-rpc 0.23.5", - "time 0.3.15", + "time 0.3.9", ] [[package]] @@ -5745,9 +7008,9 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc285 dependencies = [ "derive_more", "flex-error", - "serde 1.0.145", + "serde 1.0.137", "tendermint 0.23.6", - "time 0.3.15", + "time 0.3.9", ] [[package]] @@ -5755,16 +7018,16 @@ name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.145", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.15", + "time 0.3.9", ] [[package]] @@ -5772,16 +7035,16 @@ name = "tendermint-proto" version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc2850830f4d8028c1fe1bd9f96284#87be41b8c9cc2850830f4d8028c1fe1bd9f96284" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "flex-error", "num-derive", "num-traits 0.2.15", - "prost", - "prost-types", - "serde 1.0.145", + "prost 0.9.0", + "prost-types 0.9.0", + "serde 1.0.137", "serde_bytes", "subtle-encoding", - "time 0.3.15", + "time 0.3.9", ] [[package]] @@ -5791,17 +7054,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.2.1", + "bytes 1.1.0", "flex-error", - "futures 0.3.25", - "getrandom 0.2.7", + "futures 0.3.21", + "getrandom 0.2.6", "http", - "hyper 0.14.20", + "hyper 0.14.19", "hyper-proxy", "hyper-rustls", "peg", - "pin-project", - "serde 1.0.145", + "pin-project 1.0.10", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -5809,10 +7072,10 @@ dependencies = [ "tendermint-config 0.23.5", "tendermint-proto 0.23.5", "thiserror", - "time 0.3.15", + "time 0.3.9", "tokio", - "tracing 0.1.37", - "url 2.3.1", + "tracing 0.1.35", + "url 2.2.2", "uuid", "walkdir", ] @@ -5824,17 +7087,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc285 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.2.1", + "bytes 1.1.0", "flex-error", - "futures 0.3.25", - "getrandom 0.2.7", + "futures 0.3.21", + "getrandom 0.2.6", "http", - "hyper 0.14.20", + "hyper 0.14.19", "hyper-proxy", "hyper-rustls", "peg", - "pin-project", - "serde 1.0.145", + "pin-project 1.0.10", + "serde 1.0.137", "serde_bytes", "serde_json", "subtle-encoding", @@ -5842,10 +7105,10 @@ dependencies = [ "tendermint-config 0.23.6", "tendermint-proto 0.23.6", "thiserror", - "time 0.3.15", + "time 0.3.9", "tokio", - "tracing 0.1.37", - "url 2.3.1", + "tracing 0.1.35", + "url 2.2.2", "uuid", "walkdir", ] @@ -5857,12 +7120,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "simple-error", "tempfile", "tendermint 0.23.5", - "time 0.3.15", + "time 0.3.9", ] [[package]] @@ -5872,12 +7135,22 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=87be41b8c9cc285 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "simple-error", "tempfile", "tendermint 0.23.6", - "time 0.3.15", + "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", ] [[package]] @@ -5889,6 +7162,18 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall 0.2.13", + "redox_termios", +] + [[package]] name = "termtree" version = "0.2.4" @@ -5897,15 +7182,25 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" -version = "0.2.11" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" +checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "term_size", + "unicode-width", +] + [[package]] name = "textwrap" version = "0.12.1" @@ -5937,22 +7232,21 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" dependencies = [ - "once_cell", + "lazy_static 0.2.11", + "unreachable", ] [[package]] -name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +name = "thread_local" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "cc", - "fs_extra", - "libc", + "once_cell", ] [[package]] @@ -5968,9 +7262,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.15" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "itoa", "libc", @@ -6002,8 +7296,8 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.15", - "url 2.3.1", + "time 0.3.9", + "url 2.2.2", ] [[package]] @@ -6023,20 +7317,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" dependencies = [ - "autocfg 1.1.0", - "bytes 1.2.1", + "bytes 1.1.0", "libc", "memchr", - "mio 0.8.4", + "mio 0.8.3", "num_cpus", + "once_cell", "parking_lot 0.12.1", - "pin-project-lite", + "pin-project-lite 0.2.9", "signal-hook-registry", - "socket2", + "socket2 0.4.4", "tokio-macros", "winapi 0.3.9", ] @@ -6079,7 +7373,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", ] @@ -6124,7 +7418,7 @@ checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", "mio 0.6.23", "num_cpus", @@ -6148,12 +7442,12 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", ] @@ -6188,7 +7482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.2.1", + "bytes 1.1.0", "futures-core", "tokio", "tokio-stream", @@ -6207,14 +7501,15 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" dependencies = [ "futures-util", "log 0.4.17", + "pin-project 1.0.10", "tokio", - "tungstenite 0.17.3", + "tungstenite 0.14.0", ] [[package]] @@ -6223,26 +7518,26 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "futures-core", "futures-sink", "log 0.4.17", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "futures-core", "futures-sink", - "pin-project-lite", + "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.37", + "tracing 0.1.35", ] [[package]] @@ -6251,7 +7546,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.145", + "serde 1.0.137", ] [[package]] @@ -6263,25 +7558,25 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.0", - "bytes 1.2.1", + "bytes 1.1.0", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.20", + "hyper 0.14.19", "hyper-timeout", - "percent-encoding 2.2.0", - "pin-project", - "prost", - "prost-derive", + "percent-encoding 2.1.0", + "pin-project 1.0.10", + "prost 0.9.0", + "prost-derive 0.9.0", "tokio", "tokio-stream", "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", - "tracing 0.1.37", + "tracing 0.1.35", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -6292,30 +7587,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" dependencies = [ "proc-macro2", - "prost-build", + "prost-build 0.9.0", "quote", "syn", ] [[package]] name = "tower" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", - "pin-project", - "pin-project-lite", + "pin-project 1.0.10", + "pin-project-lite 0.2.9", "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.3", "tower-layer", "tower-service", - "tracing 0.1.37", + "tracing 0.1.35", ] [[package]] @@ -6323,10 +7618,10 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", - "pin-project", - "prost", + "bytes 1.1.0", + "futures 0.3.21", + "pin-project 1.0.10", + "prost 0.9.0", "tendermint-proto 0.23.5", "tokio", "tokio-stream", @@ -6341,10 +7636,10 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082#fcc0014d0bda707109901abfa1b2f782d242f082" dependencies = [ - "bytes 1.2.1", - "futures 0.3.25", - "pin-project", - "prost", + "bytes 1.1.0", + "futures 0.3.21", + "pin-project 1.0.10", + "prost 0.9.0", "tendermint-proto 0.23.6", "tokio", "tokio-stream", @@ -6356,9 +7651,9 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-make" @@ -6371,9 +7666,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" @@ -6381,22 +7676,22 @@ version = "0.1.30" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite", + "pin-project-lite 0.2.9", "tracing-attributes 0.1.19", "tracing-core 0.1.22", ] [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", - "pin-project-lite", - "tracing-attributes 0.1.23", - "tracing-core 0.1.30", + "pin-project-lite 0.2.9", + "tracing-attributes 0.1.21", + "tracing-core 0.1.27", ] [[package]] @@ -6411,9 +7706,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" dependencies = [ "proc-macro2", "quote", @@ -6425,14 +7720,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", + "lazy_static 1.4.0", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" dependencies = [ "once_cell", "valuable", @@ -6444,7 +7739,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.37", + "tracing 0.1.35", "tracing-subscriber 0.2.25", ] @@ -6454,8 +7749,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project", - "tracing 0.1.37", + "pin-project 1.0.10", + "tracing 0.1.35", ] [[package]] @@ -6463,7 +7758,7 @@ name = "tracing-futures" version = "0.2.5" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "pin-project-lite", + "pin-project-lite 0.2.9", "tracing 0.1.30", ] @@ -6473,9 +7768,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "lazy_static", + "lazy_static 1.4.0", "log 0.4.17", - "tracing-core 0.1.30", + "tracing-core 0.1.27", ] [[package]] @@ -6485,25 +7780,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local", - "tracing-core 0.1.30", + "thread_local 1.1.4", + "tracing-core 0.1.27", ] [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ + "ansi_term", + "lazy_static 1.4.0", "matchers", - "nu-ansi-term", - "once_cell", "regex", "sharded-slab", - "smallvec 1.10.0", - "thread_local", - "tracing 0.1.37", - "tracing-core 0.1.30", + "smallvec 1.8.0", + "thread_local 1.1.4", + "tracing 0.1.35", + "tracing-core 0.1.27", "tracing-log", ] @@ -6512,8 +7807,8 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.25", - "pin-project-lite", + "futures 0.3.21", + "pin-project-lite 0.2.9", "tower-layer", "tower-make", "tower-service", @@ -6527,6 +7822,49 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" +[[package]] +name = "trust-dns-proto" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static 1.4.0", + "log 0.4.17", + "rand 0.8.5", + "smallvec 1.8.0", + "thiserror", + "tinyvec", + "url 2.2.2", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" +dependencies = [ + "cfg-if 1.0.0", + "futures-util", + "ipconfig", + "lazy_static 1.4.0", + "log 0.4.17", + "lru-cache", + "parking_lot 0.11.2", + "resolv-conf", + "smallvec 1.8.0", + "thiserror", + "trust-dns-proto", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -6541,52 +7879,52 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.2.1", + "bytes 1.1.0", "http", "httparse", "input_buffer", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.3.1", + "url 2.2.2", "utf-8", ] [[package]] name = "tungstenite" -version = "0.16.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.2.1", + "bytes 1.1.0", "http", "httparse", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.3.1", + "url 2.2.2", "utf-8", ] [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.2.1", + "bytes 1.1.0", "http", "httparse", "log 0.4.17", "rand 0.8.5", - "sha-1 0.10.0", + "sha-1 0.9.8", "thiserror", - "url 2.3.1", + "url 2.2.2", "utf-8", ] @@ -6613,15 +7951,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -6655,36 +7993,73 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array 0.14.5", + "subtle 2.4.1", +] + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "unsigned-varint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" + +[[package]] +name = "unsigned-varint" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" +dependencies = [ + "asynchronous-codec", + "bytes 1.1.0", + "futures-io", + "futures-util", +] [[package]] name = "untrusted" @@ -6705,13 +8080,14 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", - "idna 0.3.0", - "percent-encoding 2.2.0", + "idna 0.2.3", + "matches", + "percent-encoding 2.1.0", ] [[package]] @@ -6732,7 +8108,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.6", ] [[package]] @@ -6775,6 +8151,32 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +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" @@ -6813,33 +8215,32 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" dependencies = [ - "bytes 1.2.1", + "bytes 1.1.0", "futures-channel", "futures-util", "headers", "http", - "hyper 0.14.20", + "hyper 0.14.19", "log 0.4.17", "mime 0.3.16", "mime_guess", "multipart", - "percent-encoding 2.2.0", - "pin-project", - "rustls-pemfile", + "percent-encoding 2.1.0", + "pin-project 1.0.10", "scoped-tls", - "serde 1.0.145", + "serde 1.0.137", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.7.4", + "tokio-util 0.6.10", "tower-service", - "tracing 0.1.37", + "tracing 0.1.35", ] [[package]] @@ -6862,9 +8263,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -6872,13 +8273,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", + "lazy_static 1.4.0", "log 0.4.17", - "once_cell", "proc-macro2", "quote", "syn", @@ -6887,9 +8288,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -6899,9 +8300,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6909,9 +8310,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" dependencies = [ "proc-macro2", "quote", @@ -6922,19 +8323,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" [[package]] name = "wasm-encoder" -version = "0.18.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64ac98d5d61192cc45c701b7e4bd0b9aff91e2edfc7a088406cfe2288581e2c" +checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" dependencies = [ "leb128", ] +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures 0.3.21", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmer" version = "2.2.0" @@ -6982,9 +8398,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", - "smallvec 1.10.0", + "smallvec 1.8.0", "target-lexicon", "thiserror", "wasmer-types", @@ -7005,9 +8421,9 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.10.0", + "smallvec 1.8.0", "target-lexicon", - "tracing 0.1.37", + "tracing 0.1.35", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7022,11 +8438,11 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static", + "lazy_static 1.4.0", "loupe", "more-asserts", "rayon", - "smallvec 1.10.0", + "smallvec 1.8.0", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7052,12 +8468,12 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static", + "lazy_static 1.4.0", "loupe", "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.145", + "serde 1.0.137", "serde_bytes", "target-lexicon", "thiserror", @@ -7078,11 +8494,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.4", + "object", "rkyv", - "serde 1.0.145", + "serde 1.0.137", "tempfile", - "tracing 0.1.37", + "tracing 0.1.35", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7117,7 +8533,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.4", + "object", "thiserror", "wasmer-compiler", "wasmer-types", @@ -7132,7 +8548,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.145", + "serde 1.0.137", "thiserror", ] @@ -7153,7 +8569,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.145", + "serde 1.0.137", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -7173,9 +8589,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "47.0.1" +version = "42.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b98502f3978adea49551e801a6687678e6015317d7d9470a67fe813393f2a8" +checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" dependencies = [ "leb128", "memchr", @@ -7185,18 +8601,38 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.49" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" +checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" dependencies = [ "wast", ] +[[package]] +name = "watchexec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" +dependencies = [ + "clap 2.34.0", + "derive_builder", + "embed-resource", + "env_logger", + "glob", + "globset", + "lazy_static 1.4.0", + "log 0.4.17", + "nix 0.20.2", + "notify", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" dependencies = [ "js-sys", "wasm-bindgen", @@ -7210,12 +8646,12 @@ checksum = "41c0c0c928020760cc69884944d54e7fca43ff5d00933d0a63fd85155466fbed" dependencies = [ "awc", "clarity", - "futures 0.3.25", - "lazy_static", + "futures 0.3.21", + "lazy_static 1.4.0", "log 0.4.17", "num 0.4.0", "num256", - "serde 1.0.145", + "serde 1.0.137", "serde_derive", "serde_json", "tokio", @@ -7242,9 +8678,9 @@ dependencies = [ [[package]] name = "websocket" -version = "0.26.5" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92aacab060eea423e4036820ddd28f3f9003b2c4d8048cbda985e5a14e18038d" +checksum = "d0e2836502b48713d4e391e7e016df529d46e269878fe5d961b15a1fd6417f1a" dependencies = [ "bytes 0.4.12", "futures 0.1.31", @@ -7263,9 +8699,9 @@ dependencies = [ [[package]] name = "websocket-base" -version = "0.26.5" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aec794b07318993d1db16156d5a9c750120597a5ee40c6b928d416186cb138" +checksum = "403f3fd505ff930da84156389639932955fb09705b3dccd1a3d60c8e7ff62776" dependencies = [ "base64 0.10.1", "bitflags", @@ -7292,15 +8728,21 @@ dependencies = [ [[package]] name = "which" -version = "4.3.0" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", + "lazy_static 1.4.0", "libc", - "once_cell", ] +[[package]] +name = "widestring" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" + [[package]] name = "winapi" version = "0.2.8" @@ -7370,27 +8812,6 @@ dependencies = [ "windows_x86_64_msvc 0.36.1", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" - [[package]] name = "windows_aarch64_msvc" version = "0.29.0" @@ -7403,12 +8824,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" - [[package]] name = "windows_i686_gnu" version = "0.29.0" @@ -7421,12 +8836,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" -[[package]] -name = "windows_i686_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" - [[package]] name = "windows_i686_msvc" version = "0.29.0" @@ -7439,12 +8848,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" -[[package]] -name = "windows_i686_msvc" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" - [[package]] name = "windows_x86_64_gnu" version = "0.29.0" @@ -7457,18 +8860,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" - [[package]] name = "windows_x86_64_msvc" version = "0.29.0" @@ -7482,10 +8873,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.0" +name = "winreg" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi 0.3.9", +] [[package]] name = "winreg" @@ -7515,6 +8909,17 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "xattr" version = "0.2.3" @@ -7533,11 +8938,25 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yamux" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" +dependencies = [ + "futures 0.3.21", + "log 0.4.17", + "nohash-hasher", + "parking_lot 0.11.2", + "rand 0.8.5", + "static_assertions", +] + [[package]] name = "zeroize" -version = "1.5.7" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" dependencies = [ "zeroize_derive", ] @@ -7556,18 +8975,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.10.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "4.1.6+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" dependencies = [ "libc", "zstd-sys", @@ -7575,9 +8994,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "1.6.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" dependencies = [ "cc", "libc", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 71d34f4404423d7745020a362bd48a3e9802e92e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1340/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 070773eb239a93ffac51edb446146323e4c636d9 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1341/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 752ec580b4af0e05f28aec6ab5607940054d4b5f Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1342/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 27dce8a8846078509adfcd4b3e0d9ba0627998d0 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:54:52 +0200 Subject: [PATCH 1343/1995] [fix]: Removed duplicated code block --- 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 37e96fb9b6..5ced875453 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -280,7 +280,7 @@ impl From for MerkleValue { } impl MerkleValue { - /// Get the natural byte repesentation of the value + /// Get the natural byte representation of the value pub fn to_bytes(self) -> Vec { match self { Self::Bytes(bytes) => bytes, From e5c10c4e8fb0ec0f6b92bec1de4f15974baae0ef Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1344/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 8119255945b12b66c73dec4f20183aee8537640b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1345/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 8b0bac838bf74a171d6392b921ce5729a15b1f8c Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1346/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/queries.rs | 2 +- shared/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0c7ad256ab..33eff32490 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -17,8 +17,8 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; -use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; +use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From d8d330cd4f3832cabe133810961856de14ec21bf Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 18 Oct 2022 15:15:57 +0200 Subject: [PATCH 1347/1995] rebasing --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 33eff32490..d9c5775cff 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -18,7 +18,6 @@ use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; use namada::types::keccak::encode::Encode; -use namada::types::ethereum_events::EthAddress; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; From 5528fedf77859ecf83dec13af1c5d39b082e8bc7 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1348/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 214bb6accee83b7fe4153c5ab77ae747ddb49c40 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1349/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 5ced875453..198eeeddfe 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,6 +291,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From e8cc9b5d8849b69079987d145c795632ef30a608 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1350/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 9eff7c07927f64bd2c2bb39e76ae774ccaf3d378 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1351/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From de2e9dcc9f947a65c7f3d4a1c40ca74c2539ed45 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1352/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 198eeeddfe..d6eea520e2 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -301,6 +301,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From e4971ec5f11887837fa5980217de47f885eae300 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1353/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - apps/src/lib/node/ledger/shell/queries.rs | 1 + shared/Cargo.toml | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d9c5775cff..0c7ad256ab 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -17,6 +17,7 @@ use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; +use namada::types::ethereum_events::EthAddress; use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 8e7104040db3c60252ad77988f9165ffdb785ce3 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1354/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 01544b6cee35c8929869f2b149a8cea70a285e8c Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1355/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index d6eea520e2..980e420cb1 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -311,6 +311,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From b80bd23c307eac8460dcfd4cfb73fa875c724aa0 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1356/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 9b1ea4e86bcc3eb97e0b53ae32a259f9884cb0e0 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 10:20:38 +0200 Subject: [PATCH 1357/1995] [chore]: Rebase on previous branches --- apps/src/lib/node/ledger/shell/queries.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0c7ad256ab..d613769c81 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,5 +1,4 @@ //! Shell methods for querying state - use std::cmp::max; use std::default::Default; From 8183a8704dccd6124538b69c87e73498ca53ec66 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 25 Oct 2022 11:11:28 +0200 Subject: [PATCH 1358/1995] [chore]: rebasing on eth-bridge-integration --- shared/src/types/storage.rs | 83 ------------------------------------- 1 file changed, 83 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 980e420cb1..2fce537fd4 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,36 +291,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { @@ -764,59 +734,6 @@ impl KeySeg for KeccakHash { self.to_string() } - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } -} - -/// Implement [`KeySeg`] for a type via base32hex of its BE bytes (using -/// `to_le_bytes()` and `from_le_bytes` methods) that maintains sort order of -/// the original data. -// TODO this could be a bit more efficient without the string conversion (atm -// with base32hex), if we can use bytes for storage key directly (which we can -// with rockDB, but atm, we're calling `to_string()` using the custom `Display` -// impl from here) -macro_rules! impl_int_key_seg { - ($unsigned:ty, $signed:ty, $len:literal) => { - impl KeySeg for $unsigned { - fn parse(string: String) -> Result { - let bytes = - BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { - Error::ParseKeySeg(format!( - "Failed parsing {} with {}", - string, err - )) - })?; - let mut fixed_bytes = [0; $len]; - fixed_bytes.copy_from_slice(&bytes); - Ok(<$unsigned>::from_be_bytes(fixed_bytes)) - } - - fn raw(&self) -> String { - BASE32HEX_NOPAD.encode(&self.to_be_bytes()) - } - - fn to_db_key(&self) -> DbKeySeg { - DbKeySeg::StringSeg(self.raw()) - } - } - - impl KeySeg for $signed { - fn parse(string: String) -> Result { - // get signed int from a unsigned int complemented with a min - // value - let complemented = <$unsigned>::parse(string)?; - let signed = (complemented as $signed) ^ <$signed>::MIN; - Ok(signed) - } - - fn raw(&self) -> String { - // signed int is converted to unsigned int that preserves the - // order by complementing it with a min value - let complemented = (*self ^ <$signed>::MIN) as $unsigned; - complemented.raw() - } - fn to_db_key(&self) -> DbKeySeg { DbKeySeg::StringSeg(self.raw()) } From 71dbeb6545ecdcf05187e1fd5170eb2d5d303085 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1359/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 7b11e0afebd12abf835006dcd361192d8c81e273 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 14:34:11 +0000 Subject: [PATCH 1360/1995] query_tx_events should return eyre::Result --- apps/src/lib/client/rpc.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f7e56d83d8..0a4e883a57 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -12,6 +12,7 @@ use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; use data_encoding::HEXLOWER; +use eyre::{eyre, Context as EyreContext}; use itertools::Itertools; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; @@ -1435,13 +1436,23 @@ impl<'a> From> for Query { pub async fn query_tx_events( client: &HttpClient, tx_event_query: TxEventQuery<'_>, -) -> Result, queries::tm::Error> { - let tx_hash: Hash = tx_event_query.tx_hash().try_into().unwrap(); +) -> eyre::Result> { + let tx_hash: Hash = tx_event_query.tx_hash().try_into()?; match tx_event_query { - TxEventQuery::Accepted(_) => { - RPC.shell().accepted(client, &tx_hash).await - } - TxEventQuery::Applied(_) => RPC.shell().applied(client, &tx_hash).await, + TxEventQuery::Accepted(_) => RPC + .shell() + .accepted(client, &tx_hash) + .await + .wrap_err_with(|| { + eyre!("Failed querying whether a transaction was accepted") + }), + TxEventQuery::Applied(_) => RPC + .shell() + .applied(client, &tx_hash) + .await + .wrap_err_with(|| { + eyre!("Error querying whether a transaction was applied") + }), } } From 37090d7425023e62653eca9c1e4ea544cff8ae90 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1361/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 82e9dd0229c57523446985d6a54b85b23ed6eabe Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1362/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 144b21beab2e6d90bf82a23586bfd954fba1fed6 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1363/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 67f52a5d926c9065bd1fc94b37ca3ecf59ed5ef1 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1364/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 2fce537fd4..ce470869bc 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,6 +291,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 121a28a994cca9a35f1a9fe7747e2dbb69b27661 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1365/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 9b5e6369d39d87455bf1077ca572a27440175042 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1366/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From caa7bd27ffeb9fae4f4a4bc9ca8919d2260dd72e Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1367/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 1b17b6938167dabbc6012ee9c47463490998c98d Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1368/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 84d2724735d345a9d37f60088b024b5b8470c345 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1369/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 255afd4060f0a87bede07d3d27fb7e31bd95ee64 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1370/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index ce470869bc..657fa78578 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -301,6 +301,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From b0b9fea160668fe18ae1834b02df6b4d89fa6365 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1371/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 5b2f0088672183cf8d7958027449fe5e0cc27741 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 21 Oct 2022 10:54:52 +0200 Subject: [PATCH 1372/1995] [fix]: Removed duplicated code block --- shared/src/types/storage.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 657fa78578..2fce537fd4 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,26 +291,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 7bc980584f84af1c7ff648ecd3c400f694e2de08 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1373/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 19515b63f3fa5118625cc9c15d33264fc285128a Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1374/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, From 62e25bc5d4502b7a4ab0646aaae88058af5fda16 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1375/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 0af809fd2a5409e42019a3a4a7087b35b7a41e99 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1376/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 4752f274b945b09ac255752c97fed61723d7b4c0 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1377/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 2fce537fd4..ce470869bc 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,6 +291,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From b42cced989a586a5f29148d4d28c2c542ba975a1 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1378/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 00b6277ed89869449a5e62c821702167544fe52a Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1379/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 0fe7ea5107848841fc0c5f2ce9e6bea63219dda9 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1380/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index ce470869bc..657fa78578 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -301,6 +301,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 0177f77306dc05e6ef31da9564a386838e4b2841 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1381/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 8c5554f8fe2599a00d63be469911c8fb5a84771d Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 12 Oct 2022 16:45:33 +0200 Subject: [PATCH 1382/1995] [feat]: Added query end points to get the contents of the eth bridge pool and request merkle proofs --- shared/src/ledger/storage/merkle_tree.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..481fc0df62 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,8 +277,7 @@ impl MerkleTree { 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); - let bridge_pool = - BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); + let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); Self { base, account, From 8e6da14ed6d7e9bf438f783c6ff7a46cb7d6c062 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 13:43:00 +0200 Subject: [PATCH 1383/1995] [chore]: Added unit tests to the queries and fixed bugs it found --- Cargo.lock | 1 + shared/Cargo.toml | 1 + shared/src/ledger/storage/merkle_tree.rs | 3 ++- shared/src/types/storage.rs | 10 ++++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..e6f7387438 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,6 +4329,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", + "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9b5c65414e..6f2cd46b2a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,6 +106,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" +secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 481fc0df62..9ade2555e0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -277,7 +277,8 @@ impl MerkleTree { 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); - let bridge_pool = BridgePoolTree::new(stores.bridge_pool.1); + let bridge_pool = + BridgePoolTree::new(stores.bridge_pool.0, stores.bridge_pool.1); Self { base, account, diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 657fa78578..162ae2f6ee 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -311,6 +311,16 @@ impl MerkleValue { } } +impl MerkleValue { + /// Get the natural byte repesentation of the value + pub fn to_bytes(self) -> Vec { + match self { + Self::Bytes(bytes) => bytes, + Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), + } + } +} + /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From e0b82052ed505e4c9a12e6bb5928a31735e1ce24 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 14 Oct 2022 17:08:43 +0200 Subject: [PATCH 1384/1995] [feat]: Added recovery ids to secp256k1 signatures --- Cargo.lock | 1 - shared/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6f7387438..9248c9915c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4329,7 +4329,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "secp256k1", "serde 1.0.137", "serde_json", "sha2 0.9.9", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6f2cd46b2a..9b5c65414e 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -106,7 +106,6 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rust_decimal = "1.14.3" -secp256k1 = "0.21.3" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 5dd3168c2ac5acd8dda610d59bbaec3a682b31c4 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 15:30:13 +0200 Subject: [PATCH 1385/1995] Update shared/src/types/eth_bridge_pool.rs Co-authored-by: James --- shared/src/types/eth_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index 23fe162ab6..f922e12036 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -138,7 +138,7 @@ impl Encode<2> for MultiSignedMerkleRoot { pub struct RelayProof { /// Information about the signing validators pub validator_args: ValidatorSetArgs, - /// A merkle root signed by ta quorum of validators + /// A merkle root signed by a quorum of validators pub root: MultiSignedMerkleRoot, /// A membership proof pub proof: BridgePoolProof, From 6c3107879a0505533ec9fb342a7529b18fe6528c Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 26 Oct 2022 16:05:04 +0200 Subject: [PATCH 1386/1995] [fix]: Adding changes from code review --- apps/src/lib/node/ledger/shell/queries.rs | 130 ++++++++++------------ shared/src/types/eth_bridge_pool.rs | 10 +- shared/src/types/key/secp256k1.rs | 14 ++- 3 files changed, 81 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d613769c81..601f4c77df 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -360,43 +360,36 @@ where /// Read the current contents of the Ethereum bridge /// pool. fn read_ethereum_bridge_pool(&self) -> response::Query { - if let Ok(Some(stores)) = self + let stores = self .storage .db .read_merkle_tree_stores(self.storage.last_height) - { - let store = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store, - _ => unreachable!(), - }; - let transfers: Vec = store - .iter() - .map(|hash| { - let res = self - .storage - .read(&get_key_from_hash(hash)) - .unwrap() - .0 - .unwrap(); - BorshDeserialize::try_from_slice(res.as_slice()).unwrap() - }) - .collect(); - response::Query { - code: 0, - value: transfers.try_to_vec().unwrap(), - ..Default::default() - } - } else { - response::Query { - code: 1, - log: "Could not retrieve the Ethereum bridge pool for the \ - latest height" - .into(), - info: "Could not retrieve the Ethereum bridge pool for the \ - latest height" - .into(), - ..Default::default() - } + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing block \ + height", + ); + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, + _ => unreachable!(), + }; + + let transfers: Vec = store + .iter() + .map(|hash| { + let res = self + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); + response::Query { + code: 0, + value: transfers.try_to_vec().unwrap(), + ..Default::default() } } @@ -410,43 +403,40 @@ where >::try_from_slice(request_bytes.as_slice()) { // get the latest signed merkle root of the Ethereum bridge pool - let signed_root: MultiSignedMerkleRoot = - match self.storage.read(&get_signed_root_key()) { - Ok((Some(bytes), _)) => { - BorshDeserialize::try_from_slice(bytes.as_slice()) - .unwrap() - } - _ => { - return response::Query { - code: 1, - log: "Could not deserialize the signed Ethereum \ - bridge pool merkle root" - .into(), - info: "Could not deserialize the signed Ethereum \ - bridge pool merkle root" - .into(), - ..Default::default() - }; - } - }; - // get the merkle tree corresponding to the above root. - let tree = if let Ok(Some(stores)) = - self.storage.db.read_merkle_tree_stores(signed_root.height) + let signed_root: MultiSignedMerkleRoot = match self + .storage + .read(&get_signed_root_key()) + .expect("Reading the database should not faile") { - MerkleTree::::new(stores) - } else { - return response::Query { - code: 1, - log: "Could not retrieve the Ethereum bridge pool for the \ - latest signed root" - .into(), - info: "Could not retrieve the Ethereum bridge pool for \ - the latest signed root" - .into(), - ..Default::default() - }; + (Some(bytes), _) => { + BorshDeserialize::try_from_slice(bytes.as_slice()).unwrap() + } + _ => { + return response::Query { + code: 1, + log: "No signed root for the Ethereum bridge pool \ + exists in storage." + .into(), + info: "No signed root for the Ethereum bridge pool \ + exists in storage." + .into(), + ..Default::default() + }; + } }; + // get the merkle tree corresponding to the above root. + let tree = MerkleTree::::new( + self.storage + .db + .read_merkle_tree_stores(signed_root.height) + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing \ + block height", + ), + ); + // get the membership proof let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); match tree.get_sub_tree_existence_proof( @@ -1181,7 +1171,7 @@ mod test_queries { // create a signed Merkle root for this pool let signed_root = MultiSignedMerkleRoot { - sigs: vec![], + sigs: Default::default(), root: transfer.keccak256(), height: Default::default(), }; @@ -1260,7 +1250,7 @@ mod test_queries { // create a signed Merkle root for this pool let signed_root = MultiSignedMerkleRoot { - sigs: vec![], + sigs: Default::default(), root: transfer.keccak256(), height: Default::default(), }; diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index f922e12036..eb25dae7ba 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -1,5 +1,7 @@ //! The necessary type definitions for the contents of the //! Ethereum bridge pool +use std::collections::BTreeSet; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::token::Token; @@ -12,6 +14,9 @@ use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; use crate::types::vote_extensions::validator_set_update::ValidatorSetArgs; +/// A namespace used in our Ethereuem smart contracts +const NAMESPACE: &str = "transfer"; + /// A transfer message to be submitted to Ethereum /// to move assets from Namada across the bridge. #[derive( @@ -63,8 +68,9 @@ pub struct PendingTransfer { impl Encode<8> for PendingTransfer { fn tokenize(&self) -> [Token; 8] { + // TODO: This version should be looked up from storage let version = Token::Uint(1.into()); - let namespace = Token::String("transfer".into()); + let namespace = Token::String(NAMESPACE.into()); let from = Token::Address(self.transfer.asset.0.into()); let fee = Token::Uint(u64::from(self.gas_fee.amount).into()); let to = Token::Address(self.transfer.recipient.0.into()); @@ -113,7 +119,7 @@ pub struct GasFee { #[derive(Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct MultiSignedMerkleRoot { /// The signatures from validators - pub sigs: Vec, + pub sigs: BTreeSet, /// The Merkle root being signed pub root: KeccakHash, /// The block height at which this root was valid diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 5e5278b06e..16cf7c1cda 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -1,5 +1,6 @@ //! secp256k1 keys and related functionality +use std::cmp::Ordering; use std::fmt; use std::fmt::{Debug, Display}; use std::hash::{Hash, Hasher}; @@ -443,7 +444,18 @@ impl Hash for Signature { impl PartialOrd for Signature { fn partial_cmp(&self, other: &Self) -> Option { - self.0.serialize().partial_cmp(&other.0.serialize()) + match self.0.serialize().partial_cmp(&other.0.serialize()) { + Some(Ordering::Equal) => { + self.1.serialize().partial_cmp(&other.1.serialize()) + } + res => res, + } + } +} + +impl Ord for Signature { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() } } From 98816cec02f79b309c705f066b6841f694b8f5c6 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 16:05:53 +0200 Subject: [PATCH 1387/1995] Update shared/src/types/vote_extensions/validator_set_update.rs Co-authored-by: Tiago Carvalho --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 1312371927..fdc05dd0de 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -296,7 +296,7 @@ impl Encode<1> for ValidatorSetArgs { .collect(), ); let nonce = Token::Uint(self.nonce.clone().into()); - [Token::FixedArray(vec![addrs, powers, nonce])] + [Token::Tuple(vec![addrs, powers, nonce])] } } From 9d4fd81bd656f586fce05718bf259452e249d1a0 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Wed, 26 Oct 2022 16:06:00 +0200 Subject: [PATCH 1388/1995] Update shared/src/types/key/secp256k1.rs Co-authored-by: Tiago Carvalho --- shared/src/types/key/secp256k1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 16cf7c1cda..f23e2ec820 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -431,7 +431,7 @@ impl Encode<1> for Signature { let r = Token::FixedBytes(sig_serialized[..32].to_vec()); let s = Token::FixedBytes(sig_serialized[32..].to_vec()); let v = Token::FixedBytes(vec![self.1.serialize()]); - [Token::FixedArray(vec![r, s, v])] + [Token::Tuple(vec![r, s, v])] } } From 3341ee127f12d23ac6b1e35ec05cde7372d03c2e Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 2 Nov 2022 15:50:23 +0100 Subject: [PATCH 1389/1995] [c hore]: rebasing --- Cargo.lock | 1998 ++++------------------------------- shared/src/types/storage.rs | 83 +- wasm/Cargo.lock | 19 - 3 files changed, 273 insertions(+), 1827 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9248c9915c..b20e8d1c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,16 +14,16 @@ dependencies = [ "futures-sink", "log 0.4.17", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-util 0.7.3", ] [[package]] name = "actix-http" -version = "3.0.4" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a" +checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" dependencies = [ "actix-codec", "actix-rt", @@ -45,13 +45,13 @@ dependencies = [ "itoa", "language-tags 0.3.2", "local-channel", - "log 0.4.17", "mime 0.3.16", "percent-encoding 2.1.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "rand 0.8.5", - "sha-1 0.10.0", + "sha1", "smallvec 1.8.0", + "tracing 0.1.35", "zstd", ] @@ -73,7 +73,7 @@ checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -90,7 +90,7 @@ dependencies = [ "http", "log 0.4.17", "openssl", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio-openssl", "tokio-util 0.7.3", ] @@ -102,7 +102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" dependencies = [ "local-waker", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -120,41 +120,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes-gcm" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle 2.4.1", -] - [[package]] name = "ahash" version = "0.7.6" @@ -345,12 +310,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" -[[package]] -name = "asn1_der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" - [[package]] name = "assert_cmd" version = "1.0.8" @@ -414,10 +373,10 @@ dependencies = [ [[package]] name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +version = "1.9.0" +source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ + "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -425,8 +384,9 @@ dependencies = [ "once_cell", "parking", "polling", + "rustversion", "slab", - "socket2 0.4.4", + "socket2", "waker-fn", "winapi 0.3.9", ] @@ -451,17 +411,18 @@ dependencies = [ [[package]] name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +version = "1.5.0" +source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" dependencies = [ "async-io", + "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", "libc", "once_cell", + "rustversion", "signal-hook", "winapi 0.3.9", ] @@ -488,26 +449,12 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] -[[package]] -name = "async-std-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" -dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "trust-dns-resolver", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -555,35 +502,13 @@ dependencies = [ "futures-io", "futures-util", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-rustls", "tungstenite 0.12.0", "webpki-roots", ] -[[package]] -name = "asynchronous-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" -dependencies = [ - "bytes 1.1.0", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite 0.2.9", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -642,7 +567,7 @@ dependencies = [ "mime 0.3.16", "openssl", "percent-encoding 2.1.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "rand 0.8.5", "serde 1.0.137", "serde_json", @@ -684,12 +609,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -702,6 +621,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" @@ -713,14 +641,14 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.59.2" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static 1.4.0", + "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", @@ -763,17 +691,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "blake2" version = "0.10.4" @@ -919,19 +836,13 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "memchr", "regex-automata", ] @@ -1010,12 +921,6 @@ dependencies = [ "iovec", ] -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -1048,19 +953,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" -[[package]] -name = "cargo-watch" -version = "7.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" -dependencies = [ - "clap 2.34.0", - "log 0.4.17", - "shell-escape", - "stderrlog", - "watchexec", -] - [[package]] name = "cc" version = "1.0.72" @@ -1091,18 +983,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha20" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.1.5", - "zeroize", -] - [[package]] name = "chacha20" version = "0.8.1" @@ -1111,20 +991,7 @@ checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", -] - -[[package]] -name = "chacha20poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" -dependencies = [ - "aead", - "chacha20 0.7.1", - "cipher", - "poly1305", - "zeroize", + "cpufeatures", ] [[package]] @@ -1134,11 +1001,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ "iana-time-zone", - "js-sys", "num-integer", "num-traits 0.2.15", - "time 0.1.44", - "wasm-bindgen", "winapi 0.3.9", ] @@ -1177,22 +1041,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", - "term_size", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - [[package]] name = "clap" version = "3.0.0-beta.2" @@ -1201,11 +1049,11 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static 1.4.0", + "lazy_static", "os_str_bytes", - "strsim 0.10.0", + "strsim", "termcolor", - "textwrap 0.12.1", + "textwrap", "unicode-width", "vec_map", ] @@ -1216,7 +1064,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e043fca6ce2fabc4566fe447d2185a724529a383f3e9938279a53ea75532a02" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num-bigint 0.4.3", "num-traits 0.2.15", "num256", @@ -1294,7 +1142,7 @@ 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", @@ -1341,15 +1189,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "cpufeatures" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" -dependencies = [ - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -1458,7 +1297,7 @@ dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "lazy_static", "memoffset", "scopeguard", ] @@ -1471,7 +1310,7 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -1481,7 +1320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -1500,16 +1339,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - [[package]] name = "crypto-mac" version = "0.8.0" @@ -1517,7 +1346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1545,32 +1374,12 @@ dependencies = [ "syn", ] -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "cuckoofilter" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" -dependencies = [ - "byteorder", - "fnv", - "rand 0.7.3", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1580,7 +1389,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1597,38 +1406,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]] @@ -1644,24 +1429,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", ] @@ -1683,37 +1457,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" @@ -1765,37 +1508,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - -[[package]] -name = "dns-parser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error 1.2.3", + "subtle", ] [[package]] @@ -1818,7 +1531,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", @@ -1883,19 +1596,6 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "embed-resource" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" -dependencies = [ - "cc", - "rustc_version 0.4.0", - "toml", - "vswhom", - "winreg 0.10.1", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -1905,18 +1605,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "enum-as-inner" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" -dependencies = [ - "heck 0.4.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -1952,21 +1640,12 @@ 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", ] -[[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 = "error" version = "0.1.9" @@ -2096,7 +1775,7 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", - "blake2 0.10.4", + "blake2", "blake2b_simd", "borsh", "digest 0.10.3", @@ -2105,7 +1784,7 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools", "measure_time", "miracl_core", "num 0.4.0", @@ -2115,7 +1794,7 @@ dependencies = [ "serde_bytes", "serde_json", "subproductdomain", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -2179,12 +1858,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "fixedbitset" version = "0.4.1" @@ -2198,7 +1871,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -2249,25 +1921,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" @@ -2342,7 +1995,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -2362,7 +2014,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite", "waker-fn", ] @@ -2377,17 +2029,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-rustls" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" -dependencies = [ - "futures-io", - "rustls", - "webpki", -] - [[package]] name = "futures-sink" version = "0.3.21" @@ -2400,12 +2041,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.21" @@ -2419,7 +2054,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] @@ -2465,16 +2100,6 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" -dependencies = [ - "opaque-debug 0.3.0", - "polyval", -] - [[package]] name = "gimli" version = "0.25.0" @@ -2513,19 +2138,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "globset" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log 0.4.17", - "regex", -] - [[package]] name = "gloo-timers" version = "0.2.4" @@ -2551,9 +2163,9 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20", "hex", - "itertools 0.10.3", + "itertools", "miracl_core", "rand 0.8.5", "rand_core 0.6.3", @@ -2688,43 +2300,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", -] - [[package]] name = "hmac" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac 0.8.0", + "crypto-mac", "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2733,18 +2318,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array 0.14.5", - "hmac 0.8.1", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", + "hmac", ] [[package]] @@ -2766,7 +2340,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -2816,8 +2390,8 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.9", - "socket2 0.4.4", + "pin-project-lite", + "socket2", "tokio", "tower-service", "tracing 0.1.35", @@ -2868,7 +2442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper 0.14.19", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-io-timeout", ] @@ -2910,8 +2484,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.137", "serde_derive", @@ -2937,8 +2511,8 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.137", "serde_derive", @@ -2960,8 +2534,8 @@ source = "git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2 dependencies = [ "base64 0.13.0", "bytes 1.1.0", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "tendermint-proto 0.23.5", ] @@ -2973,8 +2547,8 @@ source = "git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279 dependencies = [ "base64 0.13.0", "bytes 1.1.0", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "tendermint-proto 0.23.6", ] @@ -2988,7 +2562,7 @@ dependencies = [ "anyhow", "bytes 1.1.0", "hex", - "prost 0.9.0", + "prost", "ripemd160", "sha2 0.9.9", "sha3 0.9.1", @@ -3023,43 +2597,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if-addrs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2273e421f7c4f0fc99e1934fe4776f59d8df2972f4199d703fc0da9f2a9f73de" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "if-addrs-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "if-watch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-lite", - "if-addrs", - "ipnet", - "libc", - "log 0.4.17", - "winapi 0.3.9", -] - [[package]] name = "impl-codec" version = "0.6.0" @@ -3115,26 +2652,6 @@ dependencies = [ "serde 1.0.137", ] -[[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" @@ -3171,33 +2688,12 @@ dependencies = [ "libc", ] -[[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2 0.3.19", - "widestring", - "winapi 0.3.9", - "winreg 0.6.2", -] - [[package]] name = "ipnet" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.3" @@ -3268,12 +2764,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" -[[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" @@ -3335,422 +2825,11 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "libp2p" -version = "0.38.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-deflate", - "libp2p-dns", - "libp2p-floodsub", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-kad", - "libp2p-mdns", - "libp2p-mplex", - "libp2p-noise", - "libp2p-ping", - "libp2p-plaintext", - "libp2p-pnet", - "libp2p-relay", - "libp2p-request-response", - "libp2p-swarm", - "libp2p-swarm-derive", - "libp2p-tcp", - "libp2p-uds", - "libp2p-wasm-ext", - "libp2p-websocket", - "libp2p-yamux", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-core" -version = "0.28.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "either", - "fnv", - "futures 0.3.21", - "futures-timer", - "lazy_static 1.4.0", - "libsecp256k1 0.3.5", - "log 0.4.17", - "multihash", - "multistream-select", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "ring", - "rw-stream-sink", - "sha2 0.9.9", - "smallvec 1.8.0", - "thiserror", - "unsigned-varint 0.7.1", - "void", - "zeroize", -] - -[[package]] -name = "libp2p-deflate" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "flate2", - "futures 0.3.21", - "libp2p-core", -] - -[[package]] -name = "libp2p-dns" -version = "0.28.1" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std-resolver", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "smallvec 1.8.0", - "trust-dns-resolver", -] - -[[package]] -name = "libp2p-floodsub" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "cuckoofilter", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "base64 0.13.0", - "byteorder", - "bytes 1.1.0", - "fnv", - "futures 0.3.21", - "hex_fmt", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "regex", - "sha2 0.9.9", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-identify" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-kad" -version = "0.30.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayvec 0.5.2", - "asynchronous-codec", - "bytes 1.1.0", - "either", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "sha2 0.9.9", - "smallvec 1.8.0", - "uint", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-mdns" -version = "0.30.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "data-encoding", - "dns-parser", - "futures 0.3.21", - "if-watch", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", - "void", -] - -[[package]] -name = "libp2p-mplex" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - -[[package]] -name = "libp2p-noise" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.8.5", - "sha2 0.9.9", - "snow", - "static_assertions", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "libp2p-ping" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.7.3", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-plaintext" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "unsigned-varint 0.7.1", - "void", -] - -[[package]] -name = "libp2p-pnet" -version = "0.21.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "rand 0.7.3", - "salsa20", - "sha3 0.9.1", -] - -[[package]] -name = "libp2p-relay" -version = "0.2.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "futures-timer", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-request-response" -version = "0.11.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "lru", - "minicbor", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "rand 0.7.3", - "smallvec 1.8.0", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.23.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "libp2p-tcp" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-timer", - "if-watch", - "ipnet", - "libc", - "libp2p-core", - "log 0.4.17", - "socket2 0.4.4", -] - -[[package]] -name = "libp2p-uds" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", -] - -[[package]] -name = "libp2p-wasm-ext" -version = "0.28.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "js-sys", - "libp2p-core", - "parity-send-wrapper", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "libp2p-websocket" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "futures-rustls", - "libp2p-core", - "log 0.4.17", - "quicksink", - "rw-stream-sink", - "soketto", - "url 2.2.2", - "webpki-roots", -] - -[[package]] -name = "libp2p-yamux" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "parking_lot 0.11.2", - "thiserror", - "yamux", -] - [[package]] name = "librocksdb-sys" -version = "0.6.1+6.28.2" +version = "0.8.0+7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" +checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" dependencies = [ "bindgen", "bzip2-sys", @@ -3758,25 +2837,10 @@ dependencies = [ "glob", "libc", "libz-sys", + "tikv-jemalloc-sys", "zstd-sys", ] -[[package]] -name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg 0.2.0", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.1", - "typenum", -] - [[package]] name = "libsecp256k1" version = "0.7.0" @@ -3785,7 +2849,7 @@ dependencies = [ "arrayref", "base64 0.13.0", "digest 0.9.0", - "hmac-drbg 0.3.0", + "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3802,7 +2866,7 @@ source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -3934,24 +2998,6 @@ dependencies = [ "syn", ] -[[package]] -name = "lru" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "mach" version = "0.3.2" @@ -3973,13 +3019,7 @@ dependencies = [ "serde 1.0.137", "serde_derive", "serde_yaml", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" +] [[package]] name = "matchers" @@ -4057,7 +3097,7 @@ dependencies = [ "crossbeam-channel", "crossbeam-utils 0.8.8", "integer-encoding", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.7.14", "serde 1.0.137", @@ -4091,26 +3131,6 @@ dependencies = [ "unicase 2.6.0", ] -[[package]] -name = "minicbor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51aa5bb0ca22415daca596a227b507f880ad1b2318a87fa9325312a5d285ca0d" -dependencies = [ - "minicbor-derive", -] - -[[package]] -name = "minicbor-derive" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54999f917cd092b13904737e26631aa2b2b88d625db68e4bab461dcd8006c788" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4170,18 +3190,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" @@ -4224,33 +3232,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "multihash" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.5", - "multihash-derive", - "sha2 0.9.9", - "unsigned-varint 0.5.1", -] - -[[package]] -name = "multihash-derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "multimap" version = "0.8.3" @@ -4275,22 +3256,9 @@ dependencies = [ "twoway", ] -[[package]] -name = "multistream-select" -version = "0.10.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - [[package]] name = "namada" -version = "0.7.1" +version = "0.8.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4315,16 +3283,16 @@ dependencies = [ "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ics23", - "itertools 0.10.3", - "libsecp256k1 0.7.0", + "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "num-rational 0.4.1", "parity-wasm", "pretty_assertions", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "pwasm-utils", "rand 0.8.5", "rand_core 0.6.3", @@ -4356,7 +3324,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.7.1" +version = "0.8.1" dependencies = [ "ark-serialize", "ark-std", @@ -4365,20 +3333,20 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", + "bimap", "bit-set", "blake2b-rs", "borsh", "byte-unit", "byteorder", "bytes 1.1.0", - "cargo-watch", "circular-queue", - "clap 3.0.0-beta.2", + "clap", "clarity", "color-eyre", "config", + "data-encoding", "derivative", - "directories", "ed25519-consensus", "ethabi", "eyre", @@ -4388,11 +3356,9 @@ dependencies = [ "flate2", "futures 0.3.21", "git2", - "hex", - "itertools 0.10.3", + "itertools", "libc", "libloading", - "libp2p", "message-io", "namada", "num-derive", @@ -4401,10 +3367,9 @@ dependencies = [ "num_cpus", "once_cell", "orion", - "pathdiff", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.3", "rayon", @@ -4438,7 +3403,6 @@ dependencies = [ "tokio-test", "toml", "tonic", - "tonic-build", "tower", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109901abfa1b2f782d242f082)", @@ -4453,18 +3417,18 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.7.1" +version = "0.8.1" dependencies = [ "borsh", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools", + "lazy_static", "madato", "namada", ] [[package]] name = "namada_macros" -version = "0.7.1" +version = "0.8.1" dependencies = [ "quote", "syn", @@ -4472,37 +3436,38 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.1" +version = "0.8.1" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] [[package]] name = "namada_tests" -version = "0.7.1" +version = "0.8.1" dependencies = [ "assert_cmd", "borsh", "chrono", "color-eyre", "concat-idents", + "data-encoding", "derivative", "escargot", "expectrl", "eyre", "file-serve", "fs_extra", - "hex", - "itertools 0.10.3", - "libp2p", + "itertools", "namada", "namada_apps", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "pretty_assertions", "proptest", - "prost 0.9.0", + "prost", "rand 0.8.5", "serde_json", "sha2 0.9.9", @@ -4515,28 +3480,34 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.1" +version = "0.8.1" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] name = "namada_vm_env" -version = "0.7.1" +version = "0.8.1" dependencies = [ "borsh", - "hex", "namada", - "namada_macros", ] [[package]] name = "namada_vp_prelude" -version = "0.7.1" +version = "0.8.1" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -4545,7 +3516,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", @@ -4568,19 +3539,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "nix" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.21.2" @@ -4618,12 +3576,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - [[package]] name = "nom" version = "5.1.2" @@ -4645,24 +3597,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" @@ -4822,7 +3756,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num 0.4.0", "num-derive", "num-traits 0.2.15", @@ -4938,7 +3872,7 @@ checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", "getrandom 0.2.6", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -4963,23 +3897,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" -[[package]] -name = "parity-multiaddr" -version = "0.11.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayref", - "bs58", - "byteorder", - "data-encoding", - "multihash", - "percent-encoding 2.1.0", - "serde 1.0.137", - "static_assertions", - "unsigned-varint 0.7.1", - "url 2.2.2", -] - [[package]] name = "parity-scale-codec" version = "3.1.5" @@ -5006,12 +3923,6 @@ dependencies = [ "syn", ] -[[package]] -name = "parity-send-wrapper" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" - [[package]] name = "parity-wasm" version = "0.42.2" @@ -5035,17 +3946,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api 0.4.7", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -5071,20 +3971,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.9.3" @@ -5104,12 +3990,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -5164,53 +4044,23 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset 0.2.0", - "indexmap", -] - [[package]] name = "petgraph" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset", "indexmap", ] -[[package]] -name = "pin-project" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" -dependencies = [ - "pin-project-internal 0.4.29", -] - [[package]] name = "pin-project" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ - "pin-project-internal 1.0.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-internal", ] [[package]] @@ -5224,12 +4074,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -5250,40 +4094,18 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +version = "2.3.0" +source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", + "rustversion", "wepoll-ffi", "winapi 0.3.9", ] -[[package]] -name = "poly1305" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" -dependencies = [ - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -5297,7 +4119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools", "predicates-core", ] @@ -5402,7 +4224,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", @@ -5413,16 +4235,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "prost" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.7.0", -] - [[package]] name = "prost" version = "0.9.0" @@ -5430,25 +4242,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.1.0", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost-build" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" -dependencies = [ - "bytes 1.1.0", - "heck 0.3.3", - "itertools 0.9.0", - "log 0.4.17", - "multimap", - "petgraph 0.5.1", - "prost 0.7.0", - "prost-types 0.7.0", - "tempfile", - "which", + "prost-derive", ] [[package]] @@ -5459,31 +4253,18 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.1.0", "heck 0.3.3", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools", + "lazy_static", "log 0.4.17", "multimap", - "petgraph 0.6.2", - "prost 0.9.0", - "prost-types 0.9.0", + "petgraph", + "prost", + "prost-types", "regex", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" -dependencies = [ - "anyhow", - "itertools 0.9.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.9.0" @@ -5491,22 +4272,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools", "proc-macro2", "quote", "syn", ] -[[package]] -name = "prost-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" -dependencies = [ - "bytes 1.1.0", - "prost 0.7.0", -] - [[package]] name = "prost-types" version = "0.9.0" @@ -5514,7 +4285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.1.0", - "prost 0.9.0", + "prost", ] [[package]] @@ -5564,21 +4335,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quicksink" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project-lite 0.1.12", -] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" @@ -5838,17 +4598,6 @@ dependencies = [ "redox_syscall 0.2.13", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", - "thiserror", -] - [[package]] name = "regalloc" version = "0.0.31" @@ -5934,12 +4683,12 @@ dependencies = [ "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", + "pin-project-lite", "serde 1.0.137", "serde_json", "serde_urlencoded", @@ -5949,17 +4698,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", + "winreg", ] [[package]] @@ -6034,9 +4773,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" +checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" dependencies = [ "libc", "librocksdb-sys", @@ -6141,9 +4880,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" @@ -6157,17 +4896,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "rw-stream-sink" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" -dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", - "static_assertions", -] - [[package]] name = "ryu" version = "1.0.10" @@ -6227,15 +4955,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "salsa20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -6251,7 +4970,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", ] @@ -6384,7 +5103,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", @@ -6507,7 +5226,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6519,20 +5238,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] [[package]] -name = "sha2" -version = "0.8.2" +name = "sha1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", ] [[package]] @@ -6543,7 +5261,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6555,7 +5273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] @@ -6587,15 +5305,9 @@ 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]] -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" @@ -6654,35 +5366,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "snow" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" -dependencies = [ - "aes-gcm", - "blake2 0.9.2", - "chacha20poly1305", - "rand 0.8.5", - "rand_core 0.6.3", - "ring", - "rustc_version 0.3.3", - "sha2 0.9.9", - "subtle 2.4.1", - "x25519-dalek", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - [[package]] name = "socket2" version = "0.4.4" @@ -6693,22 +5376,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "soketto" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" -dependencies = [ - "base64 0.12.3", - "bytes 0.5.6", - "flate2", - "futures 0.3.21", - "httparse", - "log 0.4.17", - "rand 0.7.3", - "sha-1 0.9.8", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -6718,7 +5385,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#04ad1eeb28901b57a7599bbe433b3822965dabe8" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?rev=04ad1eeb28901b57a7599bbe433b3822965dabe8#04ad1eeb28901b57a7599bbe433b3822965dabe8" dependencies = [ "blake2b-rs", "borsh", @@ -6745,25 +5412,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stderrlog" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" -dependencies = [ - "atty", - "chrono", - "log 0.4.17", - "termcolor", - "thread_local 0.3.4", -] - -[[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" @@ -6805,12 +5453,6 @@ dependencies = [ "ark-std", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -6919,15 +5561,15 @@ dependencies = [ "futures 0.3.21", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", "signature", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint-proto 0.23.5", "time 0.3.9", @@ -6947,15 +5589,15 @@ dependencies = [ "futures 0.3.21", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", "signature", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint-proto 0.23.6", "time 0.3.9", @@ -7022,8 +5664,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "subtle-encoding", @@ -7039,8 +5681,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "subtle-encoding", @@ -7063,7 +5705,7 @@ dependencies = [ "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", + "pin-project", "serde 1.0.137", "serde_bytes", "serde_json", @@ -7096,7 +5738,7 @@ dependencies = [ "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", + "pin-project", "serde 1.0.137", "serde_bytes", "serde_json", @@ -7143,16 +5785,6 @@ dependencies = [ "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", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -7191,16 +5823,6 @@ dependencies = [ "syn", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "term_size", - "unicode-width", -] - [[package]] name = "textwrap" version = "0.12.1" @@ -7232,21 +5854,22 @@ dependencies = [ [[package]] name = "thread_local" -version = "0.3.4" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ - "lazy_static 0.2.11", - "unreachable", + "once_cell", ] [[package]] -name = "thread_local" -version = "1.1.4" +name = "tikv-jemalloc-sys" +version = "0.5.2+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" dependencies = [ - "once_cell", + "cc", + "fs_extra", + "libc", ] [[package]] @@ -7328,9 +5951,9 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi 0.3.9", ] @@ -7373,7 +5996,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -7418,7 +6041,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", @@ -7447,7 +6070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -7507,7 +6130,7 @@ checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" dependencies = [ "futures-util", "log 0.4.17", - "pin-project 1.0.10", + "pin-project", "tokio", "tungstenite 0.14.0", ] @@ -7522,7 +6145,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -7535,7 +6158,7 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tracing 0.1.35", ] @@ -7567,9 +6190,9 @@ dependencies = [ "hyper 0.14.19", "hyper-timeout", "percent-encoding 2.1.0", - "pin-project 1.0.10", - "prost 0.9.0", - "prost-derive 0.9.0", + "pin-project", + "prost", + "prost-derive", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7587,7 +6210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" dependencies = [ "proc-macro2", - "prost-build 0.9.0", + "prost-build", "quote", "syn", ] @@ -7602,8 +6225,8 @@ dependencies = [ "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", - "pin-project-lite 0.2.9", + "pin-project", + "pin-project-lite", "rand 0.8.5", "slab", "tokio", @@ -7620,8 +6243,8 @@ source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.10", - "prost 0.9.0", + "pin-project", + "prost", "tendermint-proto 0.23.5", "tokio", "tokio-stream", @@ -7638,8 +6261,8 @@ source = "git+https://github.com/heliaxdev/tower-abci.git?rev=fcc0014d0bda707109 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.10", - "prost 0.9.0", + "pin-project", + "prost", "tendermint-proto 0.23.6", "tokio", "tokio-stream", @@ -7676,7 +6299,7 @@ version = "0.1.30" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.19", "tracing-core 0.1.22", ] @@ -7689,7 +6312,7 @@ checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.21", "tracing-core 0.1.27", ] @@ -7720,7 +6343,7 @@ 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]] @@ -7749,7 +6372,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", + "pin-project", "tracing 0.1.35", ] @@ -7758,7 +6381,7 @@ name = "tracing-futures" version = "0.2.5" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing 0.1.30", ] @@ -7768,7 +6391,7 @@ 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", ] @@ -7780,7 +6403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local 1.1.4", + "thread_local", "tracing-core 0.1.27", ] @@ -7791,12 +6414,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" dependencies = [ "ansi_term", - "lazy_static 1.4.0", + "lazy_static", "matchers", "regex", "sharded-slab", "smallvec 1.8.0", - "thread_local 1.1.4", + "thread_local", "tracing 0.1.35", "tracing-core 0.1.27", "tracing-log", @@ -7808,7 +6431,7 @@ version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "futures 0.3.21", - "pin-project-lite 0.2.9", + "pin-project-lite", "tower-layer", "tower-make", "tower-service", @@ -7822,49 +6445,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -[[package]] -name = "trust-dns-proto" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static 1.4.0", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "thiserror", - "tinyvec", - "url 2.2.2", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lazy_static 1.4.0", - "log 0.4.17", - "lru-cache", - "parking_lot 0.11.2", - "resolv-conf", - "smallvec 1.8.0", - "thiserror", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -8024,43 +6604,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array 0.14.5", - "subtle 2.4.1", -] - -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - -[[package]] -name = "unsigned-varint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" - -[[package]] -name = "unsigned-varint" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures-io", - "futures-util", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -8151,32 +6694,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "void" -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" @@ -8230,7 +6747,7 @@ dependencies = [ "mime_guess", "multipart", "percent-encoding 2.1.0", - "pin-project 1.0.10", + "pin-project", "scoped-tls", "serde 1.0.137", "serde_json", @@ -8278,7 +6795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" dependencies = [ "bumpalo", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "proc-macro2", "quote", @@ -8336,21 +6853,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.21", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -8438,7 +6940,7 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static 1.4.0", + "lazy_static", "loupe", "more-asserts", "rayon", @@ -8468,7 +6970,7 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static 1.4.0", + "lazy_static", "loupe", "memmap2", "more-asserts", @@ -8608,26 +7110,6 @@ dependencies = [ "wast", ] -[[package]] -name = "watchexec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" -dependencies = [ - "clap 2.34.0", - "derive_builder", - "embed-resource", - "env_logger", - "glob", - "globset", - "lazy_static 1.4.0", - "log 0.4.17", - "nix 0.20.2", - "notify", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "web-sys" version = "0.3.57" @@ -8647,7 +7129,7 @@ dependencies = [ "awc", "clarity", "futures 0.3.21", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "num 0.4.0", "num256", @@ -8733,16 +7215,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", - "lazy_static 1.4.0", + "lazy_static", "libc", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - [[package]] name = "winapi" version = "0.2.8" @@ -8872,15 +7348,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "winreg" version = "0.10.1" @@ -8909,17 +7376,6 @@ dependencies = [ "tap", ] -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "xattr" version = "0.2.3" @@ -8938,20 +7394,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "yamux" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "zeroize" version = "1.5.5" @@ -8975,18 +7417,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.10.2+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.6+zstd.1.5.2" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -8994,9 +7436,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 162ae2f6ee..5ced875453 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -291,36 +291,6 @@ impl MerkleValue { } } -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - -impl MerkleValue { - /// Get the natural byte repesentation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::Transfer(transfer) => transfer.try_to_vec().unwrap(), - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { @@ -764,6 +734,59 @@ impl KeySeg for KeccakHash { self.to_string() } + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + +/// Implement [`KeySeg`] for a type via base32hex of its BE bytes (using +/// `to_le_bytes()` and `from_le_bytes` methods) that maintains sort order of +/// the original data. +// TODO this could be a bit more efficient without the string conversion (atm +// with base32hex), if we can use bytes for storage key directly (which we can +// with rockDB, but atm, we're calling `to_string()` using the custom `Display` +// impl from here) +macro_rules! impl_int_key_seg { + ($unsigned:ty, $signed:ty, $len:literal) => { + impl KeySeg for $unsigned { + fn parse(string: String) -> Result { + let bytes = + BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { + Error::ParseKeySeg(format!( + "Failed parsing {} with {}", + string, err + )) + })?; + let mut fixed_bytes = [0; $len]; + fixed_bytes.copy_from_slice(&bytes); + Ok(<$unsigned>::from_be_bytes(fixed_bytes)) + } + + fn raw(&self) -> String { + BASE32HEX_NOPAD.encode(&self.to_be_bytes()) + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } + } + + impl KeySeg for $signed { + fn parse(string: String) -> Result { + // get signed int from a unsigned int complemented with a min + // value + let complemented = <$unsigned>::parse(string)?; + let signed = (complemented as $signed) ^ <$signed>::MIN; + Ok(signed) + } + + fn raw(&self) -> String { + // signed int is converted to unsigned int that preserves the + // order by complementing it with a min value + let complemented = (*self ^ <$signed>::MIN) as $unsigned; + complemented.raw() + } + fn to_db_key(&self) -> DbKeySeg { DbKeySeg::StringSeg(self.raw()) } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 34c1f76a8c..e8a72f19d2 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1502,7 +1502,6 @@ dependencies = [ "rand", "rand_core 0.6.4", "rust_decimal", - "secp256k1", "serde", "serde_json", "sha2 0.9.9", @@ -2306,24 +2305,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "secp256k1" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "semver" version = "0.11.0" From dd157a55957c5624c2a9f1aba2075eecea7b9262 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Nov 2022 15:10:14 +0000 Subject: [PATCH 1390/1995] [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 19b8b54b52..1a2bf7273e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.482d444214f61b51df344da56f24cd0ca392e699e18c49d2d27b9f108ef643bd.wasm", + "tx_bond.wasm": "tx_bond.a4d5ce995dfbf957543fb46bf421d692fe31d12be329e5681cba8f70cf6b832d.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.b5a3bf6ca1dea0767406d64251928816a7d95b974cff09f6e702a8f3fbd64b1f.wasm", - "tx_init_account.wasm": "tx_init_account.343e04328e157514ec85cfb650cad5cad659eac27e80b1a0dec61286352a3c9d.wasm", - "tx_init_nft.wasm": "tx_init_nft.ea5ace3004d4d63b6a648bf2d16c94c95da65f85c4bc20a77f940b9cdfe346e9.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c8e187e2bd7869253f9d9d7de5fa2cb5896e9ca114f124ad800131c9e82db1a7.wasm", - "tx_init_validator.wasm": "tx_init_validator.23b5d73ff65718b5bc9f51423fad36c176dcde9e1006fc7c37cd8e64aae9b8b3.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.110baf82d92f78ca740750808237ecd4793e24b662876f363f51c5d1cd030694.wasm", - "tx_transfer.wasm": "tx_transfer.07335720d2ab07311219a81fd6baca5801792dc16b7c9bab490e2b756257a6dd.wasm", - "tx_unbond.wasm": "tx_unbond.f8879ee80dadf71bd663d46abbd39b47b4c59a3603d29465cf8cff1acbdfa9d3.wasm", - "tx_update_vp.wasm": "tx_update_vp.d2743de89548f3ae6decf2a32ab086e960be9b954bdd24bd6e8e731195449540.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.348c3c28fc9e7356a7106f4b037a0bc7b6b207761b000ad0e0cb786678afbee5.wasm", - "tx_withdraw.wasm": "tx_withdraw.a2a0a3f9eb961cba5bb4d1677805bafdcc807637fbd203f8afaa2aa2adb6857e.wasm", - "vp_nft.wasm": "vp_nft.e88e46e49cbbc28dd1fc4e518195bffc4d1feb43b4976d02580865298fd29e75.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.f6b3d44133b0c35cdbfbe19328d8cdfc62809bd30a0c64eef57f3877cc7e8c2f.wasm", - "vp_token.wasm": "vp_token.2100aaa1fed90d35e87aace6516794facdd7aab3062102c1a762bef604c98075.wasm", - "vp_user.wasm": "vp_user.14fdcbaa1bd3c28115a3eb1f53802b4042080bb255e276b15d2fb16338aacf31.wasm" + "tx_ibc.wasm": "tx_ibc.d2b843a342d4ce00bbe1da1024d0c7584b33250c63413562ed255134dfdba687.wasm", + "tx_init_account.wasm": "tx_init_account.14fa78708ce443cbcd80db64b4dfe881f97d917006b6da609a03180f0a2276e1.wasm", + "tx_init_nft.wasm": "tx_init_nft.910efb22032559555f57f35d5f108aed7c75371e28943f69c6c241c911c0e229.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6274c834a5aebb573f1fd759a06ccf4860dbdda633294a12cb29820be43e175b.wasm", + "tx_init_validator.wasm": "tx_init_validator.170b6231dc1231efe1cc2cc32acf231b31420e2d5fd8d5933e6aec45dc4cc123.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.674ecb2fde2ea8485c61913b4d62b2f7524c0b4a3367472cc3e2fc52bb5e5e0c.wasm", + "tx_transfer.wasm": "tx_transfer.de246f08731596eefcf0c9aa5c6a5a48458ac93185f56272a1f490c920495a09.wasm", + "tx_unbond.wasm": "tx_unbond.671f5760b468ed87ece024d4c665cdf407692e6e508fba0ba8d846088e500f13.wasm", + "tx_update_vp.wasm": "tx_update_vp.a78e6961e28110f9a0f7a301fa9d02a085f5a7ede8c8624314c2e221c8c3da3b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.fbf26f7f0c3d054f62149cf2fd0a3e3a46305e6e2238e74c7dc9f18667a70514.wasm", + "tx_withdraw.wasm": "tx_withdraw.b32b769f3e83f07cae2fd79d08cf247548466400c90fce941ed23d7e9fb7b756.wasm", + "vp_nft.wasm": "vp_nft.1068a5909913d1cd499b8d06f9d42d0953fc5bdef0cb71130f4fbf1416632744.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.b154abffa686f8fda4cd6954e6eebda3150bf06a93947254ad90d0a935b9320a.wasm", + "vp_token.wasm": "vp_token.b6840a748ce2ebce2b52b0e04bd7be7031d86d38de4f20414b38b7d8bc322143.wasm", + "vp_user.wasm": "vp_user.2a21ae6bfb02eda543f915371d8aeb3fff4a0185ed7be7ba1921e103a8f43097.wasm" } \ No newline at end of file From 4e09ed4ef521e611c451e118ff29a319e700a065 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Nov 2022 15:35:58 +0000 Subject: [PATCH 1391/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f22b117b80..3792159394 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", + "tx_bond.wasm": "tx_bond.2a1d5e5cddae6d94446c7ef0098b4d2502d6d2247135ca7fa627c4b1e5b60f1a.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", - "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", - "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", - "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", - "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", - "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", - "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", - "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", - "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", - "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", - "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" + "tx_ibc.wasm": "tx_ibc.27c5761db88a2781aa276758c8e5383e7340382f5b19b56bc3a769917a2f025b.wasm", + "tx_init_account.wasm": "tx_init_account.79bdc333cf0fe97c9659ba9993b41ca80b774dd87bdad58a7b84e2f36b378bb4.wasm", + "tx_init_nft.wasm": "tx_init_nft.cb7148f0447ae4f40678e4cebb456f00364a005c8f55f7d41ea0b5004cf81563.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.090b73504023be24566ada3a8732daae561f459931b0d95520e4b9422ac636e1.wasm", + "tx_init_validator.wasm": "tx_init_validator.f1a06234a549af1d701e714f8faf1efb9ae75ce9adcb9256d8aaabaafe2b527c.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.35010fda84a02570a05cf5fb2013c9b53eee6e50acb73f5ad0fd470639fd938a.wasm", + "tx_transfer.wasm": "tx_transfer.27a654b4095ff1de26bd03b49547cd0b87ddd1a1e3b53d899904adf94d20aeec.wasm", + "tx_unbond.wasm": "tx_unbond.2b9dff596923083d72e06a913773817f739b722d23bc31f3918050e0ef2e9ec9.wasm", + "tx_update_vp.wasm": "tx_update_vp.387008935eaadcbe3b25e258a645e982626f80b251240a7799a63cbb004f00e7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8de2db529ce33a7966de831a8f1c293087decc5dac62c6e21747c06efe99454d.wasm", + "tx_withdraw.wasm": "tx_withdraw.47505c2dd02dcec907bbbff0e916f08bc8c89b35866e09b9fd86753fd310d96b.wasm", + "vp_nft.wasm": "vp_nft.44cfe7d16436d5189992cfe48a13380ecb3528d681c4b854350be6817f273481.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7ece6eeeb91d99008aa68e34b3c2c856e18afe365b144623e8097e18e3966205.wasm", + "vp_token.wasm": "vp_token.b42e8fc5482aa28a260fc04b144f4913755182c6b053e63d7bdb876821b52e4f.wasm", + "vp_user.wasm": "vp_user.39f92d177dd36bd79bfea9fae0524ee77ecc7747f2850660b7cb8bf63a213efb.wasm" } \ No newline at end of file From fa57069887855850b48994c41c7e411788a95587 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Nov 2022 15:36:06 +0000 Subject: [PATCH 1392/1995] [ci] wasm checksums update --- wasm/checksums.json | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index f22b117b80..b6b674e08a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,19 @@ { - "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", + "tx_bond.wasm": "tx_bond.f94daa197a68f33f7afee70eeb5e20fadb03f19aa78f9f5ebaf89dca4bcbbdae.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", - "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", - "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", - "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", - "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", - "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", - "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", - "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", - "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", - "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", - "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" + "tx_ibc.wasm": "tx_ibc.eccc650e774e6e0ef31c932f5eb1f09d73dbcac0aa256df373aa7a607328c84a.wasm", + "tx_init_account.wasm": "tx_init_account.abd3c56af3badb5e7ebb00f9e33af0eff8b23619d5c11e4eca441db388397a98.wasm", + "tx_init_nft.wasm": "tx_init_nft.6dd436e32f06a2ec2941b2c184dcee6a0fccabe52f6494ba04336f1e9d8e23f8.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4ebb2101f4509b318de5e46960ebe6403eae038d3fee0991aca48026a87b380d.wasm", + "tx_init_validator.wasm": "tx_init_validator.524aad0c4c3cbcaf5b550da4f9a7d4eef058b715fb7642e723cd58407dc89a0c.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f0419c463915abafd23017007510aec91898fed92da7ac7c3be887fdc2a8f26a.wasm", + "tx_transfer.wasm": "tx_transfer.505a0e098815b8492d3732468ea2d51e2164c284d0cebecd04f1c85f4d32d691.wasm", + "tx_unbond.wasm": "tx_unbond.610a140bc7f3e435eee8cc8f840a6bdb0dd3294ee8b3bfb17a3ad36d82cf4762.wasm", + "tx_update_vp.wasm": "tx_update_vp.fe3b5eb185b7e80ac56ddd8f9be021bb4a9e556297ee4700d570029445d0563d.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8def2a9cd774bdb1163e5938a2d4e794b59847c7ffa85c80d80ce43407e4dbe2.wasm", + "tx_withdraw.wasm": "tx_withdraw.89f0351171222376c6612419d3329734bd20e4dd93e0e95f3ed888d8ba4e5857.wasm", + "vp_nft.wasm": "vp_nft.c52535be2bd677f6860e1563b89ae2fbeaa17bbad12132c70090cca21e17a401.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.788a5ad30f84743af3942ca13b1d80d301cc41d882065e3d34c8f4352bd9d68d.wasm", + "vp_token.wasm": "vp_token.c5ff7bd6f4e6bd71af98fe3f631254699d3950ba5d7193e6c2afc05d14c6b8f7.wasm", + "vp_user.wasm": "vp_user.a99ab87320db4b1d4c109dd354668ad75e9336c9161ce29f1e0b8543e898e88a.wasm" } \ No newline at end of file From 440923e328852a396a99125c7b584b50b301ad39 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 16:51:35 +0000 Subject: [PATCH 1393/1995] Remove linked issues from comments The related issues were #599 and #600, which are fixed on the branch leading up to this commit. --- apps/src/lib/node/ledger/shell/queries.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3115a83eb5..ace8198ca9 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -728,16 +728,6 @@ mod test_queries { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as /// expected. #[test] - // TODO: we should fix this test to cope with epoch changes only - // happening at the first block of a new epoch. an erroneous change - // was introduced to the ledger, that updated the epoch correctly - // at the first block of the new epoch, but recorded `height + 1` - // instead of the actual height of the epoch change. since this - // test depended on that erroneous logic to pass, it's busted. - // - // linked issues: - // - - // - fn test_can_send_validator_set_update() { let (mut shell, _recv, _) = test_utils::setup_at_height(0u64); From 50d54d8657377044552aa167b3e926d9b5b9320b Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 3 Nov 2022 10:39:17 +0100 Subject: [PATCH 1394/1995] [feat]: Added tests for the eth bridge vp checking that escrowing nam works --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 3 + shared/src/ledger/eth_bridge/parameters.rs | 37 --- shared/src/ledger/eth_bridge/vp/mod.rs | 210 +++++++++++++++++- 3 files changed, 212 insertions(+), 38 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 03dbfd34b6..f2461c043c 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -1037,6 +1037,7 @@ mod test_bridge_pool_vp { keys_changed.insert( wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), ); + let verifiers = BTreeSet::default(); // create the data to be given to the vp let vp = BridgePoolVp { @@ -1139,6 +1140,7 @@ mod test_bridge_pool_vp { &verifiers, ), }; + let to_sign = transfer.try_to_vec().expect("Test failed"); let sig = common::SigScheme::sign(&bertha_keypair(), &to_sign); let signed = SignedTxData { @@ -1219,6 +1221,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); let verifiers = BTreeSet::default(); + // create the data to be given to the vp let vp = BridgePoolVp { ctx: setup_ctx( diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index 46ff52c0a3..f16292223b 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,40 +158,3 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } - -#[cfg(test)] -mod tests { - use eyre::Result; - - use crate::ledger::eth_bridge::parameters::{ - ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, - UpgradeableContract, - }; - use crate::types::ethereum_events::EthAddress; - - /// Ensure we can serialize and deserialize a [`Config`] struct to and from - /// TOML. This can fail if complex fields are ordered before simple fields - /// in any of the config structs. - #[test] - fn test_round_trip_toml_serde() -> Result<()> { - let config = EthereumBridgeConfig { - min_confirmations: MinimumConfirmations::default(), - contracts: Contracts { - native_erc20: EthAddress([42; 20]), - bridge: UpgradeableContract { - address: EthAddress([23; 20]), - version: ContractVersion::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: ContractVersion::default(), - }, - }, - }; - let serialized = toml::to_string(&config)?; - let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; - - assert_eq!(config, deserialized); - Ok(()) - } -} diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 4cc7b85f50..3618e0e74b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -240,7 +240,8 @@ fn extract_valid_keys_changed( /// amount, and that the changes balance each other out. If the balance changes /// are invalid, the reason is logged and a `None` is returned. Otherwise, /// return the `Address` of the owner of the balance which is decreasing, -/// as by how much it decreased, which should be authorizing the balance change. +/// and by how much it decreased, which should be authorizing the balance +/// change. pub(super) fn check_balance_changes( reader: impl StorageReader, key_a: wrapped_erc20s::Key, @@ -392,10 +393,28 @@ fn calculate_delta(balance_pre: i128, balance_post: i128) -> Result { #[cfg(test)] mod tests { + use std::default::Default; + use std::env::temp_dir; + use rand::Rng; use super::*; + use crate::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; + use crate::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; + use crate::ledger::gas::VpGasMeter; + use crate::ledger::storage::mockdb::MockDB; + use crate::ledger::storage::traits::Sha256Hasher; + use crate::ledger::storage::write_log::WriteLog; + use crate::ledger::storage::Storage; + use crate::proto::Tx; + use crate::types::address::wnam; + use crate::types::chain::ChainId; use crate::types::ethereum_events; + use crate::types::ethereum_events::EthAddress; + use crate::vm::wasm::VpCache; + use crate::vm::WasmCacheRwAccess; const ARBITRARY_OWNER_A_ADDRESS: &str = "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; @@ -411,6 +430,65 @@ mod tests { .expect("should always be able to construct this key") } + /// Initialize some dummy storage for testing + fn setup_storage() -> Storage { + let mut storage = Storage::::open( + std::path::Path::new(""), + ChainId::default(), + None, + ); + + // setup a user with a balance + let balance_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + storage + .write( + &balance_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // a dummy config for testing + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + config.init_storage(&mut storage); + storage + } + + /// Setup a ctx for running native vps + fn setup_ctx<'a>( + tx: &'a Tx, + storage: &'a Storage, + write_log: &'a WriteLog, + keys_changed: &'a BTreeSet, + verifiers: &'a BTreeSet
, + ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { + Ctx::new( + &super::super::ADDRESS, + storage, + write_log, + tx, + VpGasMeter::new(0u64), + keys_changed, + verifiers, + VpCache::new(temp_dir(), 100usize), + ) + } + #[test] fn test_error_if_triggered_without_keys_changed() { let keys_changed = BTreeSet::new(); @@ -528,4 +606,134 @@ mod tests { assert_matches!(result, Ok(None)); } } + + /// Test that escrowing Nam is accepted. + #[test] + fn test_escrow_nam_accepted() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog, &keys_changed, &verifiers), + }; + + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(res.expect("Test failed")); + } + + /// Test that escrowing must increase the balance + #[test] + fn test_escrowed_nam_must_increase() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // do not credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog, &keys_changed, &verifiers), + }; + + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(!res.expect("Test failed")); + } + + /// Test that the VP checks that the bridge pool vp will + /// be triggered if escrowing occurs. + #[test] + fn test_escrowing_must_trigger_bridge_pool_vp() { + let mut writelog = WriteLog::default(); + let storage = setup_storage(); + // debit the user's balance + let account_key = balance_key( + &xan(), + &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), + ); + writelog + .write( + &account_key, + Amount::from(0).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + // credit the balance to the escrow + let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + writelog + .write( + &escrow_key, + Amount::from(100).try_to_vec().expect("Test failed"), + ) + .expect("Test failed"); + + let keys_changed = BTreeSet::from([account_key, escrow_key]); + let verifiers = BTreeSet::from([]); + + // set up the VP + let tx = Tx::new(vec![], None); + let vp = EthBridge { + ctx: setup_ctx(&tx, &storage, &writelog, &keys_changed, &verifiers), + }; + + let res = vp.validate_tx( + &tx.try_to_vec().expect("Test failed"), + &keys_changed, + &verifiers, + ); + assert!(!res.expect("Test failed")); + } } From 00c28b0856af4b32312215f318b1f361c9682dc5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Nov 2022 09:48:55 +0000 Subject: [PATCH 1395/1995] 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 04cd993f91..1001503727 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.270e089c71433a61cf2da72417aaf1e0bb21b2a34f0d2dae29c70b21c0133caa.wasm", + "tx_bond.wasm": "tx_bond.f8753f056e76cb691d438ddfca027d0287455ab6bead474bf266b6f9bbc7997a.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.08919fb46124efd7116214c5ea65c3b4130f0eab3eac17849c3807eb21198f7d.wasm", - "tx_init_account.wasm": "tx_init_account.ec26f77dbdd4a40c65c22c1d88ada359c06d262b6b4833f2e7cdf8574e934e4d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.e1bbf5715597be42e6bcf8a633de49a5b61b4797cdd599551b9de4df17fad3af.wasm", - "tx_init_validator.wasm": "tx_init_validator.0388157980a96fd7d76072182fe21a978e1ba913db657202412b93c8614d8a10.wasm", - "tx_transfer.wasm": "tx_transfer.b6f60e5a944309ccc640fe5db81a4c9fe24b213bf2cd82cce82799287f112018.wasm", - "tx_unbond.wasm": "tx_unbond.4acb715528fe067791d433ce133226b9af44a199b7289cd5df34cbf6e3b9c5d1.wasm", - "tx_update_vp.wasm": "tx_update_vp.b4100e61f9960c56eac90db86cfc6200927dabe499ac43cea284605020117b20.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.29d1c9f2c4a9b9cd9a8cc0c9b8f99cad1eeea0b3a21945e25250729bbc028c80.wasm", - "tx_withdraw.wasm": "tx_withdraw.a7b18837c92156a11328193df4206bcef9e9fc7860cdc7ed11deb588e233417d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6642fff3ad3b7754b24a99ae649ded7bb8926d6523c26c78fd0e8f354cbb7046.wasm", - "vp_token.wasm": "vp_token.0e88ed7ff14ddcc02e450b06af2693482451a15e647d75a462dc5e42ac69b19b.wasm", - "vp_user.wasm": "vp_user.acc77f94e833d5ff70ac2f85aed9a39b70d877f28172ff7040bb7a79727b3012.wasm" + "tx_ibc.wasm": "tx_ibc.48d5a00f93ccc8e5d8a2813d3026740ada13c47762fd2aaac4e83e595c7096bd.wasm", + "tx_init_account.wasm": "tx_init_account.ef797f2dc06c8b5a0ce1b313b315b4a5485e842f050f6aeeff03bf4066d451f9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.09216d39eac6183af70899f0f593c59d8cf39d351382ad7cabbaacc337bff3da.wasm", + "tx_init_validator.wasm": "tx_init_validator.30cfdd052967aac2e41cddd4b3f59be23f024bf1ef9b26158746ff605d3f57e5.wasm", + "tx_transfer.wasm": "tx_transfer.dcc33623a491a1d7da05c8589f233b6a8ec83e0f85ab97a39320eb13a62befc0.wasm", + "tx_unbond.wasm": "tx_unbond.dc2c9cd27c0de7af08aee6e0f2ed29372c3f796d995b485f549a48fe735564f6.wasm", + "tx_update_vp.wasm": "tx_update_vp.f7eeb6c3704db11c837f54998bcc1956dd32fdccd10006d43a37a75c6fd8de13.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f6ca9903fc27dcd2dd52c8f28da5a22d9d19fa2b4ca097592e41dbc4b337b73e.wasm", + "tx_withdraw.wasm": "tx_withdraw.f882754a921ac904841c65a60202577f142e0f402bd9c2f777f1812004c8cf52.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.896c2b3a425ecaa9d0e5e6e4bec8232271ce732e28a3a7c96852f130c4c67908.wasm", + "vp_token.wasm": "vp_token.7678ce67c2040aed8a67e614df3f9cb22b1aa0e224fbc2a1480eb30c7b2fec64.wasm", + "vp_user.wasm": "vp_user.0893b5f6b294d93d357d021a1fca5f182279b00b961aaf6ea4d1e003f3ebc372.wasm" } \ No newline at end of file From 8926a0f7462a6d08208ed9e80bfb93e3b28803f6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 13:15:36 +0000 Subject: [PATCH 1396/1995] Code review suggestions * Remove the `EventLog` from `Storage`, and add it back to the `Shell`. * Pass a reference to the `EventLog` to `RequestCtx`. * Return `Option` instead of `Vec` from accepted and applied RPC calls. --- apps/src/lib/client/rpc.rs | 9 ++-- .../lib/node/ledger/shell/finalize_block.rs | 5 +- apps/src/lib/node/ledger/shell/mod.rs | 17 +++++++ apps/src/lib/node/ledger/shell/queries.rs | 1 + shared/src/ledger/queries/mod.rs | 6 +++ shared/src/ledger/queries/router.rs | 1 + shared/src/ledger/queries/shell.rs | 24 +++++++--- shared/src/ledger/queries/types.rs | 13 +++-- shared/src/ledger/storage/mod.rs | 48 ------------------- 9 files changed, 56 insertions(+), 68 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0a4e883a57..a8ecd5b8e5 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -76,7 +76,7 @@ pub async fn query_tx_status( loop { tracing::debug!(query = ?status, "Querying tx status"); - let mut events = match query_tx_events(&client, status).await { + let maybe_event = match query_tx_events(&client, status).await { Ok(response) => response, Err(err) => { tracing::debug!(%err, "ABCI query failed"); @@ -84,8 +84,7 @@ pub async fn query_tx_status( continue; } }; - if let Some(e) = events.pop() { - // we should only have one event matching the query + if let Some(e) = maybe_event { break Ok(e); } sleep_update(status, &mut backoff).await; @@ -1433,10 +1432,12 @@ impl<'a> From> for Query { } } +/// Call the corresponding `tx_event_query` RPC method, to fetch +/// the current status of a transation. pub async fn query_tx_events( client: &HttpClient, tx_event_query: TxEventQuery<'_>, -) -> eyre::Result> { +) -> eyre::Result> { let tx_hash: Hash = tx_event_query.tx_hash().try_into()?; match tx_event_query { TxEventQuery::Accepted(_) => RPC diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 577fa430f4..a02c9b7559 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,7 +1,6 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use namada::ledger::protocol; -use namada::ledger::storage::EventLogExt; use namada::types::storage::{BlockHash, Header}; use namada::types::transaction::protocol::ProtocolTxType; @@ -256,9 +255,7 @@ where .finalize_transaction() .map_err(|_| Error::GasOverflow)?; - self.storage - .event_log_mut() - .log_events(response.events.clone()); + self.event_log_mut().log_events(response.events.clone()); Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 5ab3e9ce5d..745afa86f9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -21,6 +21,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ @@ -333,6 +334,8 @@ where pub(super) tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, + /// Log of events emitted by `FinalizeBlock` ABCI calls. + event_log: EventLog, } impl Shell @@ -447,9 +450,23 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), + // TODO: config event log params + event_log: EventLog::default(), } } + /// Return a reference to the [`EventLog`]. + #[inline] + pub fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + pub fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } + /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 3f854b2a3d..2a8e4d1669 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -57,6 +57,7 @@ where pub fn query(&self, query: request::Query) -> response::Query { let ctx = RequestCtx { storage: &self.storage, + event_log: self.event_log(), vp_wasm_cache: self.vp_wasm_cache.read_only(), tx_wasm_cache: self.tx_wasm_cache.read_only(), }; diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 6a4f31bf57..495ced876c 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -148,6 +148,7 @@ mod testing { use tempfile::TempDir; use super::*; + use crate::ledger::events::log::EventLog; use crate::ledger::storage::testing::TestStorage; use crate::types::storage::BlockHeight; use crate::vm::wasm::{self, TxCache, VpCache}; @@ -162,6 +163,8 @@ mod testing { pub rpc: RPC, /// storage pub storage: TestStorage, + /// event log + pub event_log: EventLog, /// VP wasm compilation cache pub vp_wasm_cache: VpCache, /// tx wasm compilation cache @@ -181,6 +184,7 @@ mod testing { pub fn new(rpc: RPC) -> Self { // Initialize the `TestClient` let storage = TestStorage::default(); + let event_log = EventLog::default(); let (vp_wasm_cache, vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); let (tx_wasm_cache, tx_cache_dir) = @@ -188,6 +192,7 @@ mod testing { Self { rpc, storage, + event_log, vp_wasm_cache: vp_wasm_cache.read_only(), tx_wasm_cache: tx_wasm_cache.read_only(), vp_cache_dir, @@ -222,6 +227,7 @@ mod testing { }; let ctx = RequestCtx { storage: &self.storage, + event_log: &self.event_log, vp_wasm_cache: self.vp_wasm_cache.clone(), tx_wasm_cache: self.tx_wasm_cache.clone(), }; diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index e4823e5ad7..9ff33247f9 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -1008,6 +1008,7 @@ mod test { ..RequestQuery::default() }; let ctx = RequestCtx { + event_log: &client.event_log, storage: &client.storage, vp_wasm_cache: client.vp_wasm_cache.clone(), tx_wasm_cache: client.tx_wasm_cache.clone(), diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index cbe1638b25..ec1db2e95f 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -6,7 +6,7 @@ use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, EventLogExt, DB}; +use crate::ledger::storage::{DBIter, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; use crate::types::hash::Hash; use crate::types::storage::{self, Epoch, PrefixValue}; @@ -34,10 +34,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash] ) -> Option = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash] ) -> Vec = applied, + ( "applied" / [tx_hash: Hash] ) -> Option = applied, } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -227,25 +227,35 @@ where fn accepted( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); - Ok(ctx.storage.query_event_log(matcher)) + Ok(ctx + .event_log + .iter_with_matcher(matcher) + .by_ref() + .next() + .cloned()) } fn applied( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let matcher = dumb_queries::QueryMatcher::applied(tx_hash); - Ok(ctx.storage.query_event_log(matcher)) + Ok(ctx + .event_log + .iter_with_matcher(matcher) + .by_ref() + .next() + .cloned()) } #[cfg(test)] diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index e2092b4f6e..679cc4413a 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,5 +1,6 @@ use tendermint_proto::crypto::ProofOps; +use crate::ledger::events::log::EventLog; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::ledger::storage_api; @@ -12,17 +13,19 @@ use crate::vm::WasmCacheRoAccess; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] -pub struct RequestCtx<'a, D, H> +pub struct RequestCtx<'shell, D, H> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - /// Storage access - pub storage: &'a Storage, - /// VP WASM compilation cache + /// Reference to the ledger's [`Storage`]. + pub storage: &'shell Storage, + /// Log of events emitted by `FinalizeBlock` ABCI calls. + pub event_log: &'shell EventLog, + /// Cache of VP wasm compiled artifacts. #[cfg(feature = "wasm-runtime")] pub vp_wasm_cache: VpCache, - /// tx WASM compilation cache + /// Cache of transaction wasm compiled artifacts. #[cfg(feature = "wasm-runtime")] pub tx_wasm_cache: TxCache, } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index ffaff8b347..5ee2f57ffd 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -13,8 +13,6 @@ use std::array; use thiserror::Error; -use super::events::log::{dumb_queries, EventLog}; -use super::events::Event; use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; @@ -71,8 +69,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Log of events emitted by `FinalizeBlock`. - event_log: EventLog, } /// The block storage data @@ -274,48 +270,6 @@ pub trait DBWriteBatch { fn delete>(&mut self, key: K); } -/// Methods for querying and mutating an event log. -pub trait EventLogExt { - /// Query events in the event log matching the given query. - fn query_event_log( - &self, - matcher: dumb_queries::QueryMatcher, - ) -> Vec; - /// Return a reference to the [`EventLog`]. - fn event_log(&self) -> &EventLog; - /// Return a mutable reference to the [`EventLog`]. - fn event_log_mut(&mut self) -> &mut EventLog; -} - -impl EventLogExt for Storage -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - /// Query events in the event log matching the given query. - fn query_event_log( - &self, - matcher: dumb_queries::QueryMatcher, - ) -> Vec { - self.event_log() - .iter_with_matcher(matcher) - .cloned() - .collect::>() - } - - /// Return a reference to the [`EventLog`]. - #[inline] - fn event_log(&self) -> &EventLog { - &self.event_log - } - - /// Return a mutable reference to the [`EventLog`]. - #[inline] - fn event_log_mut(&mut self) -> &mut EventLog { - &mut self.event_log - } -} - impl Storage where D: DB + for<'iter> DBIter<'iter>, @@ -348,7 +302,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - event_log: EventLog::default(), } } @@ -969,7 +922,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - event_log: EventLog::default(), } } } From bc7f6db44caf6788db3eb18dec0e145a26594b72 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 13:48:09 +0000 Subject: [PATCH 1397/1995] Small fixes --- apps/src/lib/client/rpc.rs | 1 + shared/src/ledger/queries/shell.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index a8ecd5b8e5..5de87f073d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1458,6 +1458,7 @@ pub async fn query_tx_events( } /// Lookup the full response accompanying the specified transaction event +// TODO: maybe remove this in favor of `query_tx_status` pub async fn query_tx_response( ledger_address: &TendermintAddress, tx_query: TxEventQuery<'_>, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index ec1db2e95f..d1ec15a2a7 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -58,10 +58,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash]) -> Option = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash]) -> Vec = applied, + ( "applied" / [tx_hash: Hash]) -> Option = applied, } // Handlers: From 4e508c2225163bb83953af2a4c650e906dd07b80 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 11:34:29 +0100 Subject: [PATCH 1398/1995] Begin handling of decided valset upd vext protocol txs --- apps/src/lib/node/ledger/protocol/transactions/mod.rs | 3 +++ .../ledger/protocol/transactions/validator_set_update/mod.rs | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs diff --git a/apps/src/lib/node/ledger/protocol/transactions/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/mod.rs index 64d2ab220f..3b1851cf7f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/mod.rs @@ -12,6 +12,9 @@ use namada::types::storage; #[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; +#[cfg(not(feature = "abcipp"))] +pub(super) mod validator_set_update; + #[cfg(not(feature = "abcipp"))] mod votes; diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs new file mode 100644 index 0000000000..db0e60f83a --- /dev/null +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -0,0 +1,3 @@ +//! Code for handling +//! [`namada::types::transaction::protocol::ProtocolTxType::ValidatorSetUpdate`] +//! transactions. From 0213472c8fbe752a37bbc124e8504bd224832c00 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 11:47:35 +0100 Subject: [PATCH 1399/1995] WIP: Apply valset upd protocol tx --- apps/src/lib/node/ledger/protocol/mod.rs | 25 +++++++++++++------ .../transactions/validator_set_update/mod.rs | 14 +++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e7b85b0f6c..b757e7d07b 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -218,14 +218,23 @@ where H: 'static + StorageHasher + Sync, { match tx { - ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { - events, - .. - }) => self::transactions::ethereum_events::apply_derived_tx( - storage, events, - ) - .map_err(Error::ProtocolTxError), - ProtocolTxType::ValidatorSetUpdate(_) => Ok(TxResult::default()), + ProtocolTxType::EthereumEvents(ext) => { + let ethereum_events::VextDigest { events, .. } = ext; + self::transactions::ethereum_events::apply_derived_tx( + storage, events, + ) + .map_err(Error::ProtocolTxError) + } + ProtocolTxType::ValidatorSetUpdate(ext) => { + // NOTE(feature = "abcipp"): we will not need to apply any + // storage changes when we rollback to ABCI++; we could emit + // some kind of event, notifying a relayer process of a newly + // available validator set update, though + self::transactions::validator_set_update::aggregate_votes( + storage, ext, + ) + .map_err(Error::ProtocolTxError) + } _ => { tracing::error!( "Attempt made to apply an unsupported protocol transaction! - \ diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index db0e60f83a..fdd219cdd7 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -1,3 +1,17 @@ //! Code for handling //! [`namada::types::transaction::protocol::ProtocolTxType::ValidatorSetUpdate`] //! transactions. + +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, Storage, DB}; + +pub(crate) fn aggregate_votes( + _storage: &mut Storage, + _ext: validator_set_update::VextDigest, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + todo!() +} From 792df404c8bb8fb93c19e12b9a5672c151633fcd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 14:20:46 +0100 Subject: [PATCH 1400/1995] Improve apply protocol tx comment --- apps/src/lib/node/ledger/protocol/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index b757e7d07b..bb4a1038ac 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -227,9 +227,17 @@ where } ProtocolTxType::ValidatorSetUpdate(ext) => { // NOTE(feature = "abcipp"): we will not need to apply any - // storage changes when we rollback to ABCI++; we could emit - // some kind of event, notifying a relayer process of a newly - // available validator set update, though + // storage changes when we rollback to ABCI++; this is because + // the decided vote extension digest should have >2/3 of the + // voting power already, which is the whole reason why we + // have to apply state updates with `abciplus` - we need + // to aggregate votes consisting of >2/3 of the voting power + // on a validator set update. + // + // we could, however, emit some kind of event, notifying a + // relayer process of a newly available validator set update; + // for this, we need to receive a mutable reference to the + // event log, in `apply_protocol_tx()` self::transactions::validator_set_update::aggregate_votes( storage, ext, ) From 10a6ac7ec02548982eddc938f9c1e01f4e773ae5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 14:23:42 +0100 Subject: [PATCH 1401/1995] Fix deps --- .../ledger/protocol/transactions/validator_set_update/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index fdd219cdd7..f92d8e37aa 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -2,8 +2,11 @@ //! [`namada::types::transaction::protocol::ProtocolTxType::ValidatorSetUpdate`] //! transactions. +use eyre::Result; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; +use namada::types::transaction::TxResult; +use namada::types::vote_extensions::validator_set_update; pub(crate) fn aggregate_votes( _storage: &mut Storage, From 70688e9263c996a455447205149e557ee9e58cc1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 15:32:09 +0100 Subject: [PATCH 1402/1995] Add storage sub-key segment conversion from an Epoch --- shared/src/types/storage.rs | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 5ced875453..b525828ff0 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -688,6 +688,32 @@ impl KeySeg for BlockHeight { } } +impl KeySeg for Epoch { + fn parse(string: String) -> Result { + string + .split_once('=') + .and_then(|(prefix, epoch)| (prefix == "E").then(|| epoch)) + .ok_or_else(|| Error::Temporary { + error: format!("Invalid epoch prefix on key: {string}"), + }) + .and_then(|epoch| { + epoch.parse::().map_err(|e| Error::Temporary { + error: format!("Unexpected epoch value {epoch}, {e}"), + }) + }) + .map(Epoch) + } + + fn raw(&self) -> String { + let &Epoch(epoch) = self; + format!("E={epoch}") + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + impl KeySeg for Address { fn parse(mut seg: String) -> Result { match seg.chars().next() { @@ -1068,6 +1094,19 @@ mod tests { let key = Key::from(addr.to_db_key()).push(&s).expect("cannnot push the segment"); assert_eq!(key.segments[1].raw(), s); } + + /// Test roundtrip parsing of key segments derived from [`Epoch`] + /// values. + #[test] + fn test_parse_epoch_key_segment(e in 0..=u64::MAX) { + let original_epoch = Epoch(e); + let key_seg = match original_epoch.to_db_key() { + DbKeySeg::StringSeg(s) => s, + _ => panic!("Test failed"), + }; + let parsed_epoch: Epoch = KeySeg::parse(key_seg).expect("Test failed"); + assert_eq!(original_epoch, parsed_epoch); + } } #[test] From 9a006229daa9fd13ce4448719fc4722b8f98ae64 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 25 Oct 2022 15:33:35 +0100 Subject: [PATCH 1403/1995] Add validator set update key segments --- .../ledger/eth_bridge/storage/vote_tallies.rs | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs index 629eba066f..75bfe44969 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs @@ -1,11 +1,18 @@ //! Functionality for accessing keys to do with tallying votes + use crate::types::ethereum_events::EthereumEvent; use crate::types::hash::Hash; -use crate::types::storage::Key; +use crate::types::storage::{Epoch, Key}; +use crate::types::vote_extensions::validator_set_update; -#[allow(missing_docs)] +/// Storage sub-key space reserved to keeping track of the +/// voting power assigned to Ethereum events. pub const ETH_MSGS_PREFIX_KEY_SEGMENT: &str = "eth_msgs"; +/// Storage sub-key space reserved to keeping track of the +/// voting power assigned to validator set updates. +pub const VALSET_UPDS_PREFIX_KEY_SEGMENT: &str = "validator_set_updates"; + const BODY_KEY_SEGMENT: &str = "body"; const SEEN_KEY_SEGMENT: &str = "seen"; const SEEN_BY_KEY_SEGMENT: &str = "seen_by"; @@ -68,8 +75,8 @@ impl IntoIterator for &Keys { } } -/// Get the key prefix corresponding to where details of seen [`EthereumEvent`]s -/// are stored +/// Get the key prefix corresponding to the storage location of +/// [`EthereumEvent`]s whose "seen" state is being tracked. pub fn eth_msgs_prefix() -> Key { super::prefix() .push(Ð_MSGS_PREFIX_KEY_SEGMENT.to_owned()) @@ -98,6 +105,26 @@ impl From<&Hash> for Keys { } } +/// Get the key prefix corresponding to the storage location of validator set +/// updates whose "seen" state is being tracked. +pub fn valset_upds_prefix() -> Key { + super::prefix() + .push(&VALSET_UPDS_PREFIX_KEY_SEGMENT.to_owned()) + .expect("should always be able to construct this key") +} + +impl From<&Epoch> for Keys { + fn from(epoch: &Epoch) -> Self { + let prefix = valset_upds_prefix() + .push(epoch) + .expect("should always be able to construct this key"); + Keys { + prefix, + _phantom: std::marker::PhantomData, + } + } +} + #[cfg(test)] mod test { use super::*; From 33da34f71e7bc5619a6cdbef679981bca70c2156 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 12:35:33 +0100 Subject: [PATCH 1404/1995] Do not crash the ledger when we handle a valset upd protocol tx --- .../ledger/protocol/transactions/validator_set_update/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index f92d8e37aa..2030224168 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -16,5 +16,6 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - todo!() + tracing::warn!("Called aggregate_votes() with no side effects"); + Ok(TxResult::default()) } From aa167ee3267309271f2e149fad673b2bb1d6d897 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 12:38:05 +0100 Subject: [PATCH 1405/1995] Store voting powers map instead of digest --- shared/src/ledger/eth_bridge/storage/vote_tallies.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs index 75bfe44969..c16672ccb7 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs @@ -3,7 +3,7 @@ use crate::types::ethereum_events::EthereumEvent; use crate::types::hash::Hash; use crate::types::storage::{Epoch, Key}; -use crate::types::vote_extensions::validator_set_update; +use crate::types::vote_extensions::validator_set_update::VotingPowersMap; /// Storage sub-key space reserved to keeping track of the /// voting power assigned to Ethereum events. @@ -113,7 +113,7 @@ pub fn valset_upds_prefix() -> Key { .expect("should always be able to construct this key") } -impl From<&Epoch> for Keys { +impl From<&Epoch> for Keys { fn from(epoch: &Epoch) -> Self { let prefix = valset_upds_prefix() .push(epoch) From a9dbb0ca45c77cf25b4dd7fac9c69ac03556f07d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 09:30:52 +0100 Subject: [PATCH 1406/1995] Fetch active voters from a valset upd --- .../src/lib/node/ledger/protocol/transactions/utils.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index 68f6b8a08e..0691424bab 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -8,6 +8,7 @@ use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada::types::vote_extensions::validator_set_update; use namada::types::voting_power::FractionalVotingPower; use crate::node::ledger::shell::queries::QueriesExt; @@ -42,6 +43,15 @@ pub(super) fn get_votes_for_events<'a>( }) } +/// Extract all the voters and the block heights at which they voted from the +/// given validator set update. +#[inline] +pub(super) fn get_votes_for_valset_upd( + ext: &validator_set_update::VextDigest, +) -> HashSet<(Address, BlockHeight)> { + ext.signatures.keys().cloned().collect() +} + /// Gets the voting power of `selected` from `all_active`. Errors if a /// `selected` validator is not found in `all_active`. pub(super) fn get_voting_powers_for_selected( From 5947bf57ef744c9fd062881c65cbcfc45c485adb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 10:32:38 +0100 Subject: [PATCH 1407/1995] WIP: Aggregating votes --- .../ledger/protocol/transactions/utils.rs | 38 +++++++++++++++++++ .../transactions/validator_set_update/mod.rs | 23 +++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index 0691424bab..977b26533f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -13,6 +13,44 @@ use namada::types::voting_power::FractionalVotingPower; use crate::node::ledger::shell::queries::QueriesExt; +pub(super) trait GetVoters { + fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; +} + +/// Constructs a map of validators and the block height at which they signed +/// a validator set update to their respective voting power at the given height. +pub(super) fn get_voting_powers( + storage: &Storage, + proof: P, +) -> Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + P: GetVoters, +{ + let voters = utils::get_votes_for_valset_upd(ext); + tracing::debug!(?voters, "Got validators who voted on at least one event"); + + let active_validators = utils::get_active_validators( + storage, + voters.iter().map(|(_, h)| h.to_owned()).collect(), + ); + tracing::debug!( + n = active_validators.len(), + ?active_validators, + "Got active validators in valset upd vote aggregation" + ); + + let voting_powers = + utils::get_voting_powers_for_selected(&active_validators, voters)?; + tracing::debug!( + ?voting_powers, + "Got voting powers for relevant validators" + ); + + Ok(voting_powers) +} + pub(super) fn get_active_validators( storage: &Storage, block_heights: HashSet, diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index 2030224168..c24a6b1cd3 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -8,14 +8,29 @@ use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::transaction::TxResult; use namada::types::vote_extensions::validator_set_update; +use super::ChangedKeys; +use crate::node::ledger::protocol::transactions::utils; + +impl utils::GetVoters for validator_set_update::VextDigest { + #[inline] + fn get_voters(&self) -> HashSet<(Address, BlockHeight)> { + self.signatures.keys().cloned().collect() + } +} + pub(crate) fn aggregate_votes( - _storage: &mut Storage, - _ext: validator_set_update::VextDigest, + storage: &mut Storage, + ext: validator_set_update::VextDigest, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - tracing::warn!("Called aggregate_votes() with no side effects"); - Ok(TxResult::default()) + tracing::info!( + num_votes = ext.signatures.len(), + "Aggregating new votes for validator set update" + ); + + let voting_powers = utils::get_voting_powers(storage, &ext)?; + let changed_keys = apply_updates(storage, updates, voting_powers)?; } From 2157676bdba6ca1678d176554dc48d7bf4f02d92 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 11:18:03 +0100 Subject: [PATCH 1408/1995] Refactoring get voters --- .../transactions/ethereum_events/mod.rs | 109 ++++++++++++------ .../ledger/protocol/transactions/utils.rs | 99 ++-------------- .../transactions/validator_set_update/mod.rs | 24 +++- 3 files changed, 104 insertions(+), 128 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs index e5e5317113..5e0351161f 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/ethereum_events/mod.rs @@ -18,13 +18,21 @@ use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada::types::voting_power::FractionalVotingPower; use super::ChangedKeys; -use crate::node::ledger::protocol::transactions::utils::{ - self, get_active_validators, -}; +use crate::node::ledger::protocol::transactions::utils; use crate::node::ledger::protocol::transactions::votes::{ calculate_new, calculate_updated, write, Votes, }; +impl utils::GetVoters for [MultiSignedEthEvent] { + #[inline] + fn get_voters(&self) -> HashSet<(Address, BlockHeight)> { + self.iter().fold(HashSet::new(), |mut voters, event| { + voters.extend(event.signers.iter().cloned()); + voters + }) + } +} + /// Applies derived state changes to storage, based on Ethereum `events` which /// were newly seen by some active validator(s) in the last epoch. For `events` /// which have been seen by enough voting power, extra state changes may take @@ -49,7 +57,7 @@ where protocol transaction" ); - let voting_powers = get_voting_powers(storage, &events)?; + let voting_powers = utils::get_voting_powers(storage, events.as_slice())?; let updates = events.into_iter().map(Into::::into).collect(); @@ -61,39 +69,6 @@ where }) } -/// Constructs a map of all validators who voted for an event to their -/// fractional voting power for block heights at which they voted for an event -fn get_voting_powers( - storage: &Storage, - events: &[MultiSignedEthEvent], -) -> Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - let voters = utils::get_votes_for_events(events.iter()); - tracing::debug!(?voters, "Got validators who voted on at least one event"); - - let active_validators = get_active_validators( - storage, - voters.iter().map(|(_, h)| h.to_owned()).collect(), - ); - tracing::debug!( - n = active_validators.len(), - "got active validators - {:#?}", - active_validators, - ); - - let voting_powers = - utils::get_voting_powers_for_selected(&active_validators, voters)?; - tracing::debug!( - ?voting_powers, - "got voting powers for relevant validators" - ); - - Ok(voting_powers) -} - /// Apply an Ethereum state update + act on any events which are confirmed pub(super) fn apply_updates( storage: &mut Storage, @@ -225,12 +200,13 @@ mod tests { use namada::types::address; use namada::types::ethereum_events::testing::{ arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, - DAI_ERC20_ETH_ADDRESS, + arbitrary_single_transfer, DAI_ERC20_ETH_ADDRESS, }; use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada::types::token::Amount; use super::*; + use crate::node::ledger::protocol::transactions::utils::GetVoters; #[test] /// Test applying a `TransfersToNamada` batch containing a single transfer @@ -439,4 +415,61 @@ mod tests { voting power so far" ); } + + #[test] + /// Assert we don't return anything if we try to get the votes for an empty + /// vec of events + pub fn test_get_votes_for_events_empty() { + let events = vec![]; + assert!(events.as_slice().get_voters().is_empty()); + } + + #[test] + /// Test that we correctly get the votes from a vec of events + pub fn test_get_votes_for_events() { + let events = vec![ + MultiSignedEthEvent { + event: arbitrary_single_transfer( + 1.into(), + address::testing::established_address_1(), + ), + signers: BTreeSet::from([ + ( + address::testing::established_address_1(), + BlockHeight(100), + ), + ( + address::testing::established_address_2(), + BlockHeight(102), + ), + ]), + }, + MultiSignedEthEvent { + event: arbitrary_single_transfer( + 2.into(), + address::testing::established_address_2(), + ), + signers: BTreeSet::from([ + ( + address::testing::established_address_1(), + BlockHeight(101), + ), + ( + address::testing::established_address_3(), + BlockHeight(100), + ), + ]), + }, + ]; + let voters = events.as_slice().get_voters(); + assert_eq!( + voters, + HashSet::from_iter(vec![ + (address::testing::established_address_1(), BlockHeight(100)), + (address::testing::established_address_1(), BlockHeight(101)), + (address::testing::established_address_2(), BlockHeight(102)), + (address::testing::established_address_3(), BlockHeight(100)) + ]) + ) + } } diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index 977b26533f..873c70349b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -7,13 +7,14 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::storage::BlockHeight; -use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use namada::types::vote_extensions::validator_set_update; use namada::types::voting_power::FractionalVotingPower; use crate::node::ledger::shell::queries::QueriesExt; +/// Proof of some arbitrary tally whose voters can be queried. pub(super) trait GetVoters { + /// Extract all the voters and the block heights at which they voted from + /// the given proof. fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } @@ -21,17 +22,17 @@ pub(super) trait GetVoters { /// a validator set update to their respective voting power at the given height. pub(super) fn get_voting_powers( storage: &Storage, - proof: P, -) -> Result> + proof: &P, +) -> eyre::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, - P: GetVoters, + P: GetVoters + ?Sized, { - let voters = utils::get_votes_for_valset_upd(ext); + let voters = proof.get_voters(); tracing::debug!(?voters, "Got validators who voted on at least one event"); - let active_validators = utils::get_active_validators( + let active_validators = get_active_validators( storage, voters.iter().map(|(_, h)| h.to_owned()).collect(), ); @@ -42,7 +43,7 @@ where ); let voting_powers = - utils::get_voting_powers_for_selected(&active_validators, voters)?; + get_voting_powers_for_selected(&active_validators, voters)?; tracing::debug!( ?voting_powers, "Got voting powers for relevant validators" @@ -70,26 +71,6 @@ where active_validators } -/// Extract all the voters and the block heights at which they voted from the -/// given events. -pub(super) fn get_votes_for_events<'a>( - events: impl Iterator, -) -> HashSet<(Address, BlockHeight)> { - events.fold(HashSet::new(), |mut validators, event| { - validators.extend(event.signers.iter().cloned()); - validators - }) -} - -/// Extract all the voters and the block heights at which they voted from the -/// given validator set update. -#[inline] -pub(super) fn get_votes_for_valset_upd( - ext: &validator_set_update::VextDigest, -) -> HashSet<(Address, BlockHeight)> { - ext.signatures.keys().cloned().collect() -} - /// Gets the voting power of `selected` from `all_active`. Errors if a /// `selected` validator is not found in `all_active`. pub(super) fn get_voting_powers_for_selected( @@ -165,9 +146,7 @@ mod tests { use assert_matches::assert_matches; use namada::types::address; - use namada::types::ethereum_events::testing::{ - arbitrary_single_transfer, arbitrary_voting_power, - }; + use namada::types::ethereum_events::testing::arbitrary_voting_power; use super::*; @@ -333,62 +312,4 @@ mod tests { assert_eq!(total, VotingPower::from(300)); } - - #[test] - /// Assert we don't return anything if we try to get the votes for an empty - /// vec of events - pub fn test_get_votes_for_events_empty() { - let events = vec![]; - let votes = get_votes_for_events(events.iter()); - assert!(votes.is_empty()); - } - - #[test] - /// Test that we correctly get the votes from a vec of events - pub fn test_get_votes_for_events() { - let events = vec![ - MultiSignedEthEvent { - event: arbitrary_single_transfer( - 1.into(), - address::testing::established_address_1(), - ), - signers: BTreeSet::from([ - ( - address::testing::established_address_1(), - BlockHeight(100), - ), - ( - address::testing::established_address_2(), - BlockHeight(102), - ), - ]), - }, - MultiSignedEthEvent { - event: arbitrary_single_transfer( - 2.into(), - address::testing::established_address_2(), - ), - signers: BTreeSet::from([ - ( - address::testing::established_address_1(), - BlockHeight(101), - ), - ( - address::testing::established_address_3(), - BlockHeight(100), - ), - ]), - }, - ]; - let votes = get_votes_for_events(events.iter()); - assert_eq!( - votes, - HashSet::from_iter(vec![ - (address::testing::established_address_1(), BlockHeight(100)), - (address::testing::established_address_1(), BlockHeight(101)), - (address::testing::established_address_2(), BlockHeight(102)), - (address::testing::established_address_3(), BlockHeight(100)) - ]) - ) - } } diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index c24a6b1cd3..c497a8743e 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -2,11 +2,16 @@ //! [`namada::types::transaction::protocol::ProtocolTxType::ValidatorSetUpdate`] //! transactions. +use std::collections::{HashMap, HashSet}; + use eyre::Result; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; +use namada::types::address::Address; +use namada::types::storage::BlockHeight; use namada::types::transaction::TxResult; use namada::types::vote_extensions::validator_set_update; +use namada::types::voting_power::FractionalVotingPower; use super::ChangedKeys; use crate::node::ledger::protocol::transactions::utils; @@ -32,5 +37,22 @@ where ); let voting_powers = utils::get_voting_powers(storage, &ext)?; - let changed_keys = apply_updates(storage, updates, voting_powers)?; + let changed_keys = apply_updates(storage, ext, voting_powers)?; + + Ok(TxResult { + changed_keys, + ..Default::default() + }) +} + +fn apply_updates( + _storage: &mut Storage, + _ext: validator_set_update::VextDigest, + _voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + todo!() } From 34ac8c4db353a5f4c8f28a041f4ea516970b0dc4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 12:43:54 +0100 Subject: [PATCH 1409/1995] Do not crash the ledger when applying storage updates --- .../ledger/protocol/transactions/validator_set_update/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index c497a8743e..3c56c56cf8 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -54,5 +54,6 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - todo!() + tracing::warn!("Called apply_updates() with no side effects"); + Ok(ChangedKeys::new()) } From ca8e9e7ed8113b24729980806b0e188cf5891e46 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 13:19:46 +0100 Subject: [PATCH 1410/1995] Fixed tracing debug msg --- apps/src/lib/node/ledger/protocol/transactions/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index 873c70349b..a124050b34 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -39,7 +39,7 @@ where tracing::debug!( n = active_validators.len(), ?active_validators, - "Got active validators in valset upd vote aggregation" + "Got active validators" ); let voting_powers = From a3c214ae2d5da2d493b201cf1c63a3bcebaaab5a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 16:52:34 +0100 Subject: [PATCH 1411/1995] Fix up docstr on get_voting_powers() --- apps/src/lib/node/ledger/protocol/transactions/utils.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/utils.rs b/apps/src/lib/node/ledger/protocol/transactions/utils.rs index a124050b34..e3319ea672 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/utils.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/utils.rs @@ -18,8 +18,9 @@ pub(super) trait GetVoters { fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } -/// Constructs a map of validators and the block height at which they signed -/// a validator set update to their respective voting power at the given height. +/// Returns a map whose keys are addresses of validators and the block height at +/// which they signed some arbitrary object, and whose values are the voting +/// powers of these validators at the key's given block height. pub(super) fn get_voting_powers( storage: &Storage, proof: &P, From 8d9340a15ee93aff066e9475b067703dd400866d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 26 Oct 2022 15:54:12 +0100 Subject: [PATCH 1412/1995] Fetch storage key for a given valset upd vext --- .../transactions/validator_set_update/mod.rs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index 3c56c56cf8..a85b9432fd 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -5,6 +5,7 @@ use std::collections::{HashMap, HashSet}; use eyre::Result; +use namada::ledger::eth_bridge::storage::vote_tallies; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; @@ -15,6 +16,7 @@ use namada::types::voting_power::FractionalVotingPower; use super::ChangedKeys; use crate::node::ledger::protocol::transactions::utils; +use crate::node::ledger::shell::queries::QueriesExt; impl utils::GetVoters for validator_set_update::VextDigest { #[inline] @@ -31,13 +33,18 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { + if ext.signatures.is_empty() { + tracing::debug!("Ignoring empty validator set update"); + return Ok(Default::default()); + } + tracing::info!( num_votes = ext.signatures.len(), "Aggregating new votes for validator set update" ); let voting_powers = utils::get_voting_powers(storage, &ext)?; - let changed_keys = apply_updates(storage, ext, voting_powers)?; + let changed_keys = apply_update(storage, ext, voting_powers)?; Ok(TxResult { changed_keys, @@ -45,15 +52,39 @@ where }) } -fn apply_updates( - _storage: &mut Storage, - _ext: validator_set_update::VextDigest, +fn apply_update( + storage: &mut Storage, + ext: validator_set_update::VextDigest, _voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - tracing::warn!("Called apply_updates() with no side effects"); + let epoch = { + // all votes we gathered are for the same epoch, so + // we can just fetch the block height from the first + // signature we iterate over, and calculate its cor- + // responding epoch + let height = ext + .signatures + .keys() + .map(|(_, height)| *height) + .by_ref() + .next() + .expect( + "We have at least one signature present in this validator set \ + update vote extension digest", + ); + + storage + .get_epoch(height) + .expect("The epoch of the given block height should be known") + }; + + let valset_upd_keys = vote_tallies::Keys::from(&epoch); + let _ = valset_upd_keys; + + tracing::warn!("Called apply_update() with no side effects"); Ok(ChangedKeys::new()) } From 2ea2f8bedcbd65c5b5e2ec636ebb96253c9f06ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 27 Oct 2022 10:39:49 +0100 Subject: [PATCH 1413/1995] WIP: Applying valset upd state changes in storage --- .../transactions/validator_set_update/mod.rs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index a85b9432fd..b6137e9272 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -16,6 +16,7 @@ use namada::types::voting_power::FractionalVotingPower; use super::ChangedKeys; use crate::node::ledger::protocol::transactions::utils; +use crate::node::ledger::protocol::transactions::votes::{self, Votes}; use crate::node::ledger::shell::queries::QueriesExt; impl utils::GetVoters for validator_set_update::VextDigest { @@ -55,7 +56,7 @@ where fn apply_update( storage: &mut Storage, ext: validator_set_update::VextDigest, - _voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -83,8 +84,35 @@ where }; let valset_upd_keys = vote_tallies::Keys::from(&epoch); - let _ = valset_upd_keys; + let (exists_in_storage, _) = storage.has_key(&valset_upd_keys.seen())?; - tracing::warn!("Called apply_update() with no side effects"); - Ok(ChangedKeys::new()) + let mut seen_by = Votes::default(); + for (address, block_height) in ext.signatures.into_keys() { + // TODO: more deterministic deduplication + if let Some(present) = seen_by.insert(address, block_height) { + tracing::warn!(?present, "Duplicate vote in digest"); + } + } + + let (tally, changed) = if !exists_in_storage { + tracing::debug!( + %valset_upd_keys.prefix, + ?ext.voting_powers, + "New validator set update vote aggregation started" + ); + let tally = votes::calculate_new(seen_by, &voting_powers)?; + let changed = valset_upd_keys.into_iter().collect(); + (tally, changed) + } else { + todo!() + }; + + tracing::debug!( + ?tally, + ?ext.voting_powers, + "Applying validator set update state changes" + ); + votes::write(storage, &valset_upd_keys, &ext.voting_powers, &tally)?; + + Ok(changed) } From fe468f30f2f96028c9452ae07bd6cefbd5a9da1e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 14:37:40 +0000 Subject: [PATCH 1414/1995] Calculate updated valset upd votes --- .../transactions/validator_set_update/mod.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index b6137e9272..454007e33b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -104,7 +104,27 @@ where let changed = valset_upd_keys.into_iter().collect(); (tally, changed) } else { - todo!() + tracing::debug!( + %valset_upd_keys.prefix, + "Validator set update votes already in storage", + ); + let mut votes = HashMap::default(); + seen_by.into_iter().for_each(|(address, block_height)| { + let fract_voting_power = voting_powers + .get(&(address.clone(), block_height)) + .unwrap(); + if let Some(already_present_fract_voting_power) = + votes.insert(address.clone(), fract_voting_power.to_owned()) + { + tracing::warn!( + ?address, + ?already_present_fract_voting_power, + new_fract_voting_power = ?fract_voting_power, + "Validator voted more than once, arbitrarily using later value", + ) + } + }); + votes::calculate_updated(storage, &valset_upd_keys, &votes)? }; tracing::debug!( From 1f00799a490e11989c6716cdd070a6ba716986c2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 14:40:33 +0000 Subject: [PATCH 1415/1995] Small changes --- .../transactions/validator_set_update/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index 454007e33b..fe57ff277b 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -1,6 +1,4 @@ -//! Code for handling -//! [`namada::types::transaction::protocol::ProtocolTxType::ValidatorSetUpdate`] -//! transactions. +//! Code for handling [`ProtocolTxType::ValidatorSetUpdate`] protocol txs. use std::collections::{HashMap, HashSet}; @@ -10,6 +8,8 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, Storage, DB}; use namada::types::address::Address; use namada::types::storage::BlockHeight; +#[allow(unused_imports)] +use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::TxResult; use namada::types::vote_extensions::validator_set_update; use namada::types::voting_power::FractionalVotingPower; @@ -110,9 +110,8 @@ where ); let mut votes = HashMap::default(); seen_by.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = voting_powers - .get(&(address.clone(), block_height)) - .unwrap(); + let fract_voting_power = + voting_powers.get(&(address.clone(), block_height)).unwrap(); if let Some(already_present_fract_voting_power) = votes.insert(address.clone(), fract_voting_power.to_owned()) { @@ -120,7 +119,8 @@ where ?address, ?already_present_fract_voting_power, new_fract_voting_power = ?fract_voting_power, - "Validator voted more than once, arbitrarily using later value", + "Validator voted more than once on validator set update, \ + arbitrarily using later value" ) } }); From 4c0ea430b23c13a91bd0ff1bf41161084b633072 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 14:44:50 +0000 Subject: [PATCH 1416/1995] Log confirmed validator set update --- .../transactions/validator_set_update/mod.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs index fe57ff277b..6d8dc34dec 100644 --- a/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/apps/src/lib/node/ledger/protocol/transactions/validator_set_update/mod.rs @@ -94,7 +94,7 @@ where } } - let (tally, changed) = if !exists_in_storage { + let (tally, changed, confirmed) = if !exists_in_storage { tracing::debug!( %valset_upd_keys.prefix, ?ext.voting_powers, @@ -102,7 +102,8 @@ where ); let tally = votes::calculate_new(seen_by, &voting_powers)?; let changed = valset_upd_keys.into_iter().collect(); - (tally, changed) + let confirmed = tally.seen; + (tally, changed, confirmed) } else { tracing::debug!( %valset_upd_keys.prefix, @@ -124,7 +125,10 @@ where ) } }); - votes::calculate_updated(storage, &valset_upd_keys, &votes)? + let (tally, changed) = + votes::calculate_updated(storage, &valset_upd_keys, &votes)?; + let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); + (tally, changed, confirmed) }; tracing::debug!( @@ -134,5 +138,12 @@ where ); votes::write(storage, &valset_upd_keys, &ext.voting_powers, &tally)?; + if confirmed { + tracing::debug!( + %valset_upd_keys.prefix, + "Acquired complete proof on validator set update" + ); + } + Ok(changed) } From de529b8a5c8b61b4a11e0bea21df1c43bd26f3a4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 3 Nov 2022 22:12:31 +0000 Subject: [PATCH 1417/1995] "implement" MockDB::read_subspace_val_with_height --- shared/src/ledger/storage/mockdb.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 2215072f6f..ea8ff17609 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -334,12 +334,15 @@ impl DB for MockDB { fn read_subspace_val_with_height( &self, - _key: &Key, + key: &Key, _height: BlockHeight, _last_height: BlockHeight, ) -> Result>> { - // Mock DB can read only the latest value for now - unimplemented!() + tracing::warn!( + "read_subspace_val_with_height is not implemented, will read \ + subspace value from latest height" + ); + self.read_subspace_val(key) } fn write_subspace_val( From b193c405650046f986b36c295db0967b870b7943 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 4 Nov 2022 09:33:40 +0000 Subject: [PATCH 1418/1995] Fix up the latest merge --- apps/src/lib/node/ledger/shell/queries.rs | 10 +++---- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 2 +- .../transactions/ethereum_events/eth_msgs.rs | 7 +---- .../transactions/ethereum_events/mod.rs | 26 ------------------- .../src/ledger/protocol/transactions/mod.rs | 2 +- .../src/ledger/protocol/transactions/votes.rs | 3 +-- 6 files changed, 8 insertions(+), 42 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 2a93b374bf..cddac1e277 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -12,8 +12,8 @@ use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; use namada::ledger::queries::{RequestCtx, ResponseQuery}; -use namada::ledger::storage_api; use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; +use namada::ledger::storage_api; use namada::types::address::Address; use namada::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -23,8 +23,7 @@ use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::MembershipProof::BridgePool; -use namada::types::storage::{Epoch, Key, MerkleValue, PrefixValue}; -use namada::types::storage::Epoch; +use namada::types::storage::{Epoch, MerkleValue}; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -32,7 +31,6 @@ use super::*; use crate::facade::tendermint_proto::google::protobuf; use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::response; -use crate::node::ledger::rpc::BridgePoolSubpath; #[derive(Error, Debug)] pub enum Error { @@ -170,7 +168,7 @@ where request_bytes: Vec, ) -> response::Query { if let Ok(transfers) = - >::try_from_slice(request_bytes.as_slice()) + >::try_from_slice(request_bytes.as_slice()) { // get the latest signed merkle root of the Ethereum bridge pool let signed_root: MultiSignedMerkleRoot = match self @@ -223,7 +221,7 @@ where // TODO: Use real nonce nonce: 0.into(), } - .encode(), + .encode(), ..Default::default() }, Err(e) => response::Query { diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6f93861afa..ca19040a98 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -358,7 +358,7 @@ mod test_bridge_pool_vp { // get the balance keys let token_key = wrapped_erc20s::Keys::from(&ASSET).balance(&balance.owner); - let account_key = balance_key(&xan(), &balance.owner); + let account_key = balance_key(&nam(), &balance.owner); // update the balance of xan let new_balance = match gas_delta { diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 17a6c0075e..1c5d93838a 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,13 +1,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use namada::types::ethereum_events::EthereumEvent; -use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::ledger::protocol::transactions::votes::Tally; -use crate::types::address::Address; +use crate::ledger::protocol::transactions::votes::{Tally, Votes}; use crate::types::ethereum_events::EthereumEvent; -use crate::types::storage::BlockHeight; use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::node::ledger::protocol::transactions::votes::{Tally, Votes}; /// Represents an Ethereum event being seen by some validators #[derive( diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 97ed902719..db955406f8 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -8,24 +8,12 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; -use namada::ledger::eth_bridge::storage::vote_tallies; -use namada::ledger::storage::traits::StorageHasher; -use namada::ledger::storage::{DBIter, Storage, DB}; -use namada::types::address::Address; -use namada::types::storage::BlockHeight; -use namada::types::transaction::TxResult; -use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use namada::types::voting_power::FractionalVotingPower; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{ -use super::ChangedKeys; -use crate::node::ledger::protocol::transactions::utils::{ self, get_active_validators, }; use crate::ledger::protocol::transactions::votes::{ - calculate_new, calculate_updated, write, -use crate::node::ledger::protocol::transactions::votes::{ calculate_new, calculate_updated, write, Votes, }; use crate::ledger::storage::traits::StorageHasher; @@ -230,20 +218,6 @@ mod tests { use borsh::BorshDeserialize; use storage::BlockHeight; - use namada::ledger::eth_bridge::storage::wrapped_erc20s; - use namada::ledger::pos::namada_proof_of_stake::epoched::Epoched; - use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::pos::types::{ValidatorSet, WeightedValidator}; - use namada::ledger::storage::mockdb::MockDB; - use namada::ledger::storage::testing::TestStorage; - use namada::ledger::storage::traits::Sha256Hasher; - use namada::types::address; - use namada::types::ethereum_events::testing::{ - arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, - DAI_ERC20_ETH_ADDRESS, - }; - use namada::types::ethereum_events::{EthereumEvent, TransferToNamada}; - use namada::types::token::Amount; use super::*; use crate::ledger::eth_bridge::storage::wrapped_erc20s; diff --git a/shared/src/ledger/protocol/transactions/mod.rs b/shared/src/ledger/protocol/transactions/mod.rs index 64d2ab220f..79eaa84dda 100644 --- a/shared/src/ledger/protocol/transactions/mod.rs +++ b/shared/src/ledger/protocol/transactions/mod.rs @@ -7,7 +7,7 @@ use std::collections::BTreeSet; -use namada::types::storage; +use crate::types::storage; #[cfg(not(feature = "abcipp"))] pub(super) mod ethereum_events; diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 763fb1c764..c9fc1b41f2 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -6,6 +6,7 @@ use std::collections::{BTreeMap, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; +use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::read; use crate::ledger::storage::traits::StorageHasher; @@ -13,8 +14,6 @@ use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; -use super::ChangedKeys; -use crate::node::ledger::protocol::transactions::read; /// The addresses of validators that voted for something, and the block /// heights at which they voted. We use a [`BTreeMap`] to enforce that a From 74be8b419e15354d959dbf058e3d930b8863efd1 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 4 Nov 2022 10:49:41 +0100 Subject: [PATCH 1419/1995] [feat]: Refactored eth bridge vp to be explicit in its dual functionality --- shared/src/ledger/eth_bridge/storage/mod.rs | 10 +++- shared/src/ledger/eth_bridge/vp/mod.rs | 63 ++++++++++++--------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 595f403034..734c12366f 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -4,16 +4,24 @@ pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; +use crate::types::address::xan; use crate::types::storage::{Key, KeySeg}; +use crate::types::token::balance_key; /// Key prefix for the storage subspace pub fn prefix() -> Key { Key::from(ADDRESS.to_db_key()) } +/// The key to the escrow of the VP. +pub fn escrow_key() -> Key { + balance_key(&xan(), &ADDRESS) +} + /// Returns whether a key belongs to this account or not pub fn is_eth_bridge_key(key: &Key) -> bool { - matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) + key == &escrow_key() + || matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) } #[cfg(test)] diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 34d09e03c1..b78423a6da 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -8,7 +8,7 @@ use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; -use crate::ledger::eth_bridge::storage::{self, wrapped_erc20s}; +use crate::ledger::eth_bridge::storage::{self, escrow_key, wrapped_erc20s}; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::traits::StorageHasher; @@ -58,10 +58,14 @@ where }; let escrow_post: Amount = if let Ok(Some(bytes)) = self.ctx.read_bytes_post(&escrow_key) { - BorshDeserialize::try_from_slice(bytes.as_slice()).expect( - "Deserializing the balance of the Ethereum bridge VP from \ - storage shouldn't fail", - ) + BorshDeserialize::try_from_slice(bytes.as_slice()).map_err( + |_| { + Error(eyre!( + "Couldn't deserialize the balance of the Ethereum \ + bridge VP from storage." + )) + }, + )? } else { tracing::debug!( "Could not retrieve the modified Ethereum bridge VP's \ @@ -83,6 +87,14 @@ where } } +/// One of the the two types of checks +/// this VP must perform. +#[derive(Debug)] +enum CheckType { + Escrow, + Erc20Transfer(wrapped_erc20s::Key, wrapped_erc20s::Key), +} + #[derive(thiserror::Error, Debug)] #[error(transparent)] /// Generic error that may be returned by the validity predicate @@ -125,13 +137,9 @@ where "Ethereum Bridge VP triggered", ); - // first check if Nam is being escrowed - if keys_changed.contains(&balance_key(&xan(), &super::ADDRESS)) { - return self.check_escrow(verifiers); - } - - let (key_a, key_b) = match extract_valid_keys_changed(keys_changed)? { - Some((key_a, key_b)) => (key_a, key_b), + let (key_a, key_b) = match determine_check_type(keys_changed)? { + Some(CheckType::Erc20Transfer(key_a, key_b)) => (key_a, key_b), + Some(CheckType::Escrow) => return self.check_escrow(verifiers), None => return Ok(false), }; let (sender, _) = match check_balance_changes(&self.ctx, key_a, key_b)? @@ -146,9 +154,9 @@ where /// If `keys_changed` represents a valid set of changed keys, return them, /// otherwise return `None`. -fn extract_valid_keys_changed( +fn determine_check_type( keys_changed: &BTreeSet, -) -> Result, Error> { +) -> Result, Error> { // we aren't concerned with keys that changed outside of our account let keys_changed: HashSet<_> = keys_changed .iter() @@ -164,8 +172,9 @@ fn extract_valid_keys_changed( relevant_keys.len = keys_changed.len(), "Found keys changed under our account" ); - - if keys_changed.len() != 2 { + if keys_changed.len() == 1 && keys_changed.contains(&escrow_key()) { + return Ok(Some(CheckType::Escrow)); + } else if keys_changed.len() != 2 { tracing::debug!( relevant_keys.len = keys_changed.len(), "Rejecting transaction as only two keys should have changed" @@ -210,7 +219,7 @@ fn extract_valid_keys_changed( ); return Ok(None); } - Ok(Some((key_a, key_b))) + Ok(Some(CheckType::Erc20Transfer(key_a, key_b))) } /// Checks that the balances at both `key_a` and `key_b` have changed by some @@ -393,7 +402,7 @@ mod tests { fn test_error_if_triggered_without_keys_changed() { let keys_changed = BTreeSet::new(); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert!(result.is_err()); } @@ -401,20 +410,20 @@ mod tests { #[test] fn test_rejects_if_not_two_keys_changed() { { - let keys_changed = BTreeSet::from_iter(vec![arbitrary_key()]); + let keys_changed = BTreeSet::from_iter(vec![arbitrary_key(); 3]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } { let keys_changed = BTreeSet::from_iter(vec![ - arbitrary_key(), + escrow_key(), arbitrary_key(), arbitrary_key(), ]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } @@ -426,7 +435,7 @@ mod tests { let keys_changed = BTreeSet::from_iter(vec![arbitrary_key(), arbitrary_key()]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } @@ -440,7 +449,7 @@ mod tests { .supply(), ]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } @@ -457,7 +466,7 @@ mod tests { ), ]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } @@ -483,7 +492,7 @@ mod tests { ), ]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } @@ -501,7 +510,7 @@ mod tests { ), ]); - let result = extract_valid_keys_changed(&keys_changed); + let result = determine_check_type(&keys_changed); assert_matches!(result, Ok(None)); } From 4be687c171f33a3fe0ac087cc3d715297d91c9af Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 4 Nov 2022 11:37:50 +0100 Subject: [PATCH 1420/1995] [feat]: Refactored the corner case check on minting wNam to be more readable. Added overflow checks on adds. --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 139 +++++++++++------- shared/src/types/token.rs | 7 + 2 files changed, 94 insertions(+), 52 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 6c02d60a60..f39a60ec4d 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -83,13 +83,13 @@ where /// Check that the correct amount of Nam was sent /// from the correct account into escrow - fn check_nam_escrowed( - &self, - payer_account: &Address, - escrow_account: &Address, - expected_debit: Amount, - expected_credit: Amount, - ) -> Result { + fn check_nam_escrowed(&self, delta: EscrowDelta) -> Result { + let EscrowDelta { + payer_account, + escrow_account, + expected_debit, + expected_credit, + } = delta; let debited = self.account_balance_delta(payer_account); let credited = self.account_balance_delta(escrow_account); @@ -126,6 +126,77 @@ fn check_delta(delta: &(Address, Amount), transfer: &PendingTransfer) -> bool { delta.0 == transfer.transfer.sender && delta.1 == transfer.transfer.amount } +/// Helper struct for handling the different escrow +/// checking scenarios. +struct EscrowDelta<'a> { + payer_account: &'a Address, + escrow_account: &'a Address, + expected_debit: Amount, + expected_credit: Amount, +} + +/// There are two checks we must do when minting wNam. +/// 1. Check that gas fees were escrowed. +/// 2. Check that the Nam to back wNam was escrowed. +struct EscrowCheck<'a> { + gas_check: EscrowDelta<'a>, + token_check: EscrowDelta<'a>, +} + +/// Deteremine the debit and credit amounts that should be checked. +fn escrow_check(transfer: &PendingTransfer) -> Result { + // there is a corner case where the gas fees and escrowed Nam + // are debited from the same address when mint wNam. + Ok( + if transfer.gas_fee.payer == transfer.transfer.sender + && transfer.transfer.asset == wnam() + { + let debit = transfer + .gas_fee + .amount + .checked_add(&transfer.transfer.amount) + .ok_or_else(|| { + Error(eyre!( + "Addition oveflowed adding gas fee + transfer amount." + )) + })?; + EscrowCheck { + gas_check: EscrowDelta { + payer_account: &transfer.gas_fee.payer, + escrow_account: &BRIDGE_POOL_ADDRESS, + expected_debit: debit, + expected_credit: transfer.gas_fee.amount, + }, + token_check: EscrowDelta { + payer_account: &transfer.transfer.sender, + escrow_account: &Address::Internal( + InternalAddress::EthBridge, + ), + expected_debit: debit, + expected_credit: transfer.transfer.amount, + }, + } + } else { + EscrowCheck { + gas_check: EscrowDelta { + payer_account: &transfer.gas_fee.payer, + escrow_account: &BRIDGE_POOL_ADDRESS, + expected_debit: transfer.gas_fee.amount, + expected_credit: transfer.gas_fee.amount, + }, + token_check: EscrowDelta { + payer_account: &transfer.transfer.sender, + escrow_account: &Address::Internal( + InternalAddress::EthBridge, + ), + expected_debit: transfer.transfer.amount, + expected_credit: transfer.transfer.amount, + }, + } + }, + ) +} + impl<'a, D, H, CA> NativeVp for BridgePoolVp<'a, D, H, CA> where D: 'static + DB + for<'iter> DBIter<'iter>, @@ -208,62 +279,26 @@ where ); return Ok(false); } - + // The deltas in the escrowed amounts we must check. + let escrow_checks = escrow_check(&transfer)?; + // check that gas we correctly escrowed. + if !self.check_nam_escrowed(escrow_checks.gas_check)? { + return Ok(false); + } // if we are going to mint wNam on Ethereum, the appropriate // amount of Nam must be escrowed in the Ethereum bridge VP's storage. // TODO: We should look this address up from storage if transfer.transfer.asset == wnam() { // check that correct amount of Nam was put into escrow. - return if transfer.gas_fee.payer == transfer.transfer.sender { - if !self.check_nam_escrowed( - &transfer.gas_fee.payer, - &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount + transfer.transfer.amount, - transfer.gas_fee.amount, - )? || !self.check_nam_escrowed( - &transfer.transfer.sender, - &Address::Internal(InternalAddress::EthBridge), - transfer.gas_fee.amount + transfer.transfer.amount, - transfer.transfer.amount, - )? { - Ok(false) - } else { - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer \ - {:?}.", - transfer - ); - Ok(true) - } - } else if !self.check_nam_escrowed( - &transfer.gas_fee.payer, - &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount, - transfer.gas_fee.amount, - )? || !self.check_nam_escrowed( - &transfer.transfer.sender, - &Address::Internal(InternalAddress::EthBridge), - transfer.transfer.amount, - transfer.transfer.amount, - )? { - Ok(false) - } else { + return if self.check_nam_escrowed(escrow_checks.token_check)? { tracing::info!( "The Ethereum bridge pool VP accepted the transfer {:?}.", transfer ); Ok(true) + } else { + Ok(false) }; - } else { - // check that the correct amount of gas fees were escrowed - if !self.check_nam_escrowed( - &transfer.gas_fee.payer, - &BRIDGE_POOL_ADDRESS, - transfer.gas_fee.amount, - transfer.gas_fee.amount, - )? { - return Ok(false); - } } // check that the assets to be transferred were escrowed diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..4c58459886 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -82,6 +82,13 @@ impl Amount { micro: change as u64, } } + + /// Checked addition on amounts + pub fn checked_add(&self, amount: &Amount) -> Option { + self.micro + .checked_add(amount.micro) + .map(|micro| Self { micro }) + } } impl serde::Serialize for Amount { From d186926e4363eccaa6c2e5d5aa78675f517ddcf3 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 4 Nov 2022 12:12:14 +0100 Subject: [PATCH 1421/1995] [fix]: Formatting --- shared/src/ledger/eth_bridge/storage/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 0a2aeca99f..33e91826bc 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -17,7 +17,6 @@ pub const BRIDGE_CONTRACT_SUBKEY: &str = "bridge_contract_address"; /// Sub-key for storing the Ethereum address of the governance contract. pub const GOVERNANCE_CONTRACT_SUBKEY: &str = "governance_contract_address"; - /// Key prefix for the storage subspace pub fn prefix() -> Key { Key::from(ADDRESS.to_db_key()) From fbfc10559a3b90b979b9ff4524a66988abd0e4a5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 4 Nov 2022 12:36:52 +0000 Subject: [PATCH 1422/1995] Compiling (but not yet working --- apps/src/lib/node/ledger/shell/queries.rs | 372 +------------------- shared/src/ledger/queries/mod.rs | 5 +- shared/src/ledger/queries/shell.rs | 401 +++++++++++++++++++++- 3 files changed, 403 insertions(+), 375 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index cddac1e277..e84f1699aa 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,28 +2,19 @@ use std::cmp::max; use std::default::Default; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use ferveo_common::TendermintValidator; -use namada::ledger::eth_bridge::storage::bridge_pool::{ - get_key_from_hash, get_pending_key, get_signed_root_key, -}; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; use namada::ledger::pos::PosParams; use namada::ledger::queries::{RequestCtx, ResponseQuery}; -use namada::ledger::storage::{MerkleTree, StoreRef, StoreType}; use namada::ledger::storage_api; use namada::types::address::Address; -use namada::types::eth_bridge_pool::{ - MultiSignedMerkleRoot, PendingTransfer, RelayProof, -}; use namada::types::ethereum_events::EthAddress; -use namada::types::keccak::encode::Encode; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; -use namada::types::storage::MembershipProof::BridgePool; -use namada::types::storage::{Epoch, MerkleValue}; +use namada::types::storage::Epoch; use namada::types::token::{self, Amount}; use namada::types::vote_extensions::validator_set_update::EthAddrBook; @@ -124,123 +115,6 @@ where .expect("Storage read in the protocol must not fail") .unwrap_or_default() } - - /// Read the current contents of the Ethereum bridge - /// pool. - fn read_ethereum_bridge_pool(&self) -> response::Query { - let stores = self - .storage - .db - .read_merkle_tree_stores(self.storage.last_height) - .expect("We should always be able to read the database") - .expect( - "Every signed root should correspond to an existing block \ - height", - ); - let store = match stores.get_store(StoreType::BridgePool) { - StoreRef::BridgePool(store) => store, - _ => unreachable!(), - }; - - let transfers: Vec = store - .iter() - .map(|hash| { - let res = self - .storage - .read(&get_key_from_hash(hash)) - .unwrap() - .0 - .unwrap(); - BorshDeserialize::try_from_slice(res.as_slice()).unwrap() - }) - .collect(); - response::Query { - code: 0, - value: transfers.try_to_vec().unwrap(), - ..Default::default() - } - } - - /// Generate a merkle proof for the inclusion of the - /// requested transfers in the Ethereum bridge pool. - fn generate_bridge_pool_proof( - &self, - request_bytes: Vec, - ) -> response::Query { - if let Ok(transfers) = - >::try_from_slice(request_bytes.as_slice()) - { - // get the latest signed merkle root of the Ethereum bridge pool - let signed_root: MultiSignedMerkleRoot = match self - .storage - .read(&get_signed_root_key()) - .expect("Reading the database should not faile") - { - (Some(bytes), _) => { - BorshDeserialize::try_from_slice(bytes.as_slice()).unwrap() - } - _ => { - return response::Query { - code: 1, - log: "No signed root for the Ethereum bridge pool \ - exists in storage." - .into(), - info: "No signed root for the Ethereum bridge pool \ - exists in storage." - .into(), - ..Default::default() - }; - } - }; - - // get the merkle tree corresponding to the above root. - let tree = MerkleTree::::new( - self.storage - .db - .read_merkle_tree_stores(signed_root.height) - .expect("We should always be able to read the database") - .expect( - "Every signed root should correspond to an existing \ - block height", - ), - ); - - // get the membership proof - let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); - match tree.get_sub_tree_existence_proof( - &keys, - transfers.into_iter().map(MerkleValue::from).collect(), - ) { - Ok(BridgePool(proof)) => response::Query { - code: 0, - value: RelayProof { - // TODO: use actual validators - validator_args: Default::default(), - root: signed_root, - proof, - // TODO: Use real nonce - nonce: 0.into(), - } - .encode(), - ..Default::default() - }, - Err(e) => response::Query { - code: 1, - log: e.to_string(), - info: e.to_string(), - ..Default::default() - }, - _ => unreachable!(), - } - } else { - response::Query { - code: 1, - log: "Could not deserialize transfers".into(), - info: "Could not deserialize transfers".into(), - ..Default::default() - } - } - } } /// API for querying the blockchain state. @@ -632,20 +506,10 @@ pub enum SendValsetUpd { #[cfg(test)] mod test_queries { - use namada::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; - use namada::types::eth_bridge_pool::{GasFee, TransferToEthereum}; - use namada::types::ethereum_events::EthAddress; - use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; - /// An established user address for testing & development - fn bertha_address() -> Address { - Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") - .expect("The token address decoding shouldn't fail") - } - macro_rules! test_can_send_validator_set_update { (epoch_assertions: $epoch_assertions:expr $(,)?) => { /// Test if [`QueriesExt::can_send_validator_set_update`] behaves as @@ -820,236 +684,4 @@ mod test_queries { (2, 28, Err(true)), ], } - - /// Test that reading the bridge pool works - #[test] - fn test_read_bridge_pool() { - let (mut shell, _, _) = test_utils::setup(); - let transfer = PendingTransfer { - transfer: TransferToEthereum { - asset: EthAddress([0; 20]), - recipient: EthAddress([0; 20]), - sender: bertha_address(), - amount: 0.into(), - nonce: 0.into(), - }, - gas_fee: GasFee { - amount: 0.into(), - payer: bertha_address(), - }, - }; - - // write a transfer into the bridge pool - shell - .storage - .write(&get_pending_key(&transfer), transfer.clone()) - .expect("Test failed"); - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // check the response - let resp = shell.read_ethereum_bridge_pool(); - assert_eq!(resp.code, 0); - let pool = - BTreeSet::::try_from_slice(resp.value.as_slice()) - .expect("Test failed"); - assert_eq!(pool, BTreeSet::from([transfer])); - } - - /// Test that reading the bridge pool always gets - /// the latest pool - #[test] - fn test_bridge_pool_updates() { - let (mut shell, _, _) = test_utils::setup(); - let transfer = PendingTransfer { - transfer: TransferToEthereum { - asset: EthAddress([0; 20]), - recipient: EthAddress([0; 20]), - sender: bertha_address(), - amount: 0.into(), - nonce: 0.into(), - }, - gas_fee: GasFee { - amount: 0.into(), - payer: bertha_address(), - }, - }; - - // write a transfer into the bridge pool - shell - .storage - .write(&get_pending_key(&transfer), transfer.clone()) - .expect("Test failed"); - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // update the pool - shell - .storage - .delete(&get_pending_key(&transfer)) - .expect("Test failed"); - let mut transfer2 = transfer; - transfer2.transfer.amount = 1.into(); - shell - .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) - .expect("Test failed"); - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // check the response - let resp = shell.read_ethereum_bridge_pool(); - assert_eq!(resp.code, 0); - let pool = - BTreeSet::::try_from_slice(resp.value.as_slice()) - .expect("Test failed"); - assert_eq!(pool, BTreeSet::from([transfer2])); - } - - /// Test that we can get a merkle proof even if the signed - /// merkle roots is lagging behind the pool - #[test] - fn test_get_merkle_proof() { - let (mut shell, _, _) = test_utils::setup(); - let transfer = PendingTransfer { - transfer: TransferToEthereum { - asset: EthAddress([0; 20]), - recipient: EthAddress([0; 20]), - sender: bertha_address(), - amount: 0.into(), - nonce: 0.into(), - }, - gas_fee: GasFee { - amount: 0.into(), - payer: bertha_address(), - }, - }; - - // write a transfer into the bridge pool - shell - .storage - .write(&get_pending_key(&transfer), transfer.clone()) - .expect("Test failed"); - - // create a signed Merkle root for this pool - let signed_root = MultiSignedMerkleRoot { - sigs: Default::default(), - root: transfer.keccak256(), - height: Default::default(), - }; - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // update the pool - let mut transfer2 = transfer.clone(); - transfer2.transfer.amount = 1.into(); - shell - .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) - .expect("Test failed"); - - // add the signature for the pool at the previous block height - shell - .storage - .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) - .expect("Test failed"); - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - let resp = shell.generate_bridge_pool_proof( - vec![transfer.clone()].try_to_vec().expect("Test failed"), - ); - assert_eq!(resp.code, 0); - - let tree = BridgePoolTree::new( - transfer.keccak256(), - BTreeSet::from([transfer.keccak256()]), - ); - let proof = tree - .get_membership_proof(vec![transfer]) - .expect("Test failed"); - - let proof = RelayProof { - validator_args: Default::default(), - root: signed_root, - proof, - // TODO: Use a real nonce - nonce: 0.into(), - } - .encode(); - assert_eq!(proof, resp.value); - } - - /// Test if the no merkle tree including a transfer - /// has had its root signed, then we cannot generate - /// a proof. - #[test] - fn test_cannot_get_proof() { - let (mut shell, _, _) = test_utils::setup(); - let transfer = PendingTransfer { - transfer: TransferToEthereum { - asset: EthAddress([0; 20]), - recipient: EthAddress([0; 20]), - sender: bertha_address(), - amount: 0.into(), - nonce: 0.into(), - }, - gas_fee: GasFee { - amount: 0.into(), - payer: bertha_address(), - }, - }; - - // write a transfer into the bridge pool - shell - .storage - .write(&get_pending_key(&transfer), transfer.clone()) - .expect("Test failed"); - - // create a signed Merkle root for this pool - let signed_root = MultiSignedMerkleRoot { - sigs: Default::default(), - root: transfer.keccak256(), - height: Default::default(), - }; - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // update the pool - let mut transfer2 = transfer; - transfer2.transfer.amount = 1.into(); - shell - .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) - .expect("Test failed"); - - // add the signature for the pool at the previous block height - shell - .storage - .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) - .expect("Test failed"); - - // commit the changes and increase block height - shell.storage.commit().expect("Test failed"); - shell.storage.block.height = shell.storage.block.height + 1; - - // this is in the pool, but its merkle root has not been signed yet - let resp = shell.generate_bridge_pool_proof( - vec![transfer2].try_to_vec().expect("Test failed"), - ); - // thus proof generation should fail - assert_eq!(resp.code, 1); - } } diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 898dcd716a..12b4428756 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -11,6 +11,7 @@ pub use types::{ use super::storage::traits::StorageHasher; use super::storage::{DBIter, DB}; use super::storage_api; +use crate::types::storage::BlockHeight; #[macro_use] mod router; @@ -48,7 +49,9 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - if request.height != ctx.storage.last_height { + if request.height != BlockHeight(0) + && request.height != ctx.storage.last_height + { return Err(storage_api::Error::new_const( "This query doesn't support arbitrary block heights, only the \ latest committed block height ('0' can be used as a special \ diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 277683ad6a..6d64cef7d9 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -1,15 +1,23 @@ -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use tendermint_proto::crypto::{ProofOp, ProofOps}; +use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_key_from_hash, get_pending_key, get_signed_root_key, +}; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, DB}; +use crate::ledger::storage::{DBIter, MerkleTree, StoreRef, StoreType, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::types::eth_bridge_pool::{ + MultiSignedMerkleRoot, PendingTransfer, RelayProof, +}; use crate::types::hash::Hash; -use crate::types::storage::{self, Epoch, PrefixValue}; +use crate::types::keccak::encode::Encode; +use crate::types::storage::MembershipProof::BridgePool; +use crate::types::storage::{self, Epoch, MerkleValue, PrefixValue}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::transaction::TxResult; @@ -38,6 +46,12 @@ router! {SHELL, // was the transaction applied? ( "applied" / [tx_hash: Hash] ) -> Option = applied, + + // Get the current contents of the Ethereum bridge pool + ( "eth_bridge_pool" / "contents" ) -> Vec = read_ethereum_bridge_pool, + + // Generate a merkle proof for the inclusion of requested transfers in the Ethereum bridge pool + ( "eth_bridge_pool" / "proof" ) -> Vec = (with_options generate_bridge_pool_proof), } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -62,6 +76,12 @@ router! {SHELL, // was the transaction applied? ( "applied" / [tx_hash: Hash]) -> Option = applied, + + // Get the current contents of the Ethereum bridge pool + ( "eth_bridge_pool" / "contents" ) -> Vec = read_ethereum_bridge_pool, + + // Generate a merkle proof for the inclusion of requested transfers in the Ethereum bridge pool + ( "eth_bridge_pool" / "proof" ) -> Vec = (with_options generate_bridge_pool_proof), } // Handlers: @@ -271,18 +291,146 @@ where .cloned()) } +/// Read the current contents of the Ethereum bridge +/// pool. +fn read_ethereum_bridge_pool( + ctx: RequestCtx<'_, D, H>, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let stores = ctx + .storage + .db + .read_merkle_tree_stores(ctx.storage.last_height) + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing block height", + ); + let store = match stores.get_store(StoreType::BridgePool) { + StoreRef::BridgePool(store) => store, + _ => unreachable!(), + }; + + let transfers: Vec = store + .iter() + .map(|hash| { + let res = ctx + .storage + .read(&get_key_from_hash(hash)) + .unwrap() + .0 + .unwrap(); + BorshDeserialize::try_from_slice(res.as_slice()).unwrap() + }) + .collect(); + Ok(transfers) +} + +/// Generate a merkle proof for the inclusion of the +/// requested transfers in the Ethereum bridge pool. +fn generate_bridge_pool_proof( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + if let Ok(transfers) = + >::try_from_slice(request.data.as_slice()) + { + // get the latest signed merkle root of the Ethereum bridge pool + let signed_root: MultiSignedMerkleRoot = match ctx + .storage + .read(&get_signed_root_key()) + .expect("Reading the database should not faile") + { + (Some(bytes), _) => { + BorshDeserialize::try_from_slice(bytes.as_slice()).unwrap() + } + _ => { + return Err(storage_api::Error::SimpleMessage( + "No signed root for the Ethereum bridge pool exists in \ + storage.", + )); + } + }; + + // get the merkle tree corresponding to the above root. + let tree = MerkleTree::::new( + ctx.storage + .db + .read_merkle_tree_stores(signed_root.height) + .expect("We should always be able to read the database") + .expect( + "Every signed root should correspond to an existing block \ + height", + ), + ); + + // get the membership proof + let keys: Vec<_> = transfers.iter().map(get_pending_key).collect(); + match tree.get_sub_tree_existence_proof( + &keys, + transfers.into_iter().map(MerkleValue::from).collect(), + ) { + Ok(BridgePool(proof)) => Ok(EncodedResponseQuery { + // TODO: we are returning ABI encoded data here, but Borsh + // serialized is expected! + data: RelayProof { + // TODO: use actual validators + validator_args: Default::default(), + root: signed_root, + proof, + // TODO: Use real nonce + nonce: 0.into(), + } + .encode(), + proof_ops: None, + info: Default::default(), + }), + Err(e) => Err(storage_api::Error::new(e)), + _ => unreachable!(), + } + } else { + Err(storage_api::Error::SimpleMessage( + "Could not deserialize transfers", + )) + } +} + #[cfg(test)] mod test { - use borsh::BorshDeserialize; + use std::collections::BTreeSet; + use borsh::{BorshDeserialize, BorshSerialize}; + + use crate::ledger::eth_bridge::storage::bridge_pool::{ + get_pending_key, get_signed_root_key, BridgePoolTree, + }; use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::RPC; use crate::ledger::storage_api::{self, StorageWrite}; use crate::proto::Tx; + use crate::types::address::Address; + use crate::types::eth_bridge_pool::{ + GasFee, MultiSignedMerkleRoot, PendingTransfer, RelayProof, + TransferToEthereum, + }; + use crate::types::ethereum_events::EthAddress; + use crate::types::keccak::encode::Encode; use crate::types::{address, token}; const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; + /// An established user address for testing & development + fn bertha_address() -> Address { + Address::decode("atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw") + .expect("The token address decoding shouldn't fail") + } + #[test] fn test_shell_queries_router_paths() { let path = RPC.shell().epoch_path(); @@ -388,4 +536,249 @@ mod test { Ok(()) } + + /// Test that reading the bridge pool works + #[tokio::test] + async fn test_read_bridge_pool() { + let mut client = TestClient::new(RPC); + + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + client + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // check the response + let pool = RPC + .shell() + .read_ethereum_bridge_pool(&client) + .await + .unwrap(); + assert_eq!(pool, Vec::from([transfer])); + } + + /// Test that reading the bridge pool always gets + /// the latest pool + #[tokio::test] + async fn test_bridge_pool_updates() { + let mut client = TestClient::new(RPC); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + client + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // update the pool + client + .storage + .delete(&get_pending_key(&transfer)) + .expect("Test failed"); + let mut transfer2 = transfer; + transfer2.transfer.amount = 1.into(); + client + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // check the response + let pool = RPC + .shell() + .read_ethereum_bridge_pool(&client) + .await + .unwrap(); + assert_eq!(pool, Vec::from([transfer2])); + } + + /// Test that we can get a merkle proof even if the signed + /// merkle roots is lagging behind the pool + #[tokio::test] + async fn test_get_merkle_proof() { + let mut client = TestClient::new(RPC); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + client + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: Default::default(), + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer.clone(); + transfer2.transfer.amount = 1.into(); + client + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + client + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + let resp = RPC + .shell() + .generate_bridge_pool_proof( + &client, + Some(vec![transfer.clone()].try_to_vec().expect("Test failed")), + None, + false, + ) + .await + .unwrap(); + + let tree = BridgePoolTree::new( + transfer.keccak256(), + BTreeSet::from([transfer.keccak256()]), + ); + let proof = tree + .get_membership_proof(vec![transfer]) + .expect("Test failed"); + + let proof = RelayProof { + validator_args: Default::default(), + root: signed_root, + proof, + // TODO: Use a real nonce + nonce: 0.into(), + } + .encode(); + assert_eq!(proof, resp.data); + } + + /// Test if the no merkle tree including a transfer + /// has had its root signed, then we cannot generate + /// a proof. + #[tokio::test] + async fn test_cannot_get_proof() { + let mut client = TestClient::new(RPC); + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: EthAddress([0; 20]), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: 0.into(), + nonce: 0.into(), + }, + gas_fee: GasFee { + amount: 0.into(), + payer: bertha_address(), + }, + }; + + // write a transfer into the bridge pool + client + .storage + .write(&get_pending_key(&transfer), transfer.clone()) + .expect("Test failed"); + + // create a signed Merkle root for this pool + let signed_root = MultiSignedMerkleRoot { + sigs: Default::default(), + root: transfer.keccak256(), + height: Default::default(), + }; + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // update the pool + let mut transfer2 = transfer; + transfer2.transfer.amount = 1.into(); + client + .storage + .write(&get_pending_key(&transfer2), transfer2.clone()) + .expect("Test failed"); + + // add the signature for the pool at the previous block height + client + .storage + .write(&get_signed_root_key(), signed_root.try_to_vec().unwrap()) + .expect("Test failed"); + + // commit the changes and increase block height + client.storage.commit().expect("Test failed"); + client.storage.block.height = client.storage.block.height + 1; + + // this is in the pool, but its merkle root has not been signed yet + let resp = RPC + .shell() + .generate_bridge_pool_proof( + &client, + Some(vec![transfer2].try_to_vec().expect("Test failed")), + None, + false, + ) + .await; + // thus proof generation should fail + assert!(resp.is_err()); + } } From e451ac008cb55fe50f231246aa136e437e238f16 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 4 Nov 2022 14:40:59 +0000 Subject: [PATCH 1423/1995] Add an EncodeCell for Eth ABI encoded values --- shared/src/types/keccak.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index 8f3bbfc505..decb5c6568 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -3,6 +3,7 @@ //! on Ethereum. use std::convert::{TryFrom, TryInto}; use std::fmt::Display; +use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; @@ -103,6 +104,7 @@ pub fn keccak_hash(bytes: &[u8]) -> KeccakHash { /// This module defines encoding methods compatible with Ethereum /// smart contracts. +// TODO: relocate this sub-module to a new home pub mod encode { #[doc(inline)] pub use ethabi::token::Token; @@ -110,6 +112,40 @@ pub mod encode { use super::*; + /// A container for data types that are able to be `encode` + /// or `encodePacked` Ethereum ABI-encoded. + #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] + #[repr(transparent)] + pub struct EncodeCell { + /// ABI-encoded values of type `T`. + encoded_data: Vec, + /// Indicate we do not own values of type `T`. + /// + /// Passing `PhantomData` here would trigger the drop checker, + /// which is not the desired behavior, since we own encoded values + /// of `T`, not values of `T` themselves. + _marker: PhantomData<*const T>, + } + + impl EncodeCell { + /// Return a new ABI encoded value of type `T`. + pub fn new(value: &T) -> Self + where + T: Encode, + { + let encoded_data = value.encode(); + Self { + encoded_data, + _marker: PhantomData, + } + } + + /// Return the underlying ABI encoded value. + pub fn into_inner(self) -> Vec { + self.encoded_data + } + } + /// Contains a method to encode data to a format compatible with Ethereum. pub trait Encode { /// Encodes a struct into a sequence of ABI From 62b940b26fd43783294b11fafcdfe2bde3c32fa0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 4 Nov 2022 14:41:49 +0000 Subject: [PATCH 1424/1995] Use correct encoding in RPC calls --- shared/src/ledger/queries/shell.rs | 38 +++++++++++++++++------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 6d64cef7d9..34d49aa0ac 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -15,7 +15,7 @@ use crate::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; use crate::types::hash::Hash; -use crate::types::keccak::encode::Encode; +use crate::types::keccak::encode::EncodeCell; use crate::types::storage::MembershipProof::BridgePool; use crate::types::storage::{self, Epoch, MerkleValue, PrefixValue}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] @@ -48,10 +48,12 @@ router! {SHELL, ( "applied" / [tx_hash: Hash] ) -> Option = applied, // Get the current contents of the Ethereum bridge pool - ( "eth_bridge_pool" / "contents" ) -> Vec = read_ethereum_bridge_pool, + ( "eth_bridge_pool" / "contents" ) + -> Vec = read_ethereum_bridge_pool, // Generate a merkle proof for the inclusion of requested transfers in the Ethereum bridge pool - ( "eth_bridge_pool" / "proof" ) -> Vec = (with_options generate_bridge_pool_proof), + ( "eth_bridge_pool" / "proof" ) + -> EncodeCell = (with_options generate_bridge_pool_proof), } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -78,10 +80,13 @@ router! {SHELL, ( "applied" / [tx_hash: Hash]) -> Option = applied, // Get the current contents of the Ethereum bridge pool - ( "eth_bridge_pool" / "contents" ) -> Vec = read_ethereum_bridge_pool, + ( "eth_bridge_pool" / "contents" ) + -> Vec = read_ethereum_bridge_pool, - // Generate a merkle proof for the inclusion of requested transfers in the Ethereum bridge pool - ( "eth_bridge_pool" / "proof" ) -> Vec = (with_options generate_bridge_pool_proof), + // Generate a merkle proof for the inclusion of requested + // transfers in the Ethereum bridge pool + ( "eth_bridge_pool" / "proof" ) + -> EncodeCell = (with_options generate_bridge_pool_proof), } // Handlers: @@ -376,21 +381,22 @@ where &keys, transfers.into_iter().map(MerkleValue::from).collect(), ) { - Ok(BridgePool(proof)) => Ok(EncodedResponseQuery { - // TODO: we are returning ABI encoded data here, but Borsh - // serialized is expected! - data: RelayProof { + Ok(BridgePool(proof)) => { + let data = EncodeCell::new(&RelayProof { // TODO: use actual validators validator_args: Default::default(), root: signed_root, proof, // TODO: Use real nonce nonce: 0.into(), - } - .encode(), - proof_ops: None, - info: Default::default(), - }), + }) + .try_to_vec() + .into_storage_result()?; + Ok(EncodedResponseQuery { + data, + ..Default::default() + }) + } Err(e) => Err(storage_api::Error::new(e)), _ => unreachable!(), } @@ -710,7 +716,7 @@ mod test { nonce: 0.into(), } .encode(); - assert_eq!(proof, resp.data); + assert_eq!(proof, resp.data.into_inner()); } /// Test if the no merkle tree including a transfer From 81be1cc75dfbd4c46bdc65af7d8f9660854d3d03 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 4 Nov 2022 14:45:46 +0000 Subject: [PATCH 1425/1995] Fix up docstring --- shared/src/types/keccak.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index decb5c6568..c6a1986226 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -117,13 +117,13 @@ pub mod encode { #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[repr(transparent)] pub struct EncodeCell { - /// ABI-encoded values of type `T`. + /// ABI-encoded value of type `T`. encoded_data: Vec, /// Indicate we do not own values of type `T`. /// /// Passing `PhantomData` here would trigger the drop checker, - /// which is not the desired behavior, since we own encoded values - /// of `T`, not values of `T` themselves. + /// which is not the desired behavior, since we own an encoded value + /// of `T`, not a value of `T` itself. _marker: PhantomData<*const T>, } From 3cd4d7a25b11cd2a84457db56a0f625b24fc42cd Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 4 Nov 2022 15:52:24 +0100 Subject: [PATCH 1426/1995] [feat]: Added tooling for testing wasms and vps. Adjusted the bridge pool tx wasm and added test --- .../eth_bridge/storage/wrapped_erc20s.rs | 11 +- shared/src/types/ethereum_events.rs | 19 ++ tests/src/native_vp/eth_bridge_pool.rs | 186 ++++++++++++++++++ tests/src/native_vp/mod.rs | 1 + tests/src/vm_host_env/tx.rs | 27 ++- tx_prelude/src/lib.rs | 2 + wasm/checksums.json | 36 ++-- wasm/wasm_source/src/lib.rs | 2 + wasm/wasm_source/src/tx_bond.rs | 7 +- wasm/wasm_source/src/tx_bridge_pool.rs | 64 ++++-- wasm/wasm_source/src/tx_unbond.rs | 1 + wasm/wasm_source/src/tx_withdraw.rs | 1 + wasm/wasm_source/src/vp_testnet_faucet.rs | 6 +- wasm/wasm_source/src/vp_user.rs | 8 +- 14 files changed, 328 insertions(+), 43 deletions(-) create mode 100644 tests/src/native_vp/eth_bridge_pool.rs diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index a3f4df81ca..53bd8d2c33 100644 --- a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -5,7 +5,7 @@ use eyre::eyre; use crate::types::address::Address; use crate::types::ethereum_events::EthAddress; -use crate::types::storage::{self, DbKeySeg}; +use crate::types::storage::{self, DbKeySeg, KeySeg}; #[allow(missing_docs)] pub const MULTITOKEN_KEY_SEGMENT: &str = "ERC20"; @@ -35,7 +35,7 @@ impl Keys { self.prefix .push(&BALANCE_KEY_SEGMENT.to_owned()) .expect("should always be able to construct this key") - .push(&format!("#{}", owner.encode())) + .push(&owner.to_db_key()) .expect("should always be able to construct this key") } @@ -58,6 +58,13 @@ impl From<&EthAddress> for Keys { } } +/// Construct a sub-prefix from an ERC20 address. +pub fn sub_prefix(address: &EthAddress) -> storage::Key { + storage::Key::from(MULTITOKEN_KEY_SEGMENT.to_owned().to_db_key()) + .push(&address.to_db_key()) + .expect("should always be able to construct this key") +} + /// Represents the type of a key relating to a wrapped ERC20 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub enum KeyType { diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index fcfc1f54e1..4e23d94fa1 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::keccak::KeccakHash; +use crate::types::storage::{DbKeySeg, KeySeg}; use crate::types::token::Amount; /// Anoma native type to replace the ethabi::Uint type @@ -110,6 +111,24 @@ impl From for String { } } +impl KeySeg for EthAddress { + fn parse(string: String) -> crate::types::storage::Result + where + Self: Sized, + { + Self::from_str(string.as_str()) + .map_err(|_| crate::types::storage::Error::ParseKeySeg(string)) + } + + fn raw(&self) -> String { + self.to_canonical() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } +} + /// An Ethereum event to be processed by the Anoma ledger #[derive( PartialEq, diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs new file mode 100644 index 0000000000..f2045177c8 --- /dev/null +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -0,0 +1,186 @@ +#[cfg(test)] +mod test_bridge_pool_vp { + use std::path::PathBuf; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::ledger::eth_bridge::bridge_pool_vp::BridgePoolVp; + use namada::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; + use namada::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; + use namada::ledger::eth_bridge::storage::wrapped_erc20s; + use namada::ledger::eth_bridge::ADDRESS; + use namada::proto::Tx; + use namada::types::address::{wnam, xan}; + use namada::types::eth_bridge_pool::{ + GasFee, PendingTransfer, TransferToEthereum, + }; + use namada::types::ethereum_events::EthAddress; + use namada::types::key::{common, ed25519, SecretKey}; + use namada::types::token::Amount; + use namada_apps::wallet::defaults::{albert_address, bertha_address}; + use namada_apps::wasm_loader; + + use crate::native_vp::TestNativeVpEnv; + use crate::tx::{tx_host_env, TestTxEnv}; + + const ADD_TRANSFER_WASM: &str = "tx_bridge_pool.wasm"; + const ASSET: EthAddress = EthAddress([1; 20]); + const BERTHA_WEALTH: u64 = 1_000_000; + const BERTHA_TOKENS: u64 = 10_000; + const GAS_FEE: u64 = 100; + const TOKENS: u64 = 10; + + /// A signing keypair for good old Bertha. + fn bertha_keypair() -> common::SecretKey { + // generated from + // [`namada::types::key::ed25519::gen_keypair`] + let bytes = [ + 240, 3, 224, 69, 201, 148, 60, 53, 112, 79, 80, 107, 101, 127, 186, + 6, 176, 162, 113, 224, 62, 8, 183, 187, 124, 234, 244, 251, 92, 36, + 119, 243, + ]; + let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); + ed_sk.try_to_sk().unwrap() + } + + /// Gets the absolute path to wasm directory + fn wasm_dir() -> PathBuf { + let mut current_path = std::env::current_dir() + .expect("Current directory should exist") + .canonicalize() + .expect("Current directory should exist"); + while current_path.file_name().unwrap() != "tests" { + current_path.pop(); + } + current_path.pop(); + current_path.join("wasm") + } + + /// Create necessary accounts and balances for the test. + fn setup_env(tx: Tx) -> TestTxEnv { + let mut env = TestTxEnv { + tx, + ..Default::default() + }; + let config = EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([42; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: Default::default(), + }, + }, + }; + // initialize Ethereum bridge storage + config.init_storage(&mut env.storage); + // initialize Bertha's account + env.spawn_accounts([&albert_address(), &bertha_address(), &xan()]); + // enrich Albert + env.credit_tokens( + &albert_address(), + &xan(), + None, + BERTHA_WEALTH.into(), + ); + // enrich Bertha + env.credit_tokens( + &bertha_address(), + &xan(), + None, + BERTHA_WEALTH.into(), + ); + // Bertha has ERC20 tokens too. + let sub_prefix = wrapped_erc20s::sub_prefix(&ASSET); + env.credit_tokens( + &bertha_address(), + &ADDRESS, + Some(sub_prefix), + BERTHA_TOKENS.into(), + ); + env + } + + fn validate_tx(tx: Tx) { + let env = setup_env(tx); + tx_host_env::set(env); + let mut tx_env = tx_host_env::take(); + tx_env.execute_tx().expect("Test failed."); + let vp_env = TestNativeVpEnv::from_tx_env(tx_env, BRIDGE_POOL_ADDRESS); + let result = vp_env + .validate_tx(|ctx| BridgePoolVp { ctx }) + .expect("Test failed"); + assert!(result); + } + + #[test] + fn validate_erc20_tx() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: ASSET, + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: Amount::from(TOKENS), + nonce: Default::default(), + }, + gas_fee: GasFee { + amount: Amount::from(GAS_FEE), + payer: bertha_address(), + }, + }; + let data = transfer.try_to_vec().expect("Test failed"); + let code = + wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); + let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + validate_tx(tx); + } + + #[test] + fn validate_mint_wnam_tx() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: Amount::from(TOKENS), + nonce: Default::default(), + }, + gas_fee: GasFee { + amount: Amount::from(GAS_FEE), + payer: bertha_address(), + }, + }; + let data = transfer.try_to_vec().expect("Test failed"); + let code = + wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); + let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + validate_tx(tx); + } + + #[test] + fn validate_mint_wnam_different_sender_tx() { + let transfer = PendingTransfer { + transfer: TransferToEthereum { + asset: wnam(), + recipient: EthAddress([0; 20]), + sender: bertha_address(), + amount: Amount::from(TOKENS), + nonce: Default::default(), + }, + gas_fee: GasFee { + amount: Amount::from(GAS_FEE), + payer: albert_address(), + }, + }; + let data = transfer.try_to_vec().expect("Test failed"); + let code = + wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); + let tx = Tx::new(code, Some(data)).sign(&bertha_keypair()); + validate_tx(tx); + } +} diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index 8711f86d1d..6ff5158726 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -1,3 +1,4 @@ +pub mod eth_bridge_pool; pub mod pos; use std::collections::BTreeSet; diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 3eb674946a..9d1073955a 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -13,6 +13,7 @@ use namada::types::storage::Key; use namada::types::time::DurationSecs; use namada::types::{key, token}; use namada::vm::prefix_iter::PrefixIterators; +use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; use namada_tx_prelude::{BorshSerialize, Ctx}; @@ -152,9 +153,17 @@ impl TestTxEnv { &mut self, target: &Address, token: &Address, + sub_prefix: Option, amount: token::Amount, ) { - let storage_key = token::balance_key(token, target); + let storage_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, target) + } + None => token::balance_key(token, target), + }; self.storage .write(&storage_key, amount.try_to_vec().unwrap()) .unwrap(); @@ -171,6 +180,22 @@ impl TestTxEnv { .write(&storage_key, public_key.try_to_vec().unwrap()) .unwrap(); } + + /// Apply the tx changes to the write log and return + /// the set of verifiers. + pub fn execute_tx(&mut self) -> Result<(), Error> { + let empty_data = vec![]; + wasm::run::tx( + &self.storage, + &mut self.write_log, + &mut self.gas_meter, + &self.tx.code, + self.tx.data.as_ref().unwrap_or(&empty_data), + &mut self.vp_wasm_cache, + &mut self.tx_wasm_cache, + ) + .and(Ok(())) + } } /// This module allows to test code with tx host environment functions. diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 730adb3155..92d44bbdc0 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -16,6 +16,7 @@ use core::slice; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; +pub use namada::ledger::eth_bridge; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::slash_fund::storage as slash_fund_storage; @@ -29,6 +30,7 @@ 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; +pub use namada::types::ethereum_events::EthAddress; use namada::types::internal::HostEnvResult; use namada::types::storage::{ BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, diff --git a/wasm/checksums.json b/wasm/checksums.json index f22b117b80..9c66deac7a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.9f5da714d7c05d2d6b798b1848268533440ca3a5643ee9504af129f43ba331df.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_from_intent.wasm": "tx_from_intent.9eea194231154a47f5266f616b082b64af17a74b1d82f9f66c48faeac1edabff.wasm", - "tx_ibc.wasm": "tx_ibc.de282d7aa730001451e8a326917809383b6a4e910279403cde6932d1c348dd63.wasm", - "tx_init_account.wasm": "tx_init_account.2d79309956f554310003e4cf09eab80a63a79d9a671bc9ff6e3f73a2d1e18c2e.wasm", - "tx_init_nft.wasm": "tx_init_nft.67ca13e087ad254fa8247ba6ae430b297f9c5291fa692bb1f6a49514fd7b0269.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4793f45a613357b633e82f3e65fc661e23b000fda5c457c599d8d7b877668351.wasm", - "tx_init_validator.wasm": "tx_init_validator.330f45935532b80a309ef052d1c62762d1c76976bc8ac46850ad9449b5075709.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.034cba930f1c0fc8499848a90c71554c3749ac132f510d435e8b1c002cf6b572.wasm", - "tx_transfer.wasm": "tx_transfer.e2c1fceba9fdf6c99421e17a3a845c420b145c8976a9f7bf52bc3734d2568656.wasm", - "tx_unbond.wasm": "tx_unbond.a28537454c177df4e82c716f6e5be90b2cb4a042da8addd9fdc14eccbeeae52f.wasm", - "tx_update_vp.wasm": "tx_update_vp.3a37e7675b95d9f0681de7c90eec28fee2ed9d9db17588f11d30fb1ef6d56070.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.3ad7d17d6cfbbae7c45908260d4cdf16da651f0690a052ea3303380de0d535c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.6232c80ca02835c58b1fbb59170283906f056cfeaec4cd805e790285546ed970.wasm", - "vp_nft.wasm": "vp_nft.3163d70fddc5424f708900eb1d3f205b8ae17a67b8b2d999a5740cde87ce41f2.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4ee358a72aefbb00f5aa1733a9d225d07953a131a4416e1d56073835cf7f05c5.wasm", - "vp_token.wasm": "vp_token.3f30ca81205217bb60fc9104ca95a2e863c5e65b32f9ade683d01f0b6343080e.wasm", - "vp_user.wasm": "vp_user.44c574f2e92400a118d7e5c4af736a23485e4316fda2984ddae5201344826b36.wasm" + "tx_bond.wasm": "tx_bond.05077fe2c2a1d2bd13a4070f64b6a6c0ff1895d51775885e01f512635438e6cd.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.a13edf85eb317219ab19e554695c92675ee0164f03e2386712eab76f461ff02c.wasm", + "tx_from_intent.wasm": "tx_from_intent.23ca9b7c07f1b7ce250b6b12304521d03303c37c9f7e1e5db6e2cefebf4443fa.wasm", + "tx_ibc.wasm": "tx_ibc.0afac0a0034055866227f8888d49029216db6d6282da1195853ef29e8bc1e5c6.wasm", + "tx_init_account.wasm": "tx_init_account.46a2e034efa1e66ea48a449f29af960f28dde6de0bec0927268700909ff61369.wasm", + "tx_init_nft.wasm": "tx_init_nft.04ac2f0197239c24fea8990503a13b2753c849206d8654c7072aaef92425fd08.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.ccfc32280e2f5b7bc21e6f39ce1cf9391d88be7e3a299982231380b9f6f2fc74.wasm", + "tx_init_validator.wasm": "tx_init_validator.0483ea5597393bcbfcb4fbe134fda0a9009c1549f871ee45a93dd2fb12252a2f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.c1ed1c60037fe29c2046e419266d974a04b57eb7172499b1430f8bd79775ebc6.wasm", + "tx_transfer.wasm": "tx_transfer.63edad3e35c9516de438ca8db024efbef019b241d9df2914638a4399cdf26d86.wasm", + "tx_unbond.wasm": "tx_unbond.86544be24e7dd810419289a6002d254813251c153ca21b040c3652036410ddf8.wasm", + "tx_update_vp.wasm": "tx_update_vp.270413f136a1cdb922b2f6a8efa314517f471f3090ddcd1435ad7e8baf7ad7da.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.92147642f2df0c2f7c2d3d72f7e77bb4c8775073bf5459abcb9027bf92cd2a71.wasm", + "tx_withdraw.wasm": "tx_withdraw.da2bc6bf4ea8dcf2eb86100a88156955200ecf373ca30ddea07ee67a87f7f420.wasm", + "vp_nft.wasm": "vp_nft.2e841a7951dc7c45951ac06eae1791020f2dae1ec13ed31079e5213d7f17dd3f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.719a5b774c5ae889494853edf7dcb56f8245ab29c305de3469c0354a32ba63c9.wasm", + "vp_token.wasm": "vp_token.afc87e6b140e48decd34734319ae9ac9034bf69e13350eb6bb23c740b419fdc5.wasm", + "vp_user.wasm": "vp_user.a0c7d4139018fddeff088c873dad38db0cdb76f26f94647ae70c005a18d9c2e5.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 8929992754..99d5bb044a 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -1,5 +1,7 @@ #[cfg(feature = "tx_bond")] pub mod tx_bond; +#[cfg(feature = "tx_bridge_pool")] +pub mod tx_bridge_pool; #[cfg(feature = "tx_ibc")] pub mod tx_ibc; #[cfg(feature = "tx_init_account")] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 18a96ad60d..8def153df3 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -93,7 +93,12 @@ mod tests { // 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); + tx_env.credit_tokens( + target, + &staking_token_address(), + None, + bond.amount, + ); }); let tx_code = vec![]; diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 0c945d6925..768efc39bf 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -1,25 +1,61 @@ //! A tx for adding a transfer request across the Ethereum bridge //! into the bridge pool. -use std::collections::HashSet; - use borsh::{BorshDeserialize, BorshSerialize}; -use eth_bridge_pool::{GasFee, PendingTransfer}; +use eth_bridge::storage::{bridge_pool, native_erc20_key, wrapped_erc20s}; +use eth_bridge_pool::{GasFee, PendingTransfer, TransferToEthereum}; 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 = PendingTransfer::try_from_slice( - &signed.data.unwrap()[..] - ) - .unwrap(); + let transfer = + PendingTransfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); // pay the gas fees - let GasFee { + let GasFee { amount, ref payer } = transfer.gas_fee; + token::transfer( + ctx, + payer, + &bridge_pool::BRIDGE_POOL_ADDRESS, + &address::xan(), + None, + amount, + )?; + let TransferToEthereum { + ref asset, + ref sender, amount, - ref payer, - } = transfer.gas_fees; - token::transfer(payer, &BRIDGE_POOL_ADDRESS, &address::xan(), amount); + .. + } = transfer.transfer; + // if minting wNam, escrow the correct amount + if *asset == native_erc20_address(ctx) { + token::transfer( + ctx, + sender, + ð_bridge::ADDRESS, + &address::xan(), + None, + amount, + )?; + } else { + // Otherwise we escrow ERC20 tokens. + let sub_prefix = wrapped_erc20s::sub_prefix(&transfer.transfer.asset); + token::transfer( + ctx, + sender, + &bridge_pool::BRIDGE_POOL_ADDRESS, + ð_bridge::ADDRESS, + Some(sub_prefix), + amount, + )?; + } // add transfer into the pool let pending_key = bridge_pool::get_pending_key(&transfer); - write(pending_key, transfer.try_to_vec().unwrap()); -} \ No newline at end of file + ctx.write_bytes(&pending_key, transfer.try_to_vec().unwrap()) + .unwrap(); + Ok(()) +} + +fn native_erc20_address(ctx: &mut Ctx) -> EthAddress { + let addr = ctx.read_bytes(&native_erc20_key()).unwrap().unwrap(); + BorshDeserialize::try_from_slice(addr.as_slice()).unwrap() +} diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 851329b908..0f249e73f3 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -103,6 +103,7 @@ mod tests { tx_env.credit_tokens( source, &staking_token_address(), + None, initial_stake, ); } diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 675b079609..67eb46a02d 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -110,6 +110,7 @@ mod tests { tx_env.credit_tokens( source, &staking_token_address(), + None, initial_stake, ); } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 9582791565..2fb02c389b 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -139,7 +139,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -275,7 +275,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -308,7 +308,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 256dc6bb17..aa52f6ab89 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -239,7 +239,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -283,7 +283,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -329,7 +329,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.write_public_key(&vp_owner, &public_key); @@ -379,7 +379,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { From ea06ac351c7fac9514191ad73290e2a1df5a4733 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 4 Nov 2022 14:54:30 +0000 Subject: [PATCH 1427/1995] Remove static lifetime from struct decl in Keys --- shared/src/ledger/eth_bridge/storage/vote_tallies.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs index c16672ccb7..19d9a9370e 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs +++ b/shared/src/ledger/eth_bridge/storage/vote_tallies.rs @@ -20,11 +20,11 @@ const VOTING_POWER_KEY_SEGMENT: &str = "voting_power"; /// Generator for the keys under which details of votes for some piece of data /// is stored -pub struct Keys { +pub struct Keys { /// The prefix under which the details of a piece of data for which we are /// tallying votes is stored pub prefix: Key, - _phantom: std::marker::PhantomData<&'static T>, + _phantom: std::marker::PhantomData<*const T>, } impl Keys { From f39d36ca221bec2a21c1c0d5e48fc6bd1f129c6d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 4 Nov 2022 15:50:38 +0000 Subject: [PATCH 1428/1995] TestClient should propagate errors to caller --- shared/src/ledger/queries/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 12b4428756..03bbfdf0f1 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -235,8 +235,11 @@ mod testing { tx_wasm_cache: self.tx_wasm_cache.clone(), storage_read_past_height_limit: None, }; - let response = self.rpc.handle(ctx, &request).unwrap(); - Ok(response) + // TODO: this is a hack to propagate errors to the caller, we should + // really permit error types other than [`std::io::Error`] + self.rpc.handle(ctx, &request).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) + }) } } } From 25c3a3ec17ce4cbd8ee6c96239da682e8f9ca7da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 4 Nov 2022 16:14:34 +0000 Subject: [PATCH 1429/1995] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0ec49b0a5b..2a38ef0d7c 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.f8753f056e76cb691d438ddfca027d0287455ab6bead474bf266b6f9bbc7997a.wasm", + "tx_bond.wasm": "tx_bond.023bc7a6a53348900ec8cded8b2c6751cc2320bb2be15a064fe2177346680820.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.48d5a00f93ccc8e5d8a2813d3026740ada13c47762fd2aaac4e83e595c7096bd.wasm", - "tx_init_account.wasm": "tx_init_account.ef797f2dc06c8b5a0ce1b313b315b4a5485e842f050f6aeeff03bf4066d451f9.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.09216d39eac6183af70899f0f593c59d8cf39d351382ad7cabbaacc337bff3da.wasm", - "tx_init_validator.wasm": "tx_init_validator.30cfdd052967aac2e41cddd4b3f59be23f024bf1ef9b26158746ff605d3f57e5.wasm", - "tx_transfer.wasm": "tx_transfer.dcc33623a491a1d7da05c8589f233b6a8ec83e0f85ab97a39320eb13a62befc0.wasm", - "tx_unbond.wasm": "tx_unbond.dc2c9cd27c0de7af08aee6e0f2ed29372c3f796d995b485f549a48fe735564f6.wasm", - "tx_update_vp.wasm": "tx_update_vp.f7eeb6c3704db11c837f54998bcc1956dd32fdccd10006d43a37a75c6fd8de13.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f6ca9903fc27dcd2dd52c8f28da5a22d9d19fa2b4ca097592e41dbc4b337b73e.wasm", - "tx_withdraw.wasm": "tx_withdraw.f882754a921ac904841c65a60202577f142e0f402bd9c2f777f1812004c8cf52.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.896c2b3a425ecaa9d0e5e6e4bec8232271ce732e28a3a7c96852f130c4c67908.wasm", - "vp_token.wasm": "vp_token.7678ce67c2040aed8a67e614df3f9cb22b1aa0e224fbc2a1480eb30c7b2fec64.wasm", - "vp_user.wasm": "vp_user.0893b5f6b294d93d357d021a1fca5f182279b00b961aaf6ea4d1e003f3ebc372.wasm" -} + "tx_ibc.wasm": "tx_ibc.5e663366921be1ff08fafb2c1c309f5f1d91476fd10341c691fab54c6b723a10.wasm", + "tx_init_account.wasm": "tx_init_account.26ab2048f17e9333f55588b2f213563ea12a441aab80dc5c5523aabf99e712c6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a4871088f34f4aad2516ac190e43fc9c5f37bde9461a69b5dcd181c486f7438d.wasm", + "tx_init_validator.wasm": "tx_init_validator.e54e6645b7b8b92481e1a0c2ed70b7e0999b6cfeaed32752e6bdda5b0f521b61.wasm", + "tx_transfer.wasm": "tx_transfer.8ef6b3e0900bfdef3dfaed4e97593c943d7a06f70c93ef8bfdb0d5dfbf4c6d1e.wasm", + "tx_unbond.wasm": "tx_unbond.46d10ec36a842e5830255bc86154c17ef6cee06f0e031a07314b4c6be4725615.wasm", + "tx_update_vp.wasm": "tx_update_vp.2c8e4e1cab7940795ffc779f72b1d6d29b6d21e71303730afb7ad41a3646c9b1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1606a8644660b659f4d3d76c15e18c9775e542a80d45107fe56ec5ba3f83986b.wasm", + "tx_withdraw.wasm": "tx_withdraw.57d277e80118ec27bb4b6ff201b0b30bb8f9745363618a35e6cc771edb474aeb.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f48ed282a530c6fafa893fb0f122cabc2e1d4c45b8ae48b914a22851ed96d2e7.wasm", + "vp_token.wasm": "vp_token.9e307dab97ad3a44ffa5e3b9e443d96ff5bd3add287e0d613799efce79c1d5c6.wasm", + "vp_user.wasm": "vp_user.a0db59e674026878f31f7973bbaffa559397d3a25612f87f5724c04f72df0366.wasm" +} \ No newline at end of file From 3a67de1b01218573cae7696823b5cb75410f1033 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Mon, 7 Nov 2022 10:33:18 +0100 Subject: [PATCH 1430/1995] Update apps/src/lib/client/eth_bridge_pool.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/client/eth_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index 609f199f32..94b47172c1 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -59,7 +59,7 @@ pub async fn construct_bridge_pool_proof(args: args::BridgePoolProof) { if response.code != Code::Ok { println!("{}", response.info); } else { - println!("ABI Encoded Proof:\n {:#?}", response.value); + println!("Ethereum ABI-encoded proof:\n {:#?}", response.value); } } From 3922bf30736972303588598278061b069cd992b8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 7 Nov 2022 09:35:55 +0000 Subject: [PATCH 1431/1995] Make events.rs use the Tendermint facade --- shared/src/ledger/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/events.rs b/shared/src/ledger/events.rs index b17d2db83d..e0fecdbdab 100644 --- a/shared/src/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -7,10 +7,10 @@ use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; use borsh::{BorshDeserialize, BorshSerialize}; -use tendermint_proto::abci::EventAttribute; use thiserror::Error; use crate::ledger::governance::utils::ProposalEvent; +use crate::tendermint_proto::abci::EventAttribute; use crate::types::ibc::IbcEvent; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::{hash_tx, TxType}; @@ -162,7 +162,7 @@ impl From for Event { } /// Convert our custom event into the necessary tendermint proto type -impl From for tendermint_proto::abci::Event { +impl From for crate::tendermint_proto::abci::Event { fn from(event: Event) -> Self { Self { r#type: event.event_type.to_string(), From fd997673e3e8acf805d2c79526498981d4b5fd60 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 11:36:35 +0100 Subject: [PATCH 1432/1995] [feat]: Migrated the add transfer to eth bridge pool command to namadac --- apps/src/bin/anoma-client/cli.rs | 6 +++++- apps/src/bin/anoma-relayer/cli.rs | 6 +----- apps/src/lib/cli.rs | 36 +++++++++++++++---------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index b87cdb5c66..09fd06e2d1 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -3,7 +3,7 @@ use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::cmds::*; -use namada_apps::client::{rpc, tx, utils}; +use namada_apps::client::{eth_bridge_pool, rpc, tx, utils}; pub async fn main() -> Result<()> { match cli::anoma_client_cli()? { @@ -48,6 +48,10 @@ pub async fn main() -> Result<()> { Sub::Withdraw(Withdraw(args)) => { tx::submit_withdraw(ctx, args).await; } + // Eth bridge pool + Sub::AddToEthBridgePool(args) => { + eth_bridge_pool::add_to_eth_bridge_pool(ctx, args.0).await; + } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { rpc::query_epoch(args).await; diff --git a/apps/src/bin/anoma-relayer/cli.rs b/apps/src/bin/anoma-relayer/cli.rs index e7c485fca7..4dbab91796 100644 --- a/apps/src/bin/anoma-relayer/cli.rs +++ b/apps/src/bin/anoma-relayer/cli.rs @@ -6,13 +6,9 @@ use namada_apps::cli::cmds; use namada_apps::client::eth_bridge_pool; pub async fn main() -> Result<()> { - let (cmd, ctx) = cli::anoma_relayer_cli()?; + let (cmd, _) = cli::anoma_relayer_cli()?; use cmds::EthBridgePool as Sub; match cmd { - // Ledger cmds - Sub::AddTransfer(args) => { - eth_bridge_pool::add_to_eth_bridge_pool(ctx, args).await; - } Sub::ConstructProof(args) => { eth_bridge_pool::construct_bridge_pool_proof(args).await; } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bd2221f86a..638251bbf0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -169,17 +169,19 @@ pub mod cmds { .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) + // Ethereum bridge pool + .subcommand(AddToEthBridgePool::def().display_order(3)) // Queries - .subcommand(QueryEpoch::def().display_order(3)) - .subcommand(QueryBalance::def().display_order(3)) - .subcommand(QueryBonds::def().display_order(3)) - .subcommand(QueryVotingPower::def().display_order(3)) - .subcommand(QuerySlashes::def().display_order(3)) - .subcommand(QueryResult::def().display_order(3)) - .subcommand(QueryRawBytes::def().display_order(3)) - .subcommand(QueryProposal::def().display_order(3)) - .subcommand(QueryProposalResult::def().display_order(3)) - .subcommand(QueryProtocolParameters::def().display_order(3)) + .subcommand(QueryEpoch::def().display_order(4)) + .subcommand(QueryBalance::def().display_order(4)) + .subcommand(QueryBonds::def().display_order(4)) + .subcommand(QueryVotingPower::def().display_order(4)) + .subcommand(QuerySlashes::def().display_order(4)) + .subcommand(QueryResult::def().display_order(4)) + .subcommand(QueryRawBytes::def().display_order(4)) + .subcommand(QueryProposal::def().display_order(4)) + .subcommand(QueryProposalResult::def().display_order(4)) + .subcommand(QueryProtocolParameters::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -214,6 +216,8 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProposalResult); let query_protocol_parameters = Self::parse_with_ctx(matches, QueryProtocolParameters); + let add_to_eth_bridge_pool = + Self::parse_with_ctx(matches, AddToEthBridgePool); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -227,6 +231,7 @@ pub mod cmds { .or(bond) .or(unbond) .or(withdraw) + .or(add_to_eth_bridge_pool) .or(query_epoch) .or(query_balance) .or(query_bonds) @@ -286,6 +291,7 @@ pub mod cmds { Bond(Bond), Unbond(Unbond), Withdraw(Withdraw), + AddToEthBridgePool(AddToEthBridgePool), QueryEpoch(QueryEpoch), QueryBalance(QueryBalance), QueryBonds(QueryBonds), @@ -1248,8 +1254,6 @@ pub mod cmds { #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum EthBridgePool { - /// Add a transfer to the pool. - AddTransfer(args::EthereumBridgePool), /// Construct a proof that a set of transfers is in the pool. /// This can be used to relay transfers across the /// bridge to Ethereum. @@ -1260,19 +1264,16 @@ pub mod cmds { impl Cmd for EthBridgePool { fn add_sub(app: App) -> App { - app.subcommand(AddToEthBridgePool::def().display_order(1)) - .subcommand(ConstructProof::def().display_order(1)) + app.subcommand(ConstructProof::def().display_order(1)) .subcommand(QueryEthBridgePool::def().display_order(1)) } fn parse(matches: &ArgMatches) -> Option { - let add_to_pool = AddToEthBridgePool::parse(matches) - .map(|add| Self::AddTransfer(add.0)); let construct_proof = ConstructProof::parse(matches) .map(|proof| Self::ConstructProof(proof.0)); let query_pool = QueryEthBridgePool::parse(matches) .map(|q| Self::QueryPool(q.0)); - add_to_pool.or(construct_proof).or(query_pool) + construct_proof.or(query_pool) } } @@ -1290,7 +1291,6 @@ pub mod cmds { pool. This pool holds transfers waiting to be relayed to \ Ethereum.", ) - .subcommand(AddToEthBridgePool::def().display_order(1)) .subcommand(ConstructProof::def().display_order(1)) .subcommand(QueryEthBridgePool::def().display_order(1)) } From 01eec6ac465a2816703d23c328bc0b2d5e958c53 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 11:07:39 +0000 Subject: [PATCH 1433/1995] Return EncodeCell in Encode trait --- shared/src/ledger/queries/shell.rs | 3 ++- shared/src/types/keccak.rs | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 34d49aa0ac..712f998ee0 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -715,7 +715,8 @@ mod test { // TODO: Use a real nonce nonce: 0.into(), } - .encode(); + .encode() + .into_inner(); assert_eq!(proof, resp.data.into_inner()); } diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index c6a1986226..d5c38556c7 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -112,11 +112,10 @@ pub mod encode { use super::*; - /// A container for data types that are able to be `encode` - /// or `encodePacked` Ethereum ABI-encoded. + /// A container for data types that are able to be Ethereum ABI-encoded. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] #[repr(transparent)] - pub struct EncodeCell { + pub struct EncodeCell { /// ABI-encoded value of type `T`. encoded_data: Vec, /// Indicate we do not own values of type `T`. @@ -133,7 +132,10 @@ pub mod encode { where T: Encode, { - let encoded_data = value.encode(); + let encoded_data = { + let tokens = value.tokenize(); + ethabi::encode(tokens.as_slice()) + }; Self { encoded_data, _marker: PhantomData, @@ -147,21 +149,20 @@ pub mod encode { } /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode { + pub trait Encode: Sized { /// Encodes a struct into a sequence of ABI /// [`Token`] instances. fn tokenize(&self) -> [Token; N]; - /// Returns the encoded [`Token`] instances. - fn encode(&self) -> Vec { - let tokens = self.tokenize(); - ethabi::encode(tokens.as_slice()) + /// Returns the encoded [`Token`] instances, in a type-safe enclosure. + fn encode(&self) -> EncodeCell { + EncodeCell::new(self) } /// Encodes a slice of [`Token`] instances, and returns the /// keccak hash of the encoded string. fn keccak256(&self) -> KeccakHash { - keccak_hash(self.encode().as_slice()) + keccak_hash(self.encode().into_inner().as_slice()) } /// Encodes a slice of [`Token`] instances, and returns the @@ -171,7 +172,7 @@ pub mod encode { let mut output = [0; 32]; let eth_message = { - let message = self.encode(); + let message = self.encode().into_inner(); let mut eth_message = format!("\x19Ethereum Signed Message:\n{}", message.len()) @@ -218,7 +219,7 @@ pub mod encode { Token::Uint(U256::from(42u64)), Token::String("test".into()), ]); - assert_eq!(expected, got); + assert_eq!(expected, got.into_inner()); } /// Sanity check our keccak hash implementation. From 0f778f3e2e05e66053e539c31158f93eef09e7be Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 11:24:15 +0000 Subject: [PATCH 1434/1995] Relocate keccak::encode to eth_abi --- .../ledger/eth_bridge/storage/bridge_pool.rs | 2 +- shared/src/ledger/queries/shell.rs | 4 +- shared/src/types/eth_abi.rs | 149 +++++++++++++++++ shared/src/types/eth_bridge_pool.rs | 2 +- shared/src/types/keccak.rs | 150 ------------------ shared/src/types/key/secp256k1.rs | 2 +- shared/src/types/mod.rs | 1 + .../vote_extensions/validator_set_update.rs | 4 +- 8 files changed, 157 insertions(+), 157 deletions(-) create mode 100644 shared/src/types/eth_abi.rs diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs index 73fcab868b..526635bdbd 100644 --- a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/storage/bridge_pool.rs @@ -8,9 +8,9 @@ use ethabi::Token; use eyre::eyre; use crate::types::address::{Address, InternalAddress}; +use crate::types::eth_abi::Encode; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::keccak::encode::Encode; use crate::types::keccak::{keccak_hash, KeccakHash}; use crate::types::storage::{DbKeySeg, Key, KeySeg}; diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 712f998ee0..86b6ae3ad4 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -11,11 +11,11 @@ use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, MerkleTree, StoreRef, StoreType, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::types::eth_abi::EncodeCell; use crate::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; use crate::types::hash::Hash; -use crate::types::keccak::encode::EncodeCell; use crate::types::storage::MembershipProof::BridgePool; use crate::types::storage::{self, Epoch, MerkleValue, PrefixValue}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] @@ -421,12 +421,12 @@ mod test { use crate::ledger::storage_api::{self, StorageWrite}; use crate::proto::Tx; use crate::types::address::Address; + use crate::types::eth_abi::Encode; use crate::types::eth_bridge_pool::{ GasFee, MultiSignedMerkleRoot, PendingTransfer, RelayProof, TransferToEthereum, }; use crate::types::ethereum_events::EthAddress; - use crate::types::keccak::encode::Encode; use crate::types::{address, token}; const TX_NO_OP_WASM: &str = "../wasm_for_tests/tx_no_op.wasm"; diff --git a/shared/src/types/eth_abi.rs b/shared/src/types/eth_abi.rs new file mode 100644 index 0000000000..6b1f25ea50 --- /dev/null +++ b/shared/src/types/eth_abi.rs @@ -0,0 +1,149 @@ +//! This module defines encoding methods compatible with Ethereum +//! smart contracts. + +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +#[doc(inline)] +pub use ethabi::token::Token; +use tiny_keccak::{Hasher, Keccak}; + +use crate::types::keccak::{keccak_hash, KeccakHash}; + +/// A container for data types that are able to be Ethereum ABI-encoded. +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] +#[repr(transparent)] +pub struct EncodeCell { + /// ABI-encoded value of type `T`. + encoded_data: Vec, + /// Indicate we do not own values of type `T`. + /// + /// Passing `PhantomData` here would trigger the drop checker, + /// which is not the desired behavior, since we own an encoded value + /// of `T`, not a value of `T` itself. + _marker: PhantomData<*const T>, +} + +impl EncodeCell { + /// Return a new ABI encoded value of type `T`. + pub fn new(value: &T) -> Self + where + T: Encode, + { + let encoded_data = { + let tokens = value.tokenize(); + ethabi::encode(tokens.as_slice()) + }; + Self { + encoded_data, + _marker: PhantomData, + } + } + + /// Return the underlying ABI encoded value. + pub fn into_inner(self) -> Vec { + self.encoded_data + } +} + +/// Contains a method to encode data to a format compatible with Ethereum. +pub trait Encode: Sized { + /// Encodes a struct into a sequence of ABI + /// [`Token`] instances. + fn tokenize(&self) -> [Token; N]; + + /// Returns the encoded [`Token`] instances, in a type-safe enclosure. + fn encode(&self) -> EncodeCell { + EncodeCell::new(self) + } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string. + fn keccak256(&self) -> KeccakHash { + keccak_hash(self.encode().into_inner().as_slice()) + } + + /// Encodes a slice of [`Token`] instances, and returns the + /// keccak hash of the encoded string appended to an Ethereum + /// signature header. + fn signed_keccak256(&self) -> KeccakHash { + let mut output = [0; 32]; + + let eth_message = { + let message = self.encode().into_inner(); + + let mut eth_message = + format!("\x19Ethereum Signed Message:\n{}", message.len()) + .into_bytes(); + eth_message.extend_from_slice(&message); + eth_message + }; + + let mut state = Keccak::v256(); + state.update(ð_message); + state.finalize(&mut output); + + KeccakHash(output) + } +} + +/// Represents an Ethereum encoding method equivalent +/// to `abi.encode`. +pub type AbiEncode = [Token; N]; + +impl Encode for AbiEncode { + #[inline] + fn tokenize(&self) -> [Token; N] { + self.clone() + } +} + +// TODO: test signatures here once we merge secp keys +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use ethabi::ethereum_types::U256; + + use super::*; + + /// Checks if we get the same result as `abi.encode`, for some given + /// input data. + #[test] + fn test_abi_encode() { + let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; + let expected = hex::decode(&expected[2..]).expect("Test failed"); + let got = AbiEncode::encode(&[ + Token::Uint(U256::from(42u64)), + Token::String("test".into()), + ]); + assert_eq!(expected, got.into_inner()); + } + + /// Sanity check our keccak hash implementation. + #[test] + fn test_keccak_hash_impl() { + let expected = + "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; + assert_eq!( + expected, + &hex::encode({ + let mut st = Keccak::v256(); + let mut output = [0; 32]; + st.update(b"hello"); + st.finalize(&mut output); + output + }) + ); + } + + /// Test that the methods for converting a keccak hash to/from + /// a string type are inverses. + #[test] + fn test_hex_roundtrip() { + let original = + "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8"; + let keccak_hash: KeccakHash = original.try_into().expect("Test failed"); + assert_eq!(keccak_hash.to_string().as_str(), original); + } +} diff --git a/shared/src/types/eth_bridge_pool.rs b/shared/src/types/eth_bridge_pool.rs index eb25dae7ba..75e12d63fd 100644 --- a/shared/src/types/eth_bridge_pool.rs +++ b/shared/src/types/eth_bridge_pool.rs @@ -7,8 +7,8 @@ use ethabi::token::Token; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::types::address::Address; +use crate::types::eth_abi::Encode; use crate::types::ethereum_events::{EthAddress, Uint}; -use crate::types::keccak::encode::Encode; use crate::types::keccak::KeccakHash; use crate::types::storage::{BlockHeight, DbKeySeg, Key}; use crate::types::token::Amount; diff --git a/shared/src/types/keccak.rs b/shared/src/types/keccak.rs index d5c38556c7..b1e04c8691 100644 --- a/shared/src/types/keccak.rs +++ b/shared/src/types/keccak.rs @@ -3,7 +3,6 @@ //! on Ethereum. use std::convert::{TryFrom, TryInto}; use std::fmt::Display; -use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; @@ -101,152 +100,3 @@ pub fn keccak_hash(bytes: &[u8]) -> KeccakHash { KeccakHash(output) } - -/// This module defines encoding methods compatible with Ethereum -/// smart contracts. -// TODO: relocate this sub-module to a new home -pub mod encode { - #[doc(inline)] - pub use ethabi::token::Token; - use tiny_keccak::{Hasher, Keccak}; - - use super::*; - - /// A container for data types that are able to be Ethereum ABI-encoded. - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] - #[repr(transparent)] - pub struct EncodeCell { - /// ABI-encoded value of type `T`. - encoded_data: Vec, - /// Indicate we do not own values of type `T`. - /// - /// Passing `PhantomData` here would trigger the drop checker, - /// which is not the desired behavior, since we own an encoded value - /// of `T`, not a value of `T` itself. - _marker: PhantomData<*const T>, - } - - impl EncodeCell { - /// Return a new ABI encoded value of type `T`. - pub fn new(value: &T) -> Self - where - T: Encode, - { - let encoded_data = { - let tokens = value.tokenize(); - ethabi::encode(tokens.as_slice()) - }; - Self { - encoded_data, - _marker: PhantomData, - } - } - - /// Return the underlying ABI encoded value. - pub fn into_inner(self) -> Vec { - self.encoded_data - } - } - - /// Contains a method to encode data to a format compatible with Ethereum. - pub trait Encode: Sized { - /// Encodes a struct into a sequence of ABI - /// [`Token`] instances. - fn tokenize(&self) -> [Token; N]; - - /// Returns the encoded [`Token`] instances, in a type-safe enclosure. - fn encode(&self) -> EncodeCell { - EncodeCell::new(self) - } - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string. - fn keccak256(&self) -> KeccakHash { - keccak_hash(self.encode().into_inner().as_slice()) - } - - /// Encodes a slice of [`Token`] instances, and returns the - /// keccak hash of the encoded string appended to an Ethereum - /// signature header. - fn signed_keccak256(&self) -> KeccakHash { - let mut output = [0; 32]; - - let eth_message = { - let message = self.encode().into_inner(); - - let mut eth_message = - format!("\x19Ethereum Signed Message:\n{}", message.len()) - .into_bytes(); - eth_message.extend_from_slice(&message); - eth_message - }; - - let mut state = Keccak::v256(); - state.update(ð_message); - state.finalize(&mut output); - - KeccakHash(output) - } - } - - /// Represents an Ethereum encoding method equivalent - /// to `abi.encode`. - pub type AbiEncode = [Token; N]; - - impl Encode for AbiEncode { - #[inline] - fn tokenize(&self) -> [Token; N] { - self.clone() - } - } - - // TODO: test signatures here once we merge secp keys - #[cfg(test)] - mod tests { - use std::convert::TryInto; - - use ethabi::ethereum_types::U256; - - use super::*; - - /// Checks if we get the same result as `abi.encode`, for some given - /// input data. - #[test] - fn test_abi_encode() { - let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; - let expected = hex::decode(&expected[2..]).expect("Test failed"); - let got = AbiEncode::encode(&[ - Token::Uint(U256::from(42u64)), - Token::String("test".into()), - ]); - assert_eq!(expected, got.into_inner()); - } - - /// Sanity check our keccak hash implementation. - #[test] - fn test_keccak_hash_impl() { - let expected = - "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; - assert_eq!( - expected, - &hex::encode({ - let mut st = Keccak::v256(); - let mut output = [0; 32]; - st.update(b"hello"); - st.finalize(&mut output); - output - }) - ); - } - - /// Test that the methods for converting a keccak hash to/from - /// a string type are inverses. - #[test] - fn test_hex_roundtrip() { - let original = "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8"; - let keccak_hash: KeccakHash = - original.try_into().expect("Test failed"); - assert_eq!(keccak_hash.to_string().as_str(), original); - } - } -} diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index f23e2ec820..733690387e 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -21,8 +21,8 @@ use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; +use crate::types::eth_abi::Encode; use crate::types::ethereum_events::EthAddress; -use crate::types::keccak::encode::Encode; /// The provided constant is for a traditional /// signature on this curve. For Ethereum, an extra byte is included diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 78221659b5..0a5ac9a6b6 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod address; pub mod chain; pub mod dylib; +pub mod eth_abi; pub mod eth_bridge_pool; pub mod ethereum_events; pub mod governance; diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index fdc05dd0de..74e634ab68 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -9,8 +9,8 @@ use num_rational::Ratio; use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; +use crate::types::eth_abi::{AbiEncode, Encode, Token}; use crate::types::ethereum_events::{EthAddress, Uint}; -use crate::types::keccak::encode::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; @@ -307,7 +307,7 @@ mod tag { use super::{bheight_to_token, Vext, VotingPowersMapExt}; use crate::proto::SignedSerialize; - use crate::types::keccak::encode::{AbiEncode, Encode, Token}; + use crate::types::eth_abi::{AbiEncode, Encode, Token}; use crate::types::keccak::KeccakHash; /// Tag type that indicates we should use [`AbiEncode`] From d803527dd7b516ee298db19d74d8700685151aef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Nov 2022 12:41:37 +0000 Subject: [PATCH 1435/1995] [ci] wasm checksums update --- wasm/checksums.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2a38ef0d7c..a9bf35e814 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.023bc7a6a53348900ec8cded8b2c6751cc2320bb2be15a064fe2177346680820.wasm", + "tx_bond.wasm": "tx_bond.b5a685542f6a1f5dde8919cf88f2e7bfcdecfdd6b85e99ac873f6a118cf52c03.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.5e663366921be1ff08fafb2c1c309f5f1d91476fd10341c691fab54c6b723a10.wasm", - "tx_init_account.wasm": "tx_init_account.26ab2048f17e9333f55588b2f213563ea12a441aab80dc5c5523aabf99e712c6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a4871088f34f4aad2516ac190e43fc9c5f37bde9461a69b5dcd181c486f7438d.wasm", - "tx_init_validator.wasm": "tx_init_validator.e54e6645b7b8b92481e1a0c2ed70b7e0999b6cfeaed32752e6bdda5b0f521b61.wasm", - "tx_transfer.wasm": "tx_transfer.8ef6b3e0900bfdef3dfaed4e97593c943d7a06f70c93ef8bfdb0d5dfbf4c6d1e.wasm", - "tx_unbond.wasm": "tx_unbond.46d10ec36a842e5830255bc86154c17ef6cee06f0e031a07314b4c6be4725615.wasm", + "tx_ibc.wasm": "tx_ibc.67dce76df01cc511a3f218fad3b461554a971129826fcecfe3b8fba45e6675c6.wasm", + "tx_init_account.wasm": "tx_init_account.f6b1a02bb8aa97b8ce6ca310fa5e2a511adb972e1bdf067090d27b5f61d348cd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f5b0edc2b76ee7e2019d2032bf406760f7c6903c45901ecd234de93ef128d6fe.wasm", + "tx_init_validator.wasm": "tx_init_validator.b0f7301b1e0d61d8a8db3e6f1963f790dbf75f696e85296084bd6ae5bf1e3aa5.wasm", + "tx_transfer.wasm": "tx_transfer.ec21bb8d3ab2b829333899dffd8c1e2ad19d216936f247aff1297fc9d3e0d73f.wasm", + "tx_unbond.wasm": "tx_unbond.d4edee4114f060161ed2269be4e2f3a4410247b7768ec2e3cebc28874b6e6038.wasm", "tx_update_vp.wasm": "tx_update_vp.2c8e4e1cab7940795ffc779f72b1d6d29b6d21e71303730afb7ad41a3646c9b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1606a8644660b659f4d3d76c15e18c9775e542a80d45107fe56ec5ba3f83986b.wasm", - "tx_withdraw.wasm": "tx_withdraw.57d277e80118ec27bb4b6ff201b0b30bb8f9745363618a35e6cc771edb474aeb.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.f48ed282a530c6fafa893fb0f122cabc2e1d4c45b8ae48b914a22851ed96d2e7.wasm", - "vp_token.wasm": "vp_token.9e307dab97ad3a44ffa5e3b9e443d96ff5bd3add287e0d613799efce79c1d5c6.wasm", - "vp_user.wasm": "vp_user.a0db59e674026878f31f7973bbaffa559397d3a25612f87f5724c04f72df0366.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.be05cf634da6cc5c183f8da0760f913a0991d05889d8f70447696224e27497a6.wasm", + "tx_withdraw.wasm": "tx_withdraw.715389218bfb5f219b969f9182f4eb0c5afd1ba8aab5c9355b12915eff38732b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e2731831a35f19085237620ddace1602e7a3f26cd21dc2085e8389d4c153756e.wasm", + "vp_token.wasm": "vp_token.9a33e0fb755acd953c0f8136cf4d4afe3a9af31f6ac5737f11a49b6116356acd.wasm", + "vp_user.wasm": "vp_user.ced33b567c9ff67bf89a41b0f288deffbbd6c9d89cd71866db20e16208f787f5.wasm" } \ No newline at end of file From 91738959519299d52a97211175c4eb062c1d00c5 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 14:18:20 +0100 Subject: [PATCH 1436/1995] [chore]: Updated a docstring --- shared/src/ledger/eth_bridge/vp/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 100507a62a..50d27f4875 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -175,8 +175,13 @@ where } } -/// If `keys_changed` represents a valid set of changed keys, return them, -/// otherwise return `None`. +/// Checks if `keys_changed` represents a valid set of changed keys. +/// Depending on which keys get changed, chooses which type of +/// check to perform in the `validate_tx` function. +/// 1. If the Ethereum bridge escrow key was changed, we need to check +/// that escrow was performed correctly. +/// 2. If two erc20 keys where changed, this is a transfer that needs +/// to be checked. fn determine_check_type( keys_changed: &BTreeSet, ) -> Result, Error> { From 82df8ad4585ff001e08193ddf1f1f1fb591bb807 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 14:32:02 +0100 Subject: [PATCH 1437/1995] [chore]: Renaming xan to nam --- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 22 +++++++++---------- shared/src/ledger/eth_bridge/storage/mod.rs | 4 ++-- shared/src/ledger/eth_bridge/vp/mod.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 091c94f8a8..4244f2bbb4 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -24,7 +24,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; -use crate::types::address::{wnam, nam, Address, InternalAddress}; +use crate::types::address::{nam, wnam, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::storage::Key; use crate::types::token::{balance_key, Amount}; @@ -1067,7 +1067,7 @@ mod test_bridge_pool_vp { let mut write_log = new_writelog(); // initialize the eth bridge balance to 0 let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); write_log .write( &eb_account_key, @@ -1109,7 +1109,7 @@ mod test_bridge_pool_vp { }; // We escrow 100 Nam into the bridge pool VP // and 100 Nam in the Eth bridge VP - let account_key = balance_key(&xan(), &bertha_address()); + let account_key = balance_key(&nam(), &bertha_address()); write_log .write( &account_key, @@ -1118,7 +1118,7 @@ mod test_bridge_pool_vp { .expect("Test failed"), ) .expect("Test failed"); - let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let bp_account_key = balance_key(&nam(), &BRIDGE_POOL_ADDRESS); write_log .write( &bp_account_key, @@ -1169,7 +1169,7 @@ mod test_bridge_pool_vp { let mut write_log = new_writelog(); // initialize the eth bridge balance to 0 let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); write_log .write( &eb_account_key, @@ -1211,7 +1211,7 @@ mod test_bridge_pool_vp { }; // We escrow 100 Nam into the bridge pool VP // and 100 Nam in the Eth bridge VP - let account_key = balance_key(&xan(), &bertha_address()); + let account_key = balance_key(&nam(), &bertha_address()); write_log .write( &account_key, @@ -1220,7 +1220,7 @@ mod test_bridge_pool_vp { .expect("Test failed"), ) .expect("Test failed"); - let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let bp_account_key = balance_key(&nam(), &BRIDGE_POOL_ADDRESS); write_log .write( &bp_account_key, @@ -1271,7 +1271,7 @@ mod test_bridge_pool_vp { let mut write_log = new_writelog(); // initialize the eth bridge balance to 0 let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); write_log .write( &eb_account_key, @@ -1280,7 +1280,7 @@ mod test_bridge_pool_vp { .expect("Test failed"); // initialize the gas payers account let gas_payer_balance_key = - balance_key(&xan(), &established_address_1()); + balance_key(&nam(), &established_address_1()); write_log .write( &gas_payer_balance_key, @@ -1324,7 +1324,7 @@ mod test_bridge_pool_vp { }; // We escrow 100 Nam into the bridge pool VP // and 100 Nam in the Eth bridge VP - let account_key = balance_key(&xan(), &bertha_address()); + let account_key = balance_key(&nam(), &bertha_address()); write_log .write( &account_key, @@ -1341,7 +1341,7 @@ mod test_bridge_pool_vp { .expect("Test failed"), ) .expect("Test failed"); - let bp_account_key = balance_key(&xan(), &BRIDGE_POOL_ADDRESS); + let bp_account_key = balance_key(&nam(), &BRIDGE_POOL_ADDRESS); write_log .write( &bp_account_key, diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs index 33e91826bc..4a4a5de395 100644 --- a/shared/src/ledger/eth_bridge/storage/mod.rs +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -4,7 +4,7 @@ pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; -use crate::types::address::xan; +use crate::types::address::nam; use crate::types::storage::{DbKeySeg, Key, KeySeg}; use crate::types::token::balance_key; @@ -24,7 +24,7 @@ pub fn prefix() -> Key { /// The key to the escrow of the VP. pub fn escrow_key() -> Key { - balance_key(&xan(), &ADDRESS) + balance_key(&nam(), &ADDRESS) } /// Returns whether a key belongs to this account or not diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 5fe40b6953..d4af6b6a5f 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -65,7 +65,7 @@ where &self, verifiers: &BTreeSet
, ) -> Result { - let escrow_key = balance_key(&xan(), &super::ADDRESS); + let escrow_key = balance_key(&nam(), &super::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_bytes_pre(&escrow_key) { From bdcf44241b338cf9e11001ccb634f58c09313f06 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 14:55:22 +0100 Subject: [PATCH 1438/1995] [chore]: Renaming xan to nam --- shared/src/ledger/eth_bridge/bridge_pool_vp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index bb515e20b9..37a5ff714c 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -25,7 +25,7 @@ use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::proto::SignedTxData; -use crate::types::address::{nam, wnam, Address, InternalAddress}; +use crate::types::address::{nam, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::ethereum_events::EthAddress; use crate::types::storage::Key; @@ -1204,7 +1204,7 @@ mod test_bridge_pool_vp { let storage = setup_storage(); let tx = Tx::new(vec![], None); let eb_account_key = - balance_key(&xan(), &Address::Internal(InternalAddress::EthBridge)); + balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); // the transfer to be added to the pool let transfer = PendingTransfer { From 45ec3fa801e12ab6f9f5ba59597fb4141cff5c55 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 15:08:08 +0100 Subject: [PATCH 1439/1995] [chore]: Merging in v0.9 --- shared/src/ledger/eth_bridge/parameters.rs | 37 ++++++++++++++++++++++ shared/src/ledger/eth_bridge/vp/mod.rs | 14 ++++---- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/shared/src/ledger/eth_bridge/parameters.rs index f16292223b..46ff52c0a3 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/shared/src/ledger/eth_bridge/parameters.rs @@ -158,3 +158,40 @@ impl EthereumBridgeConfig { bridge_pool_vp::init_storage(storage); } } + +#[cfg(test)] +mod tests { + use eyre::Result; + + use crate::ledger::eth_bridge::parameters::{ + ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, + UpgradeableContract, + }; + use crate::types::ethereum_events::EthAddress; + + /// Ensure we can serialize and deserialize a [`Config`] struct to and from + /// TOML. This can fail if complex fields are ordered before simple fields + /// in any of the config structs. + #[test] + fn test_round_trip_toml_serde() -> Result<()> { + let config = EthereumBridgeConfig { + min_confirmations: MinimumConfirmations::default(), + contracts: Contracts { + native_erc20: EthAddress([42; 20]), + bridge: UpgradeableContract { + address: EthAddress([23; 20]), + version: ContractVersion::default(), + }, + governance: UpgradeableContract { + address: EthAddress([18; 20]), + version: ContractVersion::default(), + }, + }, + }; + let serialized = toml::to_string(&config)?; + let deserialized: EthereumBridgeConfig = toml::from_str(&serialized)?; + + assert_eq!(config, deserialized); + Ok(()) + } +} diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 9e974d5b6f..2eae6e869b 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -454,7 +454,7 @@ mod tests { // setup a user with a balance let balance_key = balance_key( - &xan(), + &nam(), &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), ); storage @@ -628,7 +628,7 @@ mod tests { let storage = setup_storage(); // debit the user's balance let account_key = balance_key( - &xan(), + &nam(), &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), ); writelog @@ -639,7 +639,7 @@ mod tests { .expect("Test failed"); // credit the balance to the escrow - let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), &super::super::ADDRESS); writelog .write( &escrow_key, @@ -671,7 +671,7 @@ mod tests { let storage = setup_storage(); // debit the user's balance let account_key = balance_key( - &xan(), + &nam(), &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), ); writelog @@ -682,7 +682,7 @@ mod tests { .expect("Test failed"); // do not credit the balance to the escrow - let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), &super::super::ADDRESS); writelog .write( &escrow_key, @@ -715,7 +715,7 @@ mod tests { let storage = setup_storage(); // debit the user's balance let account_key = balance_key( - &xan(), + &nam(), &Address::decode(ARBITRARY_OWNER_A_ADDRESS).expect("Test failed"), ); writelog @@ -726,7 +726,7 @@ mod tests { .expect("Test failed"); // credit the balance to the escrow - let escrow_key = balance_key(&xan(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), &super::super::ADDRESS); writelog .write( &escrow_key, From 74e0b3a296f0f37fa5514890d8fbc192dbff568c Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 7 Nov 2022 15:20:11 +0100 Subject: [PATCH 1440/1995] [chore]: Renaming test values as consts --- shared/src/ledger/eth_bridge/vp/mod.rs | 35 ++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index 2eae6e869b..e613fb8d8d 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -434,6 +434,9 @@ mod tests { "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; const ARBITRARY_OWNER_B_ADDRESS: &str = "atest1v4ehgw36xuunwd6989prwdfkxqmnvsfjxs6nvv6xxucrs3f3xcmns3fcxdzrvvz9xverzvzr56le8f"; + const ARBITRARY_OWNER_A_INITIAL_BALANCE: u64 = 100; + const ESCROW_AMOUNT: u64 = 100; + const BRIDGE_POOL_ESCROW_INITIAL_BALANCE: u64 = 0; /// Return some arbitrary random key belonging to this account fn arbitrary_key() -> Key { @@ -460,7 +463,9 @@ mod tests { storage .write( &balance_key, - Amount::from(100).try_to_vec().expect("Test failed"), + Amount::from(ARBITRARY_OWNER_A_INITIAL_BALANCE) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -634,7 +639,9 @@ mod tests { writelog .write( &account_key, - Amount::from(0).try_to_vec().expect("Test failed"), + Amount::from(ARBITRARY_OWNER_A_INITIAL_BALANCE - ESCROW_AMOUNT) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -643,7 +650,11 @@ mod tests { writelog .write( &escrow_key, - Amount::from(100).try_to_vec().expect("Test failed"), + Amount::from( + BRIDGE_POOL_ESCROW_INITIAL_BALANCE + ESCROW_AMOUNT, + ) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -677,7 +688,9 @@ mod tests { writelog .write( &account_key, - Amount::from(0).try_to_vec().expect("Test failed"), + Amount::from(ARBITRARY_OWNER_A_INITIAL_BALANCE - ESCROW_AMOUNT) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -686,7 +699,9 @@ mod tests { writelog .write( &escrow_key, - Amount::from(0).try_to_vec().expect("Test failed"), + Amount::from(BRIDGE_POOL_ESCROW_INITIAL_BALANCE) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -721,7 +736,9 @@ mod tests { writelog .write( &account_key, - Amount::from(0).try_to_vec().expect("Test failed"), + Amount::from(ARBITRARY_OWNER_A_INITIAL_BALANCE - ESCROW_AMOUNT) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); @@ -730,7 +747,11 @@ mod tests { writelog .write( &escrow_key, - Amount::from(100).try_to_vec().expect("Test failed"), + Amount::from( + BRIDGE_POOL_ESCROW_INITIAL_BALANCE + ESCROW_AMOUNT, + ) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); From 63f1f23044faa6f7381b7f21934fab55e0892850 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Nov 2022 14:35:30 +0000 Subject: [PATCH 1441/1995] [ci] 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 a9bf35e814..08916c3fb9 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.b5a685542f6a1f5dde8919cf88f2e7bfcdecfdd6b85e99ac873f6a118cf52c03.wasm", + "tx_bond.wasm": "tx_bond.4385ddcf475bf5d03cfaa2cc6816a0741b3fd894a592354aae87b19135874e62.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.67dce76df01cc511a3f218fad3b461554a971129826fcecfe3b8fba45e6675c6.wasm", - "tx_init_account.wasm": "tx_init_account.f6b1a02bb8aa97b8ce6ca310fa5e2a511adb972e1bdf067090d27b5f61d348cd.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5b0edc2b76ee7e2019d2032bf406760f7c6903c45901ecd234de93ef128d6fe.wasm", - "tx_init_validator.wasm": "tx_init_validator.b0f7301b1e0d61d8a8db3e6f1963f790dbf75f696e85296084bd6ae5bf1e3aa5.wasm", - "tx_transfer.wasm": "tx_transfer.ec21bb8d3ab2b829333899dffd8c1e2ad19d216936f247aff1297fc9d3e0d73f.wasm", - "tx_unbond.wasm": "tx_unbond.d4edee4114f060161ed2269be4e2f3a4410247b7768ec2e3cebc28874b6e6038.wasm", - "tx_update_vp.wasm": "tx_update_vp.2c8e4e1cab7940795ffc779f72b1d6d29b6d21e71303730afb7ad41a3646c9b1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.be05cf634da6cc5c183f8da0760f913a0991d05889d8f70447696224e27497a6.wasm", - "tx_withdraw.wasm": "tx_withdraw.715389218bfb5f219b969f9182f4eb0c5afd1ba8aab5c9355b12915eff38732b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e2731831a35f19085237620ddace1602e7a3f26cd21dc2085e8389d4c153756e.wasm", - "vp_token.wasm": "vp_token.9a33e0fb755acd953c0f8136cf4d4afe3a9af31f6ac5737f11a49b6116356acd.wasm", - "vp_user.wasm": "vp_user.ced33b567c9ff67bf89a41b0f288deffbbd6c9d89cd71866db20e16208f787f5.wasm" + "tx_ibc.wasm": "tx_ibc.36d265ef84f11e82c304b965d9852c3aac72bd50061212e8be1d03b632579c1e.wasm", + "tx_init_account.wasm": "tx_init_account.9c0701134b0f1b3b17ff21560b8ee16a761aba2c371a5ab3c4763c7f1c3d8f42.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0246bc54874b4ecf664dbd9441c747b257996c14b229b8c5e74c2f62784a3243.wasm", + "tx_init_validator.wasm": "tx_init_validator.4ea7273d501c6be0f57ff411645fbb02bb35a8be84ec2f3305baf0b5a2d868d4.wasm", + "tx_transfer.wasm": "tx_transfer.ad40979d4a1518c9c7463648cc63212991a149f898e53423569d7eea82f73d09.wasm", + "tx_unbond.wasm": "tx_unbond.cc1425fbb1fc5f9c3db8940f3c8713d79d9224b936f2763acd5f77a129dbfdce.wasm", + "tx_update_vp.wasm": "tx_update_vp.74fbce3cf33900a5b5c6291db82b53bfe5ca2f0d5b6b87bc83667206746e0ca5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c2708194bb19f1b3a2f629dc22a9e11788d35e8a570b9eb77d32f27994280a36.wasm", + "tx_withdraw.wasm": "tx_withdraw.77ab8eb211d24b8713458adc248724c571371e7dfedf22a30f6a13c0c9110ed3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.66aa929d4f17249d9b1b4d3e0053a21f4bc46c0326c082f84e2e02161f00a392.wasm", + "vp_token.wasm": "vp_token.6ac1cf76dd3d8cfef8f2fd1400e2b2488c7553855f3fb009bb7199c085b1d249.wasm", + "vp_user.wasm": "vp_user.74ed31241805844c86e1185746b084ee326b53e6e773b4cdf7a5a925fa804280.wasm" } \ No newline at end of file From 8b37595535ee94021f27df0cec65c0079ab493e9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 14:53:13 +0000 Subject: [PATCH 1442/1995] WIP: Move QueriesExt out of apps --- .../lib/node/ledger/shell/process_proposal.rs | 3 +- apps/src/lib/node/ledger/shell/queries.rs | 434 +----------------- shared/src/ledger/storage/mod.rs | 277 +++++++++++ shared/src/ledger/storage_api/mod.rs | 1 + shared/src/ledger/storage_api/queries.rs | 149 ++++++ 5 files changed, 435 insertions(+), 429 deletions(-) create mode 100644 shared/src/ledger/storage_api/queries.rs diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index dc4de4fb9b..2f10e40c59 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -359,8 +359,7 @@ where // check that the fee payer has sufficient balance let balance = self .storage - .get_balance(&wrapper.fee.token, &wrapper.fee_payer()) - .unwrap_or_default(); + .get_balance(&wrapper.fee.token, &wrapper.fee_payer()); if wrapper.fee.amount <= balance { TxResult { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 0fcd0f6f84..d29b8a7778 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,49 +2,16 @@ use std::cmp::max; use std::default::Default; -use borsh::BorshDeserialize; -use ferveo_common::TendermintValidator; -use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::pos::types::WeightedValidator; -use namada::ledger::pos::PosParams; use namada::ledger::queries::{RequestCtx, ResponseQuery}; use namada::ledger::storage_api; use namada::types::address::Address; -use namada::types::ethereum_events::EthAddress; -use namada::types::key; -use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::Epoch; -use namada::types::token::{self, Amount}; -use namada::types::vote_extensions::validator_set_update::EthAddrBook; use super::*; -use crate::facade::tendermint_proto::google::protobuf; -use crate::facade::tendermint_proto::types::EvidenceParams; use crate::node::ledger::response; -#[derive(Error, Debug)] -pub enum Error { - #[error( - "The address '{:?}' is not among the active validator set for epoch \ - {1}" - )] - NotValidatorAddress(Address, Epoch), - #[error( - "The public key '{0}' is not among the active validator set for epoch \ - {1}" - )] - #[allow(dead_code)] - NotValidatorKey(String, Epoch), - #[error( - "The public key hash '{0}' is not among the active validator set for \ - epoch {1}" - )] - NotValidatorKeyHash(String, Epoch), - #[error("Invalid validator tendermint address")] - InvalidTMAddress, -} - impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, @@ -98,404 +65,17 @@ where }, } } - - /// Query events in the event log matching the given query. - pub fn query_event_log( - &self, - token: &Address, - owner: &Address, - ) -> token::Amount { - let balance = storage_api::StorageRead::read( - &self.storage, - &token::balance_key(token, owner), - ); - // Storage read must not fail, but there might be no value, in which - // case default (0) is returned - balance - .expect("Storage read in the protocol must not fail") - .unwrap_or_default() - } -} - -/// API for querying the blockchain state. -pub(crate) trait QueriesExt { - /// Get the set of active validators for a given epoch (defaulting to the - /// epoch of the current yet-to-be-committed block). - fn get_active_validators( - &self, - epoch: Option, - ) -> BTreeSet>; - - /// Lookup the total voting power for an epoch (defaulting to the - /// epoch of the current yet-to-be-committed block). - fn get_total_voting_power(&self, epoch: Option) -> VotingPower; - - /// Simple helper function for the ledger to get balances - /// of the specified token at the specified address - fn get_balance( - &self, - token: &Address, - owner: &Address, - ) -> std::result::Result; - - fn get_evidence_params( - &self, - epoch_duration: &EpochDuration, - pos_params: &PosParams, - ) -> EvidenceParams; - - /// Lookup data about a validator from their protocol signing key - fn get_validator_from_protocol_pk( - &self, - pk: &key::common::PublicKey, - epoch: Option, - ) -> std::result::Result, Error>; - - /// Lookup data about a validator from their address - fn get_validator_from_address( - &self, - address: &Address, - epoch: Option, - ) -> std::result::Result<(VotingPower, common::PublicKey), Error>; - - /// Given a tendermint validator, the address is the hash - /// of the validators public key. We look up the native - /// address from storage using this hash. - // TODO: We may change how this lookup is done, see - // https://github.com/anoma/namada/issues/200 - fn get_validator_from_tm_address( - &self, - tm_address: &[u8], - epoch: Option, - ) -> std::result::Result; - - /// Determines if it is possible to send a validator set update vote - /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. - /// - /// This is done by checking if we are at the second block of a new epoch. - fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; - - /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. - fn get_epoch(&self, height: BlockHeight) -> Option; - - /// Retrieves the [`BlockHeight`] that is currently being decided. - fn get_current_decision_height(&self) -> BlockHeight; - - /// For a given Namada validator, return its corresponding Ethereum bridge - /// address. - fn get_ethbridge_from_namada_addr( - &self, - validator: &Address, - epoch: Option, - ) -> Option; - - /// For a given Namada validator, return its corresponding Ethereum - /// governance address. - fn get_ethgov_from_namada_addr( - &self, - validator: &Address, - epoch: Option, - ) -> Option; - - /// Extension of [`Self::get_active_validators`], which additionally returns - /// all Ethereum addresses of some validator. - fn get_active_eth_addresses<'db>( - &'db self, - epoch: Option, - ) -> Box + 'db>; -} - -impl QueriesExt for Storage -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - fn get_active_validators( - &self, - epoch: Option, - ) -> BTreeSet> { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - let validator_set = self.read_validator_set(); - validator_set - .get(epoch) - .expect("Validators for an epoch should be known") - .active - .clone() - } - - #[cfg(not(feature = "ABCI"))] - fn get_total_voting_power(&self, epoch: Option) -> VotingPower { - self.get_active_validators(epoch) - .iter() - .map(|validator| u64::from(validator.voting_power)) - .sum::() - .into() - } - - fn get_balance( - &self, - token: &Address, - owner: &Address, - ) -> std::result::Result { - let height = self.get_block_height().0; - let (balance, _) = self - .read_with_height(&token::balance_key(token, owner), height) - .map_err(|err| { - format!( - "Unable to read token {} balance of the given address {}: \ - {:?}", - token, owner, err - ) - })?; - let balance = match balance { - Some(balance) => balance, - None => { - return Err(format!( - "Unable to read token {} balance of the given address {}", - token, owner - )); - } - }; - BorshDeserialize::try_from_slice(&balance[..]).map_err(|err| { - format!( - "Unable to deserialize the balance of the given address: {:?}", - err - ) - }) - } - - 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() - } - } - - fn get_validator_from_protocol_pk( - &self, - pk: &key::common::PublicKey, - epoch: Option, - ) -> std::result::Result, Error> { - let pk_bytes = pk - .try_to_vec() - .expect("Serializing public key should not fail"); - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - self.get_active_validators(Some(epoch)) - .iter() - .find(|validator| { - let pk_key = key::protocol_pk_key(&validator.address); - match self.read(&pk_key) { - Ok((Some(bytes), _)) => bytes == pk_bytes, - _ => false, - } - }) - .map(|validator| { - let dkg_key = - key::dkg_session_keys::dkg_pk_key(&validator.address); - let bytes = self - .read(&dkg_key) - .expect("Validator should have public dkg key") - .0 - .expect("Validator should have public dkg key"); - let dkg_publickey = - &::deserialize( - &mut bytes.as_ref(), - ) - .expect( - "DKG public key in storage should be deserializable", - ); - TendermintValidator { - power: validator.voting_power.into(), - address: validator.address.to_string(), - public_key: dkg_publickey.into(), - } - }) - .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) - } - - #[cfg(not(feature = "ABCI"))] - fn get_validator_from_address( - &self, - address: &Address, - epoch: Option, - ) -> std::result::Result<(VotingPower, common::PublicKey), Error> { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - self.get_active_validators(Some(epoch)) - .iter() - .find(|validator| address == &validator.address) - .map(|validator| { - let protocol_pk_key = key::protocol_pk_key(&validator.address); - let bytes = self - .read(&protocol_pk_key) - .expect("Validator should have public protocol key") - .0 - .expect("Validator should have public protocol key"); - let protocol_pk: common::PublicKey = - BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( - "Protocol public key in storage should be \ - deserializable", - ); - (validator.voting_power, protocol_pk) - }) - .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) - } - - fn get_validator_from_tm_address( - &self, - tm_address: &[u8], - epoch: Option, - ) -> std::result::Result { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - let validator_raw_hash = core::str::from_utf8(tm_address) - .map_err(|_| Error::InvalidTMAddress)?; - self.read_validator_address_raw_hash(&validator_raw_hash) - .ok_or_else(|| { - Error::NotValidatorKeyHash( - validator_raw_hash.to_string(), - epoch, - ) - }) - } - - #[cfg(feature = "abcipp")] - fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { - // TODO: implement this method for ABCI++; should only be able to send - // a validator set update at the second block of an epoch - true - } - - #[cfg(not(feature = "abcipp"))] - fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { - // when checking vote extensions in Prepare - // and ProcessProposal, we simply return true - if matches!(can_send, SendValsetUpd::AtPrevHeight) { - return true; - } - - let current_decision_height = self.get_current_decision_height(); - - // NOTE: the first stored height in `fst_block_heights_of_each_epoch` - // is 0, because of a bug (should be 1), so this code needs to - // handle that case - // - // we can remove this check once that's fixed - match current_decision_height { - BlockHeight(1) => return false, - BlockHeight(2) => return true, - _ => (), - } - - let fst_heights_of_each_epoch = - self.block.pred_epochs.first_block_heights(); - - fst_heights_of_each_epoch - .last() - .map(|&h| { - let second_height_of_epoch = h + 1; - current_decision_height == second_height_of_epoch - }) - .unwrap_or(false) - } - - #[inline] - fn get_epoch(&self, height: BlockHeight) -> Option { - self.block.pred_epochs.get_epoch(height) - } - - #[inline] - fn get_current_decision_height(&self) -> BlockHeight { - self.last_height + 1 - } - - #[inline] - fn get_ethbridge_from_namada_addr( - &self, - validator: &Address, - epoch: Option, - ) -> Option { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - self.read_validator_eth_hot_key(validator) - .as_ref() - .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) - } - - #[inline] - fn get_ethgov_from_namada_addr( - &self, - validator: &Address, - epoch: Option, - ) -> Option { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - self.read_validator_eth_cold_key(validator) - .as_ref() - .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) - } - - #[inline] - fn get_active_eth_addresses<'db>( - &'db self, - epoch: Option, - ) -> Box + 'db> - { - let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); - Box::new(self.get_active_validators(Some(epoch)).into_iter().map( - move |validator| { - let hot_key_addr = self - .get_ethbridge_from_namada_addr( - &validator.address, - Some(epoch), - ) - .expect( - "All Namada validators should have an Ethereum bridge \ - key", - ); - let cold_key_addr = self - .get_ethgov_from_namada_addr( - &validator.address, - Some(epoch), - ) - .expect( - "All Namada validators should have an Ethereum \ - governance key", - ); - let eth_addr_book = EthAddrBook { - hot_key_addr, - cold_key_addr, - }; - (eth_addr_book, validator.address, validator.voting_power) - }, - )) - } -} - -/// This enum is used as a parameter to -/// [`QueriesExt::can_send_validator_set_update`]. -pub enum SendValsetUpd { - /// Check if it is possible to send a validator set update - /// vote extension at the current block height. - Now, - /// Check if it is possible to send a validator set update - /// vote extension at the previous block height. - AtPrevHeight, } +// NOTE: we are testing `namada::ledger::storage_api::queries`, +// which is not possible from `namada` since we do not have +// access to the `Shell` there #[cfg(test)] mod test_queries { + use namada::ledger::storage_api::queries::{ + self, QueriesExt, SendValsetUpd, + }; + use super::*; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index deb35374ee..91b10e5d14 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -10,7 +10,10 @@ pub mod write_log; use core::fmt::Debug; use std::array; +use std::collections::BTreeSet; +use borsh::{BorshDeserialize, BorshSerialize}; +use ferveo_common::TendermintValidator; use thiserror::Error; use super::parameters::Parameters; @@ -18,6 +21,10 @@ use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; use crate::ledger::parameters::EpochDuration; +use crate::ledger::pos::namada_proof_of_stake::types::VotingPower; +use crate::ledger::pos::namada_proof_of_stake::PosBase; +use crate::ledger::pos::types::WeightedValidator; +use crate::ledger::pos::PosParams; use crate::ledger::storage::merkle_tree::{ Error as MerkleTreeError, MerkleRoot, }; @@ -26,9 +33,15 @@ pub use crate::ledger::storage::merkle_tree::{ StoreType, }; pub use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage_api::queries::{self, QueriesExt, SendValsetUpd}; use crate::tendermint::merkle::proof::Proof; +use crate::tendermint_proto::google::protobuf; +use crate::tendermint_proto::types::EvidenceParams; use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; +use crate::types::ethereum_events::EthAddress; +use crate::types::key; +use crate::types::key::dkg_session_keys::DkgPublicKey; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; use crate::types::storage::{ @@ -36,6 +49,9 @@ use crate::types::storage::{ MembershipProof, MerkleValue, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; +use crate::types::token::{self, Amount}; +use crate::types::transaction::EllipticCurve; +use crate::types::vote_extensions::validator_set_update::EthAddrBook; /// A result of a function that may fail pub type Result = std::result::Result; @@ -889,6 +905,267 @@ where } } +impl QueriesExt for Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet> { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_set = self.read_validator_set(); + validator_set + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .clone() + } + + fn get_total_voting_power(&self, epoch: Option) -> VotingPower { + self.get_active_validators(epoch) + .iter() + .map(|validator| u64::from(validator.voting_power)) + .sum::() + .into() + } + + fn get_balance(&self, token: &Address, owner: &Address) -> Amount { + let balance = storage_api::StorageRead::read( + self, + &token::balance_key(token, owner), + ); + // Storage read must not fail, but there might be no value, in which + // case default (0) is returned + balance + .expect("Storage read in the protocol must not fail") + .unwrap_or_default() + } + + 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 = + std::cmp::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() + } + } + + fn get_validator_from_protocol_pk( + &self, + pk: &key::common::PublicKey, + epoch: Option, + ) -> queries::Result> { + let pk_bytes = pk + .try_to_vec() + .expect("Serializing public key should not fail"); + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) + .iter() + .find(|validator| { + let pk_key = key::protocol_pk_key(&validator.address); + match self.read(&pk_key) { + Ok((Some(bytes), _)) => bytes == pk_bytes, + _ => false, + } + }) + .map(|validator| { + let dkg_key = + key::dkg_session_keys::dkg_pk_key(&validator.address); + let bytes = self + .read(&dkg_key) + .expect("Validator should have public dkg key") + .0 + .expect("Validator should have public dkg key"); + let dkg_publickey = + &::deserialize( + &mut bytes.as_ref(), + ) + .expect( + "DKG public key in storage should be deserializable", + ); + TendermintValidator { + power: validator.voting_power.into(), + address: validator.address.to_string(), + public_key: dkg_publickey.into(), + } + }) + .ok_or_else(|| { + queries::Error::NotValidatorKey(pk.to_string(), epoch) + }) + } + + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> queries::Result<(VotingPower, key::common::PublicKey)> { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: key::common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.voting_power, protocol_pk) + }) + .ok_or_else(|| { + queries::Error::NotValidatorAddress(address.clone(), epoch) + }) + } + + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> queries::Result
{ + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| queries::Error::InvalidTMAddress)?; + self.read_validator_address_raw_hash(&validator_raw_hash) + .ok_or_else(|| { + queries::Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + }) + } + + #[cfg(feature = "abcipp")] + fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { + // TODO: implement this method for ABCI++; should only be able to send + // a validator set update at the second block of an epoch + true + } + + #[cfg(not(feature = "abcipp"))] + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { + // when checking vote extensions in Prepare + // and ProcessProposal, we simply return true + if matches!(can_send, SendValsetUpd::AtPrevHeight) { + return true; + } + + let current_decision_height = self.get_current_decision_height(); + + // NOTE: the first stored height in `fst_block_heights_of_each_epoch` + // is 0, because of a bug (should be 1), so this code needs to + // handle that case + // + // we can remove this check once that's fixed + match current_decision_height { + BlockHeight(1) => return false, + BlockHeight(2) => return true, + _ => (), + } + + let fst_heights_of_each_epoch = + self.block.pred_epochs.first_block_heights(); + + fst_heights_of_each_epoch + .last() + .map(|&h| { + let second_height_of_epoch = h + 1; + current_decision_height == second_height_of_epoch + }) + .unwrap_or(false) + } + + #[inline] + fn get_epoch(&self, height: BlockHeight) -> Option { + self.block.pred_epochs.get_epoch(height) + } + + #[inline] + fn get_current_decision_height(&self) -> BlockHeight { + self.last_height + 1 + } + + #[inline] + fn get_ethbridge_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.read_validator_eth_hot_key(validator) + .as_ref() + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) + } + + #[inline] + fn get_ethgov_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.read_validator_eth_cold_key(validator) + .as_ref() + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) + } + + #[inline] + fn get_active_eth_addresses<'db>( + &'db self, + epoch: Option, + ) -> Box + 'db> + { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + Box::new(self.get_active_validators(Some(epoch)).into_iter().map( + move |validator| { + let hot_key_addr = self + .get_ethbridge_from_namada_addr( + &validator.address, + Some(epoch), + ) + .expect( + "All Namada validators should have an Ethereum bridge \ + key", + ); + let cold_key_addr = self + .get_ethgov_from_namada_addr( + &validator.address, + Some(epoch), + ) + .expect( + "All Namada validators should have an Ethereum \ + governance key", + ); + let eth_addr_book = EthAddrBook { + hot_key_addr, + cold_key_addr, + }; + (eth_addr_book, validator.address, validator.voting_power) + }, + )) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index b806f35801..7578d0cb12 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,6 +3,7 @@ pub mod collections; mod error; +pub mod queries; pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs new file mode 100644 index 0000000000..235b412831 --- /dev/null +++ b/shared/src/ledger/storage_api/queries.rs @@ -0,0 +1,149 @@ +//! API for querying the blockchain state. + +use std::collections::BTreeSet; + +use ferveo_common::TendermintValidator; +use thiserror::Error; + +use crate::ledger::parameters::EpochDuration; +use crate::ledger::pos::namada_proof_of_stake::types::VotingPower; +use crate::ledger::pos::types::WeightedValidator; +use crate::ledger::pos::PosParams; +use crate::tendermint_proto::types::EvidenceParams; +use crate::types::address::Address; +use crate::types::ethereum_events::EthAddress; +use crate::types::key; +use crate::types::storage::{BlockHeight, Epoch}; +use crate::types::token::Amount; +use crate::types::transaction::EllipticCurve; +use crate::types::vote_extensions::validator_set_update::EthAddrBook; + +/// Errors returned by [`QueriesExt`] operations. +#[derive(Error, Debug)] +pub enum Error { + /// The given address is not among the set of active validators for + /// the corresponding epoch. + #[error( + "The address '{:?}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorAddress(Address, Epoch), + /// The given public key does not correspond to any active validator's + /// key at the provided epoch. + #[error( + "The public key '{0}' is not among the active validator set for epoch \ + {1}" + )] + NotValidatorKey(String, Epoch), + /// The given public key hash does not correspond to any active validator's + /// key at the provided epoch. + #[error( + "The public key hash '{0}' is not among the active validator set for \ + epoch {1}" + )] + NotValidatorKeyHash(String, Epoch), + /// An invalid Tendermint validator address was detected. + #[error("Invalid validator tendermint address")] + InvalidTMAddress, +} + +/// Result type returned by [`QueriesExt`] operations. +pub type Result = ::std::result::Result; + +/// This enum is used as a parameter to +/// [`QueriesExt::can_send_validator_set_update`]. +pub enum SendValsetUpd { + /// Check if it is possible to send a validator set update + /// vote extension at the current block height. + Now, + /// Check if it is possible to send a validator set update + /// vote extension at the previous block height. + AtPrevHeight, +} + +/// Methods used to query blockchain state, such as the currently +/// active set of validators. +pub trait QueriesExt { + /// Get the set of active validators for a given epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet>; + + /// Lookup the total voting power for an epoch (defaulting to the + /// epoch of the current yet-to-be-committed block). + fn get_total_voting_power(&self, epoch: Option) -> VotingPower; + + /// Simple helper function for the ledger to get balances + /// of the specified token at the specified address. + fn get_balance(&self, token: &Address, owner: &Address) -> Amount; + + /// Return evidence parameters. + // TODO: impove this docstring + fn get_evidence_params( + &self, + epoch_duration: &EpochDuration, + pos_params: &PosParams, + ) -> EvidenceParams; + + /// Lookup data about a validator from their protocol signing key. + fn get_validator_from_protocol_pk( + &self, + pk: &key::common::PublicKey, + epoch: Option, + ) -> Result>; + + /// Lookup data about a validator from their address. + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Result<(VotingPower, key::common::PublicKey)>; + + /// Given a tendermint validator, the address is the hash + /// of the validators public key. We look up the native + /// address from storage using this hash. + // TODO: We may change how this lookup is done, see + // https://github.com/anoma/namada/issues/200 + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> Result
; + + /// Determines if it is possible to send a validator set update vote + /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. + /// + /// This is done by checking if we are at the second block of a new epoch. + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; + + /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. + fn get_epoch(&self, height: BlockHeight) -> Option; + + /// Retrieves the [`BlockHeight`] that is currently being decided. + fn get_current_decision_height(&self) -> BlockHeight; + + /// For a given Namada validator, return its corresponding Ethereum bridge + /// address. + fn get_ethbridge_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option; + + /// For a given Namada validator, return its corresponding Ethereum + /// governance address. + fn get_ethgov_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option; + + /// Extension of [`Self::get_active_validators`], which additionally returns + /// all Ethereum addresses of some validator. + fn get_active_eth_addresses<'db>( + &'db self, + epoch: Option, + ) -> Box + 'db>; +} From f638bcea6a6450f7ec0ff9ea46a1fe68ac21f311 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 15:27:43 +0000 Subject: [PATCH 1443/1995] QueriesExt import fixes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- apps/src/lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 12 ++---------- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- .../node/ledger/shell/vote_extensions/eth_events.rs | 4 ++-- .../ledger/shell/vote_extensions/val_set_update.rs | 4 ++-- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a99d3df269..039397e804 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,6 +2,7 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; +use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; @@ -15,7 +16,6 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, TxRecord, }; -use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; use crate::node::ledger::shell::vote_extensions::{ iter_protocol_txs, split_vote_extensions, }; @@ -284,6 +284,7 @@ mod test_prepare_proposal { VotingPower, WeightedValidator, }; use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::ledger::storage_api::queries::QueriesExt; use namada::proto::{Signed, SignedTxData}; use namada::types::address::nam; use namada::types::ethereum_events::EthereumEvent; @@ -301,7 +302,6 @@ mod test_prepare_proposal { use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, ExtendedVoteInfo, TxRecord, }; - use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::{ self, gen_keypair, TestShell, }; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 2f10e40c59..40d5d24922 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -3,11 +3,11 @@ use data_encoding::HEXUPPER; use namada::ledger::pos::types::VotingPower; +use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; -use super::queries::{QueriesExt, SendValsetUpd}; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index d29b8a7778..1e24a6a706 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,13 +1,6 @@ //! Shell methods for querying state -use std::cmp::max; -use std::default::Default; -use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; -use namada::ledger::pos::types::WeightedValidator; use namada::ledger::queries::{RequestCtx, ResponseQuery}; -use namada::ledger::storage_api; -use namada::types::address::Address; -use namada::types::storage::Epoch; use super::*; use crate::node::ledger::response; @@ -72,9 +65,8 @@ where // access to the `Shell` there #[cfg(test)] mod test_queries { - use namada::ledger::storage_api::queries::{ - self, QueriesExt, SendValsetUpd, - }; + use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; + use namada::types::storage::Epoch; use super::*; use crate::node::ledger::shell::test_utils; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 06efad418e..21c394200b 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -5,6 +5,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; +use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{ @@ -14,7 +15,6 @@ use namada::types::vote_extensions::{ use super::*; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedVoteInfo; -use crate::node::ledger::shell::queries::{QueriesExt, SendValsetUpd}; #[cfg(not(feature = "abcipp"))] use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 0745d76e82..eb76eed09f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -5,6 +5,7 @@ use std::collections::{BTreeMap, HashMap}; use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; +use namada::ledger::storage_api::queries::QueriesExt; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; @@ -15,7 +16,6 @@ use namada::types::vote_extensions::ethereum_events::{ use namada::types::voting_power::FractionalVotingPower; use super::*; -use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::{Shell, ShellMode}; impl Shell @@ -298,6 +298,7 @@ mod test_vote_extensions { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::ledger::storage_api::queries::QueriesExt; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; @@ -311,7 +312,6 @@ mod test_vote_extensions { use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; #[cfg(feature = "abcipp")] use crate::facade::tower_abci::request; - use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils::*; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 63b2fbe72d..c3c1ac9221 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -7,13 +7,13 @@ use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::types::VotingPower; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; +use namada::ledger::storage_api::queries::QueriesExt; use namada::types::storage::BlockHeight; use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; use super::*; -use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::Shell; impl Shell @@ -309,6 +309,7 @@ mod test_vote_extensions { use borsh::BorshSerialize; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; + use namada::ledger::storage_api::queries::QueriesExt; use namada::types::key::RefTo; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::ethereum_events; @@ -320,7 +321,6 @@ mod test_vote_extensions { use crate::facade::tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; #[cfg(feature = "abcipp")] use crate::facade::tower_abci::request; - use crate::node::ledger::shell::queries::QueriesExt; use crate::node::ledger::shell::test_utils; use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; From 7867240922f446f13cceb9ccea6465693affa1e9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 15:41:36 +0000 Subject: [PATCH 1444/1995] Add a TODO item for GATs --- shared/src/ledger/storage_api/queries.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index 235b412831..efc187e1a2 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -64,6 +64,14 @@ pub enum SendValsetUpd { /// Methods used to query blockchain state, such as the currently /// active set of validators. pub trait QueriesExt { + /// TODO: when Rust 1.65 becomes available in Namada, we should return this + /// iterator type from [`QueriesExt::get_active_eth_addresses`], to + /// avoid a heap allocation; `F` will be the closure used to process the + /// iterator we currently return in the `Storage` impl + // ```ignore + // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, VotingPower)>; + // ``` + /// Get the set of active validators for a given epoch (defaulting to the /// epoch of the current yet-to-be-committed block). fn get_active_validators( From c64d1f9b6a47c00989d18cd549ce5f6809453cb4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 16:08:27 +0000 Subject: [PATCH 1445/1995] Another TODO --- shared/src/ledger/storage_api/queries.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index efc187e1a2..75ce51f0c4 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -71,6 +71,10 @@ pub trait QueriesExt { // ```ignore // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, VotingPower)>; // ``` + // a similar strategy can be used for [`QueriesExt::get_active_validators`]: + // ```ignore + // type ActiveValidatorsIter<'db, F>: Iterator>; + // ``` /// Get the set of active validators for a given epoch (defaulting to the /// epoch of the current yet-to-be-committed block). From 46fe52ec3062d96e17d11aa7fd87b602bfaf74ff Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 17:01:14 +0000 Subject: [PATCH 1446/1995] Fix TODO comment --- shared/src/ledger/storage_api/queries.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index 75ce51f0c4..98a725f984 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -64,10 +64,10 @@ pub enum SendValsetUpd { /// Methods used to query blockchain state, such as the currently /// active set of validators. pub trait QueriesExt { - /// TODO: when Rust 1.65 becomes available in Namada, we should return this - /// iterator type from [`QueriesExt::get_active_eth_addresses`], to - /// avoid a heap allocation; `F` will be the closure used to process the - /// iterator we currently return in the `Storage` impl + // TODO: when Rust 1.65 becomes available in Namada, we should return this + // iterator type from [`QueriesExt::get_active_eth_addresses`], to + // avoid a heap allocation; `F` will be the closure used to process the + // iterator we currently return in the `Storage` impl // ```ignore // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, VotingPower)>; // ``` From ea4f4c1290999e90171741e1398744b3c1d96506 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 7 Nov 2022 18:42:14 +0000 Subject: [PATCH 1447/1995] fix: use crate::facade in rpc.rs --- apps/src/lib/client/rpc.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5de87f073d..36214c3d7e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -36,16 +36,16 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_rpc::error::Error as TError; -use tendermint_rpc::query::Query; -use tendermint_rpc::{ - Client, HttpClient, Order, SubscriptionClient, WebSocketClient, -}; use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_rpc::error::Error as TError; +use crate::facade::tendermint_rpc::query::Query; +use crate::facade::tendermint_rpc::{ + Client, HttpClient, Order, SubscriptionClient, WebSocketClient, +}; /// Query the status of a given transaction. /// From 88cf46881c5d7a5909728a0c225465a7442d494c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 15:19:35 +0100 Subject: [PATCH 1448/1995] PendingEvent::decode should use its own hardcoded MIN_CONFIRMATIONS for now --- apps/src/lib/node/ledger/ethereum_node/events.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 69f7716d66..885a50ff62 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -122,6 +122,9 @@ pub mod eth_events { block_height: Uint256, data: &[u8], ) -> Result { + // TODO: use the value from storage rather than this hardcoded + // const! + const MIN_CONFIRMATIONS: u64 = 100; match signature { signatures::TRANSFER_TO_NAMADA_SIG => { RawTransfersToNamada::decode(data).map(|txs| PendingEvent { @@ -152,8 +155,7 @@ pub mod eth_events { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: - super::super::oracle::MIN_CONFIRMATIONS.into(), + confirmations: MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -165,8 +167,7 @@ pub mod eth_events { } signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS - .into(), + confirmations: MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::NewContract { name, address }, }), @@ -174,8 +175,7 @@ pub mod eth_events { data, ) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: super::super::oracle::MIN_CONFIRMATIONS - .into(), + confirmations: MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpgradedContract { name, address }, }), @@ -183,9 +183,7 @@ pub mod eth_events { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| { PendingEvent { - confirmations: - super::super::oracle::MIN_CONFIRMATIONS - .into(), + confirmations: MIN_CONFIRMATIONS.into(), block_height, event: EthereumEvent::UpdateBridgeWhitelist { nonce, From b1137dd6d24c60e9685574b616ba6823d42b5a12 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 24 Oct 2022 15:27:40 +0100 Subject: [PATCH 1449/1995] Make Oracle configurable and controllable via channel --- .../lib/node/ledger/ethereum_node/oracle.rs | 56 ++++++++++++++----- apps/src/lib/node/ledger/mod.rs | 19 ++++++- shared/src/types/ethereum_events.rs | 1 + 3 files changed, 61 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 0d06607e92..9eb27a5e84 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -5,7 +5,7 @@ use clarity::Address; use eyre::{eyre, Result}; use namada::types::ethereum_events::{EthAddress, EthereumEvent}; use num256::Uint256; -use tokio::sync::mpsc::Sender as BoundedSender; +use tokio::sync::mpsc::{Receiver as BoundedReceiver, Sender as BoundedSender}; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -15,12 +15,33 @@ use super::events::{signatures, PendingEvent}; #[cfg(test)] use super::test_tools::mock_web3_client::Web3; -/// Minimum number of confirmations needed to trust an Ethereum branch -pub(crate) const MIN_CONFIRMATIONS: u64 = 100; +/// Configuration for an [`Oracle`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Config { + pub min_confirmations: u64, + pub mint_contract: EthAddress, + pub governance_contract: EthAddress, +} + +// TODO: this production Default implementation is temporary, there should be no +// default config - initialization should always be from storage +impl std::default::Default for Config { + fn default() -> Self { + Self { + min_confirmations: 100, + mint_contract: EthAddress([0; 20]), + governance_contract: EthAddress([1; 20]), + } + } +} -/// Dummy addresses for smart contracts -const MINT_CONTRACT: EthAddress = EthAddress([0; 20]); -const GOVERNANCE_CONTRACT: EthAddress = EthAddress([1; 20]); +/// Commands used to configure and control an `Oracle`. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum ControlCommand { + /// Initializes the oracle with the given configuration and immediately + /// starts it. + Initialize { config: Config }, +} /// The default amount of time the oracle will wait between processing blocks const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); @@ -39,6 +60,10 @@ pub struct Oracle { abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, + /// A channel for controlling and configuring the oracle. + control: BoundedReceiver, + /// Configuration for this oracle. + config: Option, } impl Deref for Oracle { @@ -68,18 +93,22 @@ impl Drop for Oracle { } impl Oracle { - /// Initialize a new [`Oracle`] + /// Construct a new [`Oracle`]. Note that it will not start until it has + /// been configured via the passed in `control` channel. pub fn new( url: &str, sender: BoundedSender, abort: Sender<()>, backoff: Duration, + control: BoundedReceiver, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), sender, abort: Some(abort), backoff, + control, + config: None, } } @@ -112,6 +141,7 @@ impl Oracle { pub fn run_oracle( url: impl AsRef, sender: BoundedSender, + control: BoundedReceiver, abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); @@ -124,12 +154,8 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = Oracle::new( - &url, - sender, - abort_sender, - DEFAULT_BACKOFF, - ); + let oracle = + Oracle::new(&url, sender, abort_sender, DEFAULT_BACKOFF, control); run_oracle_aux(oracle).await; tracing::info!( @@ -367,6 +393,8 @@ mod test_oracle { fn setup() -> TestPackage { let (admin_channel, blocks_processed_recv, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); + let (_control_sender, control_receiver) = + tokio::sync::mpsc::channel(1000); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -375,6 +403,8 @@ mod test_oracle { abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), + control: control_receiver, + config: Some(Config::default()), }, admin_channel, eth_recv: eth_receiver, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index bcac12f286..b093d9a2e3 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -25,12 +25,14 @@ use tower::ServiceBuilder; use self::abortable::AbortableSpawner; use self::ethereum_node::eth_fullnode; +use self::ethereum_node::oracle::ControlCommand; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::{ethereum_bridge, TendermintMode}; use crate::facade::tendermint_proto::abci::CheckTxType; use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; +use crate::node::ledger::ethereum_node::oracle; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; @@ -231,9 +233,20 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let (eth_node, abort_sender) = maybe_start_geth(&mut spawner, &config).await; + let (oracle_control_send, oracle_control_recv) = mpsc::channel(1); // Start oracle if necessary let (eth_receiver, oracle) = - maybe_start_ethereum_oracle(&config, abort_sender).await; + maybe_start_ethereum_oracle(&config, abort_sender, oracle_control_recv); + // TODO: pass oracle_control_send to the shell instead of initializing it + // using a hardcoded config + if let Err(error) = oracle_control_send + .send(oracle::ControlCommand::Initialize { + config: oracle::Config::default(), + }) + .await + { + tracing::error!(?error, "Could not configure the oracle",); + } // Start ABCI server and broadcaster (the latter only if we are a validator // node) @@ -611,9 +624,10 @@ fn start_tendermint( /// /// An oracle is also returned, along with its associated channel, /// for receiving Ethereum events from `geth`. -async fn maybe_start_ethereum_oracle( +fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, + control_receiver: mpsc::Receiver, ) -> (Option>, task::JoinHandle<()>) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { return (None, spawn_dummy_task(())); @@ -629,6 +643,7 @@ async fn maybe_start_ethereum_oracle( ethereum_node::oracle::run_oracle( ethereum_url, eth_sender, + control_receiver, abort_sender, ) } diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 153a358817..2b66ac0d74 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -50,6 +50,7 @@ impl From for Uint { /// Representation of address on Ethereum. The inner value is the last 20 bytes /// of the public key that controls the account. #[derive( + Copy, Clone, Debug, PartialEq, From 7c0402c7eb1e783b4cd799568d2cf6324d67e3e3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 14:40:13 +0100 Subject: [PATCH 1450/1995] Fix up rebase --- .../lib/node/ledger/ethereum_node/oracle.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index 9eb27a5e84..d9585d0bb0 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -154,8 +154,13 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = - Oracle::new(&url, sender, abort_sender, DEFAULT_BACKOFF, control); + let oracle = Oracle::new( + &url, + sender, + abort_sender, + DEFAULT_BACKOFF, + control, + ); run_oracle_aux(oracle).await; tracing::info!( @@ -228,8 +233,8 @@ async fn process( )); } }; - let minimum_latest_block = - block_to_process.clone() + Uint256::from(MIN_CONFIRMATIONS); + let minimum_latest_block = block_to_process.clone() + + Uint256::from(oracle.config.unwrap().min_confirmations); if minimum_latest_block > latest_block { tracing::debug!( ?block_to_process, @@ -253,8 +258,12 @@ async fn process( // confirmations. for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => MINT_CONTRACT.0.into(), - signatures::SigType::Governance => GOVERNANCE_CONTRACT.0.into(), + signatures::SigType::Bridge => { + oracle.config.unwrap().mint_contract.0.into() + } + signatures::SigType::Governance => { + oracle.config.unwrap().governance_contract.0.into() + } }; tracing::debug!( ?block_to_process, @@ -331,7 +340,7 @@ async fn process( ?sig, pending = pending.len(), confirmed = confirmed.len(), - ?MIN_CONFIRMATIONS, + min_confirmations = ?oracle.config.unwrap().min_confirmations, "Some events that have reached the minimum number of \ confirmations and will be sent onwards" ); @@ -696,6 +705,7 @@ mod test_oracle { mut blocks_processed_recv, .. } = setup(); + let min_confirmations = oracle.config.unwrap().min_confirmations; let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); @@ -703,7 +713,7 @@ mod test_oracle { // set the height of the chain such that there are some blocks deep // enough to be considered confirmed by the oracle let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations - let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; + let synced_block_height = min_confirmations + confirmed_block_height; for height in 0..synced_block_height + 1 { admin_channel .send(TestCmd::NewHeight(Uint256::from(height))) @@ -758,12 +768,13 @@ mod test_oracle { mut blocks_processed_recv, .. } = setup(); + let min_confirmations = oracle.config.unwrap().min_confirmations; let oracle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations - let synced_block_height = MIN_CONFIRMATIONS + confirmed_block_height; + let synced_block_height = min_confirmations + confirmed_block_height; admin_channel .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); From 4925c4cacf14e6880c69ab38a4659b77b2420cfb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:02:57 +0100 Subject: [PATCH 1451/1995] Oracle must block until configured --- .../lib/node/ledger/ethereum_node/oracle.rs | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index d9585d0bb0..c6f09ba0fa 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -62,7 +62,8 @@ pub struct Oracle { backoff: Duration, /// A channel for controlling and configuring the oracle. control: BoundedReceiver, - /// Configuration for this oracle. + /// Configuration for this oracle. The oracle cannot run if it is not + /// configured. config: Option, } @@ -134,6 +135,21 @@ impl Oracle { async fn sleep(&self) { tokio::time::sleep(self.backoff).await; } + + /// Block until the oracle is configured via the command channel. Returns + /// the initial config once configured, or `None` if the command channel is + /// closed. + async fn await_initial_configuration(&mut self) -> Option { + match self.control.recv().await { + Some(cmd) => match cmd { + ControlCommand::Initialize { config } => { + self.config = Some(config); + self.config + } + }, + None => None, + } + } } /// Set up an Oracle and run the process where the Oracle @@ -178,7 +194,17 @@ pub fn run_oracle( /// /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process -async fn run_oracle_aux(oracle: Oracle) { +async fn run_oracle_aux(mut oracle: Oracle) { + match oracle.await_initial_configuration().await { + Some(config) => { + tracing::info!(?config, "Oracle received initial configuration") + } + None => tracing::debug!( + "Oracle control channel was closed before the oracle could be \ + configured" + ), + } + // Initialize a queue to keep events which are awaiting a certain number of // confirmations let mut pending: Vec = Vec::new(); From 95a83888b876ffcc6e73b9ce29e26b07b12dba2c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:03:35 +0100 Subject: [PATCH 1452/1995] Log that oracle is awaiting initial configuration --- apps/src/lib/node/ledger/ethereum_node/oracle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle.rs index c6f09ba0fa..53ded46ee3 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle.rs @@ -195,6 +195,7 @@ pub fn run_oracle( /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process async fn run_oracle_aux(mut oracle: Oracle) { + tracing::info!("Oracle is awaiting initial configuration"); match oracle.await_initial_configuration().await { Some(config) => { tracing::info!(?config, "Oracle received initial configuration") From 1dd287192c7fcbfd30124288e3857ec347e21388 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:39:32 +0100 Subject: [PATCH 1453/1995] Move oracle into its own module folder --- .../lib/node/ledger/ethereum_node/{oracle.rs => oracle/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/src/lib/node/ledger/ethereum_node/{oracle.rs => oracle/mod.rs} (100%) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs similarity index 100% rename from apps/src/lib/node/ledger/ethereum_node/oracle.rs rename to apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs From 36d366ed4fb78accace54a39a89b862ba85908d1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:43:09 +0100 Subject: [PATCH 1454/1995] Refactor --- .../ledger/ethereum_node/oracle/config.rs | 22 ++++++++++ .../ledger/ethereum_node/oracle/control.rs | 11 +++++ .../node/ledger/ethereum_node/oracle/mod.rs | 44 +++++-------------- apps/src/lib/node/ledger/mod.rs | 13 ++---- 4 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 apps/src/lib/node/ledger/ethereum_node/oracle/config.rs create mode 100644 apps/src/lib/node/ledger/ethereum_node/oracle/control.rs diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs new file mode 100644 index 0000000000..23ecaf5f05 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -0,0 +1,22 @@ +//! Configuration for an oracle. +use namada::types::ethereum_events::EthAddress; + +/// Configuration for an [`Oracle`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Config { + pub min_confirmations: u64, + pub mint_contract: EthAddress, + pub governance_contract: EthAddress, +} + +// TODO: this production Default implementation is temporary, there should be no +// default config - initialization should always be from storage +impl std::default::Default for Config { + fn default() -> Self { + Self { + min_confirmations: 100, + mint_contract: EthAddress([0; 20]), + governance_contract: EthAddress([1; 20]), + } + } +} diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs new file mode 100644 index 0000000000..bd27cfa9d0 --- /dev/null +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs @@ -0,0 +1,11 @@ +//! The oracle is controlled by sending commands over a channel. + +use super::config::Config; + +/// Commands used to configure and control an `Oracle`. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Command { + /// Initializes the oracle with the given configuration and immediately + /// starts it. + Initialize { config: Config }, +} diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 53ded46ee3..797528bd78 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -1,9 +1,12 @@ +pub mod config; +pub mod control; + use std::ops::Deref; use std::time::Duration; use clarity::Address; use eyre::{eyre, Result}; -use namada::types::ethereum_events::{EthAddress, EthereumEvent}; +use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; use tokio::sync::mpsc::{Receiver as BoundedReceiver, Sender as BoundedSender}; use tokio::sync::oneshot::Sender; @@ -11,38 +14,11 @@ use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; +use self::config::Config; use super::events::{signatures, PendingEvent}; #[cfg(test)] use super::test_tools::mock_web3_client::Web3; -/// Configuration for an [`Oracle`]. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct Config { - pub min_confirmations: u64, - pub mint_contract: EthAddress, - pub governance_contract: EthAddress, -} - -// TODO: this production Default implementation is temporary, there should be no -// default config - initialization should always be from storage -impl std::default::Default for Config { - fn default() -> Self { - Self { - min_confirmations: 100, - mint_contract: EthAddress([0; 20]), - governance_contract: EthAddress([1; 20]), - } - } -} - -/// Commands used to configure and control an `Oracle`. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub enum ControlCommand { - /// Initializes the oracle with the given configuration and immediately - /// starts it. - Initialize { config: Config }, -} - /// The default amount of time the oracle will wait between processing blocks const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); @@ -61,7 +37,7 @@ pub struct Oracle { /// How long the oracle should wait between checking blocks backoff: Duration, /// A channel for controlling and configuring the oracle. - control: BoundedReceiver, + control: BoundedReceiver, /// Configuration for this oracle. The oracle cannot run if it is not /// configured. config: Option, @@ -101,7 +77,7 @@ impl Oracle { sender: BoundedSender, abort: Sender<()>, backoff: Duration, - control: BoundedReceiver, + control: BoundedReceiver, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), @@ -142,7 +118,7 @@ impl Oracle { async fn await_initial_configuration(&mut self) -> Option { match self.control.recv().await { Some(cmd) => match cmd { - ControlCommand::Initialize { config } => { + control::Command::Initialize { config } => { self.config = Some(config); self.config } @@ -157,7 +133,7 @@ impl Oracle { pub fn run_oracle( url: impl AsRef, sender: BoundedSender, - control: BoundedReceiver, + control: BoundedReceiver, abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); @@ -404,7 +380,7 @@ fn process_queue( #[cfg(test)] mod test_oracle { - use namada::types::ethereum_events::TransferToEthereum; + use namada::types::ethereum_events::{EthAddress, TransferToEthereum}; use tokio::sync::oneshot::{channel, Receiver}; use tokio::time::timeout; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b093d9a2e3..c44b2861ed 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -25,7 +25,6 @@ use tower::ServiceBuilder; use self::abortable::AbortableSpawner; use self::ethereum_node::eth_fullnode; -use self::ethereum_node::oracle::ControlCommand; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::{ethereum_bridge, TendermintMode}; @@ -240,8 +239,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // TODO: pass oracle_control_send to the shell instead of initializing it // using a hardcoded config if let Err(error) = oracle_control_send - .send(oracle::ControlCommand::Initialize { - config: oracle::Config::default(), + .send(oracle::control::Command::Initialize { + config: oracle::config::Config::default(), }) .await { @@ -619,15 +618,11 @@ fn start_tendermint( }) } -/// Launches a new task managing a `geth` process into the asynchronous -/// runtime, and returns its [`task::JoinHandle`]. -/// -/// An oracle is also returned, along with its associated channel, -/// for receiving Ethereum events from `geth`. +/// Potentially starts an Ethereum event oracle. fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, - control_receiver: mpsc::Receiver, + control_receiver: mpsc::Receiver, ) -> (Option>, task::JoinHandle<()>) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { return (None, spawn_dummy_task(())); From c06ac416982a06bc54e172e87fe6d8cf88a71eb6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:46:55 +0100 Subject: [PATCH 1455/1995] Add a fn for creating an oracle control channel --- apps/src/lib/node/ledger/ethereum_node/oracle/control.rs | 8 ++++++++ apps/src/lib/node/ledger/mod.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs index bd27cfa9d0..f2111dc063 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs @@ -1,7 +1,15 @@ //! The oracle is controlled by sending commands over a channel. +use tokio::sync::mpsc; + use super::config::Config; +/// Returns two sides of a [`mpsc`] channel that can be used for controlling an +/// oracle. +pub fn channel() -> (mpsc::Sender, mpsc::Receiver) { + mpsc::channel(1) +} + /// Commands used to configure and control an `Oracle`. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Command { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index c44b2861ed..643abf2822 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -232,7 +232,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let (eth_node, abort_sender) = maybe_start_geth(&mut spawner, &config).await; - let (oracle_control_send, oracle_control_recv) = mpsc::channel(1); + let (oracle_control_send, oracle_control_recv) = oracle::control::channel(); // Start oracle if necessary let (eth_receiver, oracle) = maybe_start_ethereum_oracle(&config, abort_sender, oracle_control_recv); From 1ef77d5719c232e5fd6b9eb20ba0c4d0cf147692 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 16:59:12 +0100 Subject: [PATCH 1456/1995] Refactor --- apps/src/lib/node/ledger/mod.rs | 57 +++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 643abf2822..872524f639 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -232,19 +232,21 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let (eth_node, abort_sender) = maybe_start_geth(&mut spawner, &config).await; - let (oracle_control_send, oracle_control_recv) = oracle::control::channel(); // Start oracle if necessary - let (eth_receiver, oracle) = - maybe_start_ethereum_oracle(&config, abort_sender, oracle_control_recv); - // TODO: pass oracle_control_send to the shell instead of initializing it - // using a hardcoded config - if let Err(error) = oracle_control_send - .send(oracle::control::Command::Initialize { - config: oracle::config::Config::default(), - }) - .await - { - tracing::error!(?error, "Could not configure the oracle",); + let (eth_receiver, oracle_control_sender, oracle) = + maybe_start_ethereum_oracle(&config, abort_sender); + + // TODO: pass `oracle_control_sender` to the shell for initialization from + // storage, rather than using a hardcoded config + if let Some(oracle_control_sender) = oracle_control_sender { + if let Err(error) = oracle_control_sender + .send(oracle::control::Command::Initialize { + config: oracle::config::Config::default(), + }) + .await + { + tracing::error!(?error, "Could not configure the oracle",); + } } // Start ABCI server and broadcaster (the latter only if we are a validator @@ -622,36 +624,43 @@ fn start_tendermint( fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, - control_receiver: mpsc::Receiver, -) -> (Option>, task::JoinHandle<()>) { +) -> ( + Option>, + Option>, + task::JoinHandle<()>, +) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { - return (None, spawn_dummy_task(())); + return (None, None, spawn_dummy_task(())); } let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); // Start the oracle for listening to Ethereum events let (eth_sender, eth_receiver) = mpsc::channel(ORACLE_CHANNEL_BUFFER_SIZE); - let oracle = match config.ethereum_bridge.mode { + + match config.ethereum_bridge.mode { ethereum_bridge::ledger::Mode::Managed | ethereum_bridge::ledger::Mode::Remote => { - ethereum_node::oracle::run_oracle( + let (control_sender, control_receiver) = oracle::control::channel(); + let oracle = ethereum_node::oracle::run_oracle( ethereum_url, eth_sender, control_receiver, abort_sender, - ) + ); + (Some(eth_receiver), Some(control_sender), oracle) } ethereum_bridge::ledger::Mode::EventsEndpoint => { - ethereum_node::test_tools::events_endpoint::serve( + let oracle = ethereum_node::test_tools::events_endpoint::serve( eth_sender, abort_sender, - ) + ); + (Some(eth_receiver), None, oracle) } - ethereum_bridge::ledger::Mode::Off => spawn_dummy_task(()), - }; - - (Some(eth_receiver), oracle) + ethereum_bridge::ledger::Mode::Off => { + (None, None, spawn_dummy_task(())) + } + } } /// Launches a new task managing a `geth` process into the asynchronous From 89a2e212370d0e0ee1687bcdcc3b9ae212e3b9b3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 17:46:37 +0100 Subject: [PATCH 1457/1995] Fix up tests --- .../node/ledger/ethereum_node/oracle/mod.rs | 160 ++++++++++-------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 797528bd78..7498671e0f 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -8,7 +8,9 @@ use clarity::Address; use eyre::{eyre, Result}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; -use tokio::sync::mpsc::{Receiver as BoundedReceiver, Sender as BoundedSender}; +use tokio::sync::mpsc::{ + self, Receiver as BoundedReceiver, Sender as BoundedSender, +}; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -38,9 +40,6 @@ pub struct Oracle { backoff: Duration, /// A channel for controlling and configuring the oracle. control: BoundedReceiver, - /// Configuration for this oracle. The oracle cannot run if it is not - /// configured. - config: Option, } impl Deref for Oracle { @@ -85,7 +84,6 @@ impl Oracle { abort: Some(abort), backoff, control, - config: None, } } @@ -111,20 +109,19 @@ impl Oracle { async fn sleep(&self) { tokio::time::sleep(self.backoff).await; } +} - /// Block until the oracle is configured via the command channel. Returns - /// the initial config once configured, or `None` if the command channel is - /// closed. - async fn await_initial_configuration(&mut self) -> Option { - match self.control.recv().await { - Some(cmd) => match cmd { - control::Command::Initialize { config } => { - self.config = Some(config); - self.config - } - }, - None => None, - } +/// Block until an initial configuration is received via the command channel. +/// Returns the initial config once received, or `None` if the command channel +/// is closed. +async fn await_initial_configuration( + receiver: &mut mpsc::Receiver, +) -> Option { + match receiver.recv().await { + Some(cmd) => match cmd { + control::Command::Initialize { config } => Some(config), + }, + None => None, } } @@ -172,15 +169,19 @@ pub fn run_oracle( /// is reached, an event is forwarded to the ledger process async fn run_oracle_aux(mut oracle: Oracle) { tracing::info!("Oracle is awaiting initial configuration"); - match oracle.await_initial_configuration().await { + let config = match await_initial_configuration(&mut oracle.control).await { Some(config) => { - tracing::info!(?config, "Oracle received initial configuration") + tracing::info!(?config, "Oracle received initial configuration"); + config } - None => tracing::debug!( - "Oracle control channel was closed before the oracle could be \ - configured" - ), - } + None => { + tracing::debug!( + "Oracle control channel was closed before the oracle could be \ + configured" + ); + return; + } + }; // Initialize a queue to keep events which are awaiting a certain number of // confirmations @@ -196,7 +197,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { "Checking Ethereum block for bridge events" ); tokio::select! { - result = process(&oracle, &mut pending, next_block_to_process.clone()) => { + result = process(&oracle, &config, &mut pending, next_block_to_process.clone()) => { match result { Ok(()) => next_block_to_process += 1u8.into(), Err(error) => tracing::warn!( @@ -222,6 +223,7 @@ async fn run_oracle_aux(mut oracle: Oracle) { /// sends them to the oracle's `sender` channel async fn process( oracle: &Oracle, + config: &Config, pending: &mut Vec, block_to_process: Uint256, ) -> Result<()> { @@ -236,8 +238,8 @@ async fn process( )); } }; - let minimum_latest_block = block_to_process.clone() - + Uint256::from(oracle.config.unwrap().min_confirmations); + let minimum_latest_block = + block_to_process.clone() + Uint256::from(config.min_confirmations); if minimum_latest_block > latest_block { tracing::debug!( ?block_to_process, @@ -261,11 +263,9 @@ async fn process( // confirmations. for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => { - oracle.config.unwrap().mint_contract.0.into() - } + signatures::SigType::Bridge => config.mint_contract.0.into(), signatures::SigType::Governance => { - oracle.config.unwrap().governance_contract.0.into() + config.governance_contract.0.into() } }; tracing::debug!( @@ -343,7 +343,7 @@ async fn process( ?sig, pending = pending.len(), confirmed = confirmed.len(), - min_confirmations = ?oracle.config.unwrap().min_confirmations, + min_confirmations = ?config.min_confirmations, "Some events that have reached the minimum number of \ confirmations and will be sent onwards" ); @@ -380,6 +380,8 @@ fn process_queue( #[cfg(test)] mod test_oracle { + use std::thread::JoinHandle; + use namada::types::ethereum_events::{EthAddress, TransferToEthereum}; use tokio::sync::oneshot::{channel, Receiver}; use tokio::time::timeout; @@ -397,16 +399,35 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, + control_sender: tokio::sync::mpsc::Sender, blocks_processed_recv: tokio::sync::mpsc::UnboundedReceiver, abort_recv: Receiver<()>, } + /// Helper function that starts running the oracle in a new thread, and + /// initializes it with a simple default configuration that is appropriate + /// for tests. + async fn start_with_default_config( + oracle: Oracle, + control_sender: tokio::sync::mpsc::Sender, + ) -> JoinHandle<()> { + let handle = std::thread::spawn(move || { + tokio_test::block_on(run_oracle_aux(oracle)); + }); + control_sender + .send(control::Command::Initialize { + config: Config::default(), + }) + .await + .unwrap(); + handle + } + /// Set up an oracle with a mock web3 client that we can control fn setup() -> TestPackage { let (admin_channel, blocks_processed_recv, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); - let (_control_sender, control_receiver) = - tokio::sync::mpsc::channel(1000); + let (control_sender, control_receiver) = control::channel(); let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { @@ -416,10 +437,10 @@ mod test_oracle { // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), control: control_receiver, - config: Some(Config::default()), }, admin_channel, eth_recv: eth_receiver, + control_sender, blocks_processed_recv, abort_recv, } @@ -440,17 +461,16 @@ mod test_oracle { /// Test that if the fullnode stops, the oracle /// shuts down, even if the web3 client is unresponsive - #[test] - fn test_shutdown() { + #[tokio::test] + async fn test_shutdown() { let TestPackage { oracle, eth_recv, admin_channel, + control_sender, .. } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let oracle = start_with_default_config(oracle, control_sender).await; admin_channel .send(TestCmd::Unresponsive) .expect("Test failed"); @@ -460,18 +480,17 @@ mod test_oracle { /// Test that if no logs are received from the web3 /// client, no events are sent out - #[test] - fn test_no_logs_no_op() { + #[tokio::test] + async fn test_no_logs_no_op() { let TestPackage { oracle, mut eth_recv, admin_channel, blocks_processed_recv: _processed, + control_sender, .. } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let oracle = start_with_default_config(oracle, control_sender).await; admin_channel .send(TestCmd::NewHeight(Uint256::from(150u32))) .expect("Test failed"); @@ -488,18 +507,17 @@ mod test_oracle { /// Test that if a new block height doesn't increase, /// no events are sent out even if there are /// some in the logs. - #[test] - fn test_cant_get_new_height() { + #[tokio::test] + async fn test_cant_get_new_height() { let TestPackage { oracle, mut eth_recv, admin_channel, blocks_processed_recv: _processed, + control_sender, .. } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let oracle = start_with_default_config(oracle, control_sender).await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -531,18 +549,17 @@ mod test_oracle { /// Test that the oracle waits until new logs /// are received before sending them on. - #[test] - fn test_wait_on_new_logs() { + #[tokio::test] + async fn test_wait_on_new_logs() { let TestPackage { oracle, eth_recv, admin_channel, blocks_processed_recv: _processed, + control_sender, .. } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let oracle = start_with_default_config(oracle, control_sender).await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -581,25 +598,24 @@ mod test_oracle { } // check that when web3 becomes responsive, oracle sends event admin_channel.send(TestCmd::Normal).expect("Test failed"); - seen.blocking_recv().expect("Test failed"); + seen.await.expect("Test failed"); drop(eth_recv); oracle.join().expect("Test failed"); } /// Test that events are only sent when they /// reach the required number of confirmations - #[test] - fn test_finality_gadget() { + #[tokio::test] + async fn test_finality_gadget() { let TestPackage { oracle, mut eth_recv, admin_channel, blocks_processed_recv: _processed, + control_sender, .. } = setup(); - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let oracle = start_with_default_config(oracle, control_sender).await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -650,7 +666,7 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(200u32))) .expect("Test failed"); // check the correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); + let event = eth_recv.recv().await.expect("Test failed"); if let EthereumEvent::NewContract { name, address } = event { assert_eq!(name.as_str(), "Test"); assert_eq!(address, EthAddress([0; 20])); @@ -670,13 +686,13 @@ mod test_oracle { .send(TestCmd::NewHeight(Uint256::from(225u32))) .expect("Test failed"); // wait until event is emitted - seen_second.blocking_recv().expect("Test failed"); + seen_second.await.expect("Test failed"); // increase block height so second event is confirmed admin_channel .send(TestCmd::NewHeight(Uint256::from(250u32))) .expect("Test failed"); // check correct event is received - let event = eth_recv.blocking_recv().expect("Test failed"); + let event = eth_recv.recv().await.expect("Test failed"); if let EthereumEvent::TransfersToEthereum { mut transfers, .. } = event { assert_eq!(transfers.len(), 1); @@ -706,12 +722,11 @@ mod test_oracle { eth_recv, admin_channel, mut blocks_processed_recv, + control_sender, .. } = setup(); - let min_confirmations = oracle.config.unwrap().min_confirmations; - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let min_confirmations = 100u64; // TODO + let oracle = start_with_default_config(oracle, control_sender).await; // set the height of the chain such that there are some blocks deep // enough to be considered confirmed by the oracle @@ -769,12 +784,11 @@ mod test_oracle { eth_recv, admin_channel, mut blocks_processed_recv, + control_sender, .. } = setup(); - let min_confirmations = oracle.config.unwrap().min_confirmations; - let oracle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); - }); + let min_confirmations = 100u64; // TODO + let oracle = start_with_default_config(oracle, control_sender).await; let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations let synced_block_height = min_confirmations + confirmed_block_height; From 8736914ddca0c10ab2d08c2f19e06f5dd30c1202 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 17:49:34 +0100 Subject: [PATCH 1458/1995] Fix up tests --- .../node/ledger/ethereum_node/oracle/mod.rs | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 7498671e0f..49ba75664b 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -410,14 +410,13 @@ mod test_oracle { async fn start_with_default_config( oracle: Oracle, control_sender: tokio::sync::mpsc::Sender, + config: Config, ) -> JoinHandle<()> { let handle = std::thread::spawn(move || { tokio_test::block_on(run_oracle_aux(oracle)); }); control_sender - .send(control::Command::Initialize { - config: Config::default(), - }) + .send(control::Command::Initialize { config }) .await .unwrap(); handle @@ -470,7 +469,12 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config(oracle, control_sender).await; + let oracle = start_with_default_config( + oracle, + control_sender, + Config::default(), + ) + .await; admin_channel .send(TestCmd::Unresponsive) .expect("Test failed"); @@ -490,7 +494,12 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config(oracle, control_sender).await; + let oracle = start_with_default_config( + oracle, + control_sender, + Config::default(), + ) + .await; admin_channel .send(TestCmd::NewHeight(Uint256::from(150u32))) .expect("Test failed"); @@ -517,7 +526,12 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config(oracle, control_sender).await; + let oracle = start_with_default_config( + oracle, + control_sender, + Config::default(), + ) + .await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -559,7 +573,12 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config(oracle, control_sender).await; + let oracle = start_with_default_config( + oracle, + control_sender, + Config::default(), + ) + .await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -615,7 +634,12 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config(oracle, control_sender).await; + let oracle = start_with_default_config( + oracle, + control_sender, + Config::default(), + ) + .await; // Increase height above [`MIN_CONFIRMATIONS`] admin_channel .send(TestCmd::NewHeight(100u32.into())) @@ -725,13 +749,15 @@ mod test_oracle { control_sender, .. } = setup(); - let min_confirmations = 100u64; // TODO - let oracle = start_with_default_config(oracle, control_sender).await; + let config = Config::default(); + let oracle = + start_with_default_config(oracle, control_sender, config).await; // set the height of the chain such that there are some blocks deep // enough to be considered confirmed by the oracle let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations - let synced_block_height = min_confirmations + confirmed_block_height; + let synced_block_height = + config.min_confirmations + confirmed_block_height; for height in 0..synced_block_height + 1 { admin_channel .send(TestCmd::NewHeight(Uint256::from(height))) @@ -787,11 +813,13 @@ mod test_oracle { control_sender, .. } = setup(); - let min_confirmations = 100u64; // TODO - let oracle = start_with_default_config(oracle, control_sender).await; + let config = Config::default(); + let oracle = + start_with_default_config(oracle, control_sender, config).await; let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations - let synced_block_height = min_confirmations + confirmed_block_height; + let synced_block_height = + config.min_confirmations + confirmed_block_height; admin_channel .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); From d564243854bfd75ab9f1779963b6b381221018ac Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 17:54:00 +0100 Subject: [PATCH 1459/1995] Declare type aliases for control channel --- .../node/ledger/ethereum_node/oracle/control.rs | 7 ++++++- .../lib/node/ledger/ethereum_node/oracle/mod.rs | 16 +++++++--------- apps/src/lib/node/ledger/mod.rs | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs index f2111dc063..124bff4c5d 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs @@ -4,9 +4,14 @@ use tokio::sync::mpsc; use super::config::Config; +/// Used to send commands to an oracle. +pub type Sender = mpsc::Sender; +/// Used by an oracle to receive commands. +pub type Receiver = mpsc::Receiver; + /// Returns two sides of a [`mpsc`] channel that can be used for controlling an /// oracle. -pub fn channel() -> (mpsc::Sender, mpsc::Receiver) { +pub fn channel() -> (Sender, Receiver) { mpsc::channel(1) } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 49ba75664b..484ed55221 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -8,9 +8,7 @@ use clarity::Address; use eyre::{eyre, Result}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; -use tokio::sync::mpsc::{ - self, Receiver as BoundedReceiver, Sender as BoundedSender, -}; +use tokio::sync::mpsc::Sender as BoundedSender; use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] @@ -39,7 +37,7 @@ pub struct Oracle { /// How long the oracle should wait between checking blocks backoff: Duration, /// A channel for controlling and configuring the oracle. - control: BoundedReceiver, + control: control::Receiver, } impl Deref for Oracle { @@ -76,7 +74,7 @@ impl Oracle { sender: BoundedSender, abort: Sender<()>, backoff: Duration, - control: BoundedReceiver, + control: control::Receiver, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), @@ -115,7 +113,7 @@ impl Oracle { /// Returns the initial config once received, or `None` if the command channel /// is closed. async fn await_initial_configuration( - receiver: &mut mpsc::Receiver, + receiver: &mut control::Receiver, ) -> Option { match receiver.recv().await { Some(cmd) => match cmd { @@ -130,7 +128,7 @@ async fn await_initial_configuration( pub fn run_oracle( url: impl AsRef, sender: BoundedSender, - control: BoundedReceiver, + control: control::Receiver, abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); @@ -399,7 +397,7 @@ mod test_oracle { oracle: Oracle, admin_channel: tokio::sync::mpsc::UnboundedSender, eth_recv: tokio::sync::mpsc::Receiver, - control_sender: tokio::sync::mpsc::Sender, + control_sender: control::Sender, blocks_processed_recv: tokio::sync::mpsc::UnboundedReceiver, abort_recv: Receiver<()>, } @@ -409,7 +407,7 @@ mod test_oracle { /// for tests. async fn start_with_default_config( oracle: Oracle, - control_sender: tokio::sync::mpsc::Sender, + control_sender: control::Sender, config: Config, ) -> JoinHandle<()> { let handle = std::thread::spawn(move || { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 872524f639..0cbcc98a1a 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -626,7 +626,7 @@ fn maybe_start_ethereum_oracle( abort_sender: oneshot::Sender<()>, ) -> ( Option>, - Option>, + Option, task::JoinHandle<()>, ) { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { From e936235b9b49dc9f3d90e36c2ac4167a4b8a887e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 17:56:04 +0100 Subject: [PATCH 1460/1995] Pending event decoding should use configured min confirmations --- apps/src/lib/node/ledger/ethereum_node/events.rs | 12 +++++------- apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 885a50ff62..81456310a9 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -121,10 +121,8 @@ pub mod eth_events { signature: &str, block_height: Uint256, data: &[u8], + min_confirmations: u64, ) -> Result { - // TODO: use the value from storage rather than this hardcoded - // const! - const MIN_CONFIRMATIONS: u64 = 100; match signature { signatures::TRANSFER_TO_NAMADA_SIG => { RawTransfersToNamada::decode(data).map(|txs| PendingEvent { @@ -155,7 +153,7 @@ pub mod eth_events { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: MIN_CONFIRMATIONS.into(), + confirmations: min_confirmations.into(), block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -167,7 +165,7 @@ pub mod eth_events { } signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: MIN_CONFIRMATIONS.into(), + confirmations: min_confirmations.into(), block_height, event: EthereumEvent::NewContract { name, address }, }), @@ -175,7 +173,7 @@ pub mod eth_events { data, ) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: MIN_CONFIRMATIONS.into(), + confirmations: min_confirmations.into(), block_height, event: EthereumEvent::UpgradedContract { name, address }, }), @@ -183,7 +181,7 @@ pub mod eth_events { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| { PendingEvent { - confirmations: MIN_CONFIRMATIONS.into(), + confirmations: min_confirmations.into(), block_height, event: EthereumEvent::UpdateBridgeWhitelist { nonce, diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 484ed55221..aa1da21491 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -306,6 +306,7 @@ async fn process( sig, block_to_process.clone(), log.data.0.as_slice(), + config.min_confirmations, ) { Ok(event) => Some(event), Err(error) => { From 36908ae1fec95c0a0ae6b1fb73c6c6118eeb90c8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 18:00:07 +0100 Subject: [PATCH 1461/1995] Remove old references to MIN_CONFIRMATIONS --- .../ledger/ethereum_node/oracle/config.rs | 2 +- .../node/ledger/ethereum_node/oracle/mod.rs | 46 ++++++++----------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs index 23ecaf5f05..281e6bc3ff 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -14,7 +14,7 @@ pub struct Config { impl std::default::Default for Config { fn default() -> Self { Self { - min_confirmations: 100, + min_confirmations: 10, mint_contract: EthAddress([0; 20]), governance_contract: EthAddress([1; 20]), } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index aa1da21491..e1dbefe8ec 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -257,8 +257,8 @@ async fn process( ?latest_block, "Got latest Ethereum block height" ); - // check for events with at least `[MIN_CONFIRMATIONS]` - // confirmations. + // check for events in Ethereum blocks that have reached the minimum number + // of confirmations for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { signatures::SigType::Bridge => config.mint_contract.0.into(), @@ -525,15 +525,13 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config( - oracle, - control_sender, - Config::default(), - ) - .await; - // Increase height above [`MIN_CONFIRMATIONS`] + let mut config = Config::default(); + config.min_confirmations = 100; + let oracle = + start_with_default_config(oracle, control_sender, config).await; + // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(100u32.into())) + .send(TestCmd::NewHeight(config.min_confirmations.into())) .expect("Test failed"); let new_event = ChangedContract { @@ -572,15 +570,13 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config( - oracle, - control_sender, - Config::default(), - ) - .await; - // Increase height above [`MIN_CONFIRMATIONS`] + let mut config = Config::default(); + config.min_confirmations = 100; + let oracle = + start_with_default_config(oracle, control_sender, config).await; + // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(100u32.into())) + .send(TestCmd::NewHeight(config.min_confirmations.into())) .expect("Test failed"); // set the oracle to be unresponsive @@ -633,15 +629,13 @@ mod test_oracle { control_sender, .. } = setup(); - let oracle = start_with_default_config( - oracle, - control_sender, - Config::default(), - ) - .await; - // Increase height above [`MIN_CONFIRMATIONS`] + let mut config = Config::default(); + config.min_confirmations = 100; + let oracle = + start_with_default_config(oracle, control_sender, config).await; + // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(100u32.into())) + .send(TestCmd::NewHeight(config.min_confirmations.into())) .expect("Test failed"); // confirmed after 100 blocks From ce6662ccf8fd546eb7f0609dfd5eae180f8783c9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 18:11:09 +0100 Subject: [PATCH 1462/1995] PendingEvent::decode should accept a Uint256 min_confirmations --- apps/src/lib/node/ledger/ethereum_node/events.rs | 10 +++++----- apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 81456310a9..d2e98de2dd 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -121,7 +121,7 @@ pub mod eth_events { signature: &str, block_height: Uint256, data: &[u8], - min_confirmations: u64, + min_confirmations: Uint256, ) -> Result { match signature { signatures::TRANSFER_TO_NAMADA_SIG => { @@ -153,7 +153,7 @@ pub mod eth_events { bridge_validator_hash, governance_validator_hash, }| PendingEvent { - confirmations: min_confirmations.into(), + confirmations: min_confirmations, block_height, event: EthereumEvent::ValidatorSetUpdate { nonce, @@ -165,7 +165,7 @@ pub mod eth_events { } signatures::NEW_CONTRACT_SIG => ChangedContract::decode(data) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: min_confirmations.into(), + confirmations: min_confirmations, block_height, event: EthereumEvent::NewContract { name, address }, }), @@ -173,7 +173,7 @@ pub mod eth_events { data, ) .map(|ChangedContract { name, address }| PendingEvent { - confirmations: min_confirmations.into(), + confirmations: min_confirmations, block_height, event: EthereumEvent::UpgradedContract { name, address }, }), @@ -181,7 +181,7 @@ pub mod eth_events { UpdateBridgeWhitelist::decode(data).map( |UpdateBridgeWhitelist { nonce, whitelist }| { PendingEvent { - confirmations: min_confirmations.into(), + confirmations: min_confirmations, block_height, event: EthereumEvent::UpdateBridgeWhitelist { nonce, diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index e1dbefe8ec..c60362f07e 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -306,7 +306,7 @@ async fn process( sig, block_to_process.clone(), log.data.0.as_slice(), - config.min_confirmations, + config.min_confirmations.into(), ) { Ok(event) => Some(event), Err(error) => { From 759d89067c9f694f1d9bbce64b1062caf09fe7a6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 18:13:20 +0100 Subject: [PATCH 1463/1995] Default Config has min_confirmations = 100 --- apps/src/lib/node/ledger/ethereum_node/oracle/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs index 281e6bc3ff..23ecaf5f05 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -14,7 +14,7 @@ pub struct Config { impl std::default::Default for Config { fn default() -> Self { Self { - min_confirmations: 10, + min_confirmations: 100, mint_contract: EthAddress([0; 20]), governance_contract: EthAddress([1; 20]), } From 7bd4ca3f3a4a10f38c0b41ea2fa9a69fb94ef6a8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 18:45:26 +0100 Subject: [PATCH 1464/1995] Fix clippy --- .../node/ledger/ethereum_node/oracle/mod.rs | 18 ++++++++++++------ .../transactions/ethereum_events/mod.rs | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index c60362f07e..da05f3d0af 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -525,8 +525,10 @@ mod test_oracle { control_sender, .. } = setup(); - let mut config = Config::default(); - config.min_confirmations = 100; + let config = Config { + min_confirmations: 100, + ..Config::default() + }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations @@ -570,8 +572,10 @@ mod test_oracle { control_sender, .. } = setup(); - let mut config = Config::default(); - config.min_confirmations = 100; + let config = Config { + min_confirmations: 100, + ..Config::default() + }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations @@ -629,8 +633,10 @@ mod test_oracle { control_sender, .. } = setup(); - let mut config = Config::default(); - config.min_confirmations = 100; + let config = Config { + min_confirmations: 100, + ..Config::default() + }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index db955406f8..17b627f512 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -247,7 +247,7 @@ mod tests { nonce: arbitrary_nonce(), transfers: vec![TransferToNamada { amount, - asset: asset.clone(), + asset, receiver: receiver.clone(), }], }; From 5fa1c2303a1d9a40572ed9aa59a92b2c944e6f10 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 25 Oct 2022 18:48:47 +0100 Subject: [PATCH 1465/1995] Fix up docstrings --- apps/src/lib/node/ledger/ethereum_node/oracle/config.rs | 8 ++++++-- apps/src/lib/node/ledger/ethereum_node/oracle/control.rs | 5 ++--- apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs | 8 ++++---- apps/src/lib/node/ledger/mod.rs | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs index 23ecaf5f05..7c5e00c5da 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -1,16 +1,20 @@ //! Configuration for an oracle. use namada::types::ethereum_events::EthAddress; -/// Configuration for an [`Oracle`]. +/// Configuration for an oracle. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Config { + /// The minimum number of block confirmations an Ethereum block must have + /// before it will be checked for bridge events. pub min_confirmations: u64, + /// The Ethereum address of the current bridge contract. pub mint_contract: EthAddress, + /// The Ethereum address of the current governance contract. pub governance_contract: EthAddress, } // TODO: this production Default implementation is temporary, there should be no -// default config - initialization should always be from storage +// default config - initialization should always be from storage. impl std::default::Default for Config { fn default() -> Self { Self { diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs index 124bff4c5d..accf7d0a18 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/control.rs @@ -18,7 +18,6 @@ pub fn channel() -> (Sender, Receiver) { /// Commands used to configure and control an `Oracle`. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Command { - /// Initializes the oracle with the given configuration and immediately - /// starts it. - Initialize { config: Config }, + /// Sends a configuration to the oracle for it to use. + SendConfig { config: Config }, } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index da05f3d0af..882a324931 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -67,8 +67,8 @@ impl Drop for Oracle { } impl Oracle { - /// Construct a new [`Oracle`]. Note that it will not start until it has - /// been configured via the passed in `control` channel. + /// Construct a new [`Oracle`]. Note that it can not do anything until it + /// has been sent a configuration via the passed in `control` channel. pub fn new( url: &str, sender: BoundedSender, @@ -117,7 +117,7 @@ async fn await_initial_configuration( ) -> Option { match receiver.recv().await { Some(cmd) => match cmd { - control::Command::Initialize { config } => Some(config), + control::Command::SendConfig { config } => Some(config), }, None => None, } @@ -415,7 +415,7 @@ mod test_oracle { tokio_test::block_on(run_oracle_aux(oracle)); }); control_sender - .send(control::Command::Initialize { config }) + .send(control::Command::SendConfig { config }) .await .unwrap(); handle diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 0cbcc98a1a..b1efc1eca8 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -240,7 +240,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // storage, rather than using a hardcoded config if let Some(oracle_control_sender) = oracle_control_sender { if let Err(error) = oracle_control_sender - .send(oracle::control::Command::Initialize { + .send(oracle::control::Command::SendConfig { config: oracle::config::Config::default(), }) .await From 9969d159734ba45569289d7ca845a3de37dcd87f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 27 Oct 2022 13:00:05 +0100 Subject: [PATCH 1466/1995] Use NonZeroU64 type for min_confirmations --- .../ledger/ethereum_node/oracle/config.rs | 8 +++-- .../node/ledger/ethereum_node/oracle/mod.rs | 32 ++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs index 7c5e00c5da..5d4ea74851 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -1,4 +1,6 @@ //! Configuration for an oracle. +use std::num::NonZeroU64; + use namada::types::ethereum_events::EthAddress; /// Configuration for an oracle. @@ -6,7 +8,7 @@ use namada::types::ethereum_events::EthAddress; pub struct Config { /// The minimum number of block confirmations an Ethereum block must have /// before it will be checked for bridge events. - pub min_confirmations: u64, + pub min_confirmations: NonZeroU64, /// The Ethereum address of the current bridge contract. pub mint_contract: EthAddress, /// The Ethereum address of the current governance contract. @@ -18,7 +20,9 @@ pub struct Config { impl std::default::Default for Config { fn default() -> Self { Self { - min_confirmations: 100, + // SAFETY: we must always call NonZeroU64::new_unchecked here with a + // value that is >= 1 + min_confirmations: unsafe { NonZeroU64::new_unchecked(100) }, mint_contract: EthAddress([0; 20]), governance_contract: EthAddress([1; 20]), } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 882a324931..f29f363658 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -236,8 +236,8 @@ async fn process( )); } }; - let minimum_latest_block = - block_to_process.clone() + Uint256::from(config.min_confirmations); + let minimum_latest_block = block_to_process.clone() + + Uint256::from(u64::from(config.min_confirmations)); if minimum_latest_block > latest_block { tracing::debug!( ?block_to_process, @@ -306,7 +306,7 @@ async fn process( sig, block_to_process.clone(), log.data.0.as_slice(), - config.min_confirmations.into(), + u64::from(config.min_confirmations).into(), ) { Ok(event) => Some(event), Err(error) => { @@ -379,6 +379,7 @@ fn process_queue( #[cfg(test)] mod test_oracle { + use std::num::NonZeroU64; use std::thread::JoinHandle; use namada::types::ethereum_events::{EthAddress, TransferToEthereum}; @@ -526,14 +527,17 @@ mod test_oracle { .. } = setup(); let config = Config { - min_confirmations: 100, + min_confirmations: NonZeroU64::try_from(100) + .expect("Test wasn't set up correctly"), ..Config::default() }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(config.min_confirmations.into())) + .send(TestCmd::NewHeight( + u64::from(config.min_confirmations).into(), + )) .expect("Test failed"); let new_event = ChangedContract { @@ -573,14 +577,17 @@ mod test_oracle { .. } = setup(); let config = Config { - min_confirmations: 100, + min_confirmations: NonZeroU64::try_from(100) + .expect("Test wasn't set up correctly"), ..Config::default() }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(config.min_confirmations.into())) + .send(TestCmd::NewHeight( + u64::from(config.min_confirmations).into(), + )) .expect("Test failed"); // set the oracle to be unresponsive @@ -634,14 +641,17 @@ mod test_oracle { .. } = setup(); let config = Config { - min_confirmations: 100, + min_confirmations: NonZeroU64::try_from(100) + .expect("Test wasn't set up correctly"), ..Config::default() }; let oracle = start_with_default_config(oracle, control_sender, config).await; // Increase height above the configured minimum confirmations admin_channel - .send(TestCmd::NewHeight(config.min_confirmations.into())) + .send(TestCmd::NewHeight( + u64::from(config.min_confirmations).into(), + )) .expect("Test failed"); // confirmed after 100 blocks @@ -756,7 +766,7 @@ mod test_oracle { // enough to be considered confirmed by the oracle let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations let synced_block_height = - config.min_confirmations + confirmed_block_height; + u64::from(config.min_confirmations) + confirmed_block_height; for height in 0..synced_block_height + 1 { admin_channel .send(TestCmd::NewHeight(Uint256::from(height))) @@ -818,7 +828,7 @@ mod test_oracle { let confirmed_block_height = 9; // all blocks up to and including this block have enough confirmations let synced_block_height = - config.min_confirmations + confirmed_block_height; + u64::from(config.min_confirmations) + confirmed_block_height; admin_channel .send(TestCmd::NewHeight(Uint256::from(synced_block_height))) .expect("Test failed"); From 9d911173faf2aad532352b2d04aba0516e5f2057 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 27 Oct 2022 13:06:01 +0100 Subject: [PATCH 1467/1995] Don't directly hardcode configure the oracle in run_aux --- apps/src/lib/node/ledger/mod.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b1efc1eca8..0a350f967e 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -233,22 +233,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { maybe_start_geth(&mut spawner, &config).await; // Start oracle if necessary - let (eth_receiver, oracle_control_sender, oracle) = + let (eth_receiver, _oracle_control_sender, oracle) = maybe_start_ethereum_oracle(&config, abort_sender); - // TODO: pass `oracle_control_sender` to the shell for initialization from - // storage, rather than using a hardcoded config - if let Some(oracle_control_sender) = oracle_control_sender { - if let Err(error) = oracle_control_sender - .send(oracle::control::Command::SendConfig { - config: oracle::config::Config::default(), - }) - .await - { - tracing::error!(?error, "Could not configure the oracle",); - } - } - // Start ABCI server and broadcaster (the latter only if we are a validator // node) let (abci, broadcaster, shell_handler) = start_abci_broadcaster_shell( @@ -648,6 +635,17 @@ fn maybe_start_ethereum_oracle( control_receiver, abort_sender, ); + + // TODO(namada#686): pass `oracle_control_sender` to the shell for + // initialization from storage, rather than using a + // hardcoded config + if let Err(error) = control_sender.blocking_send( + oracle::control::Command::SendConfig { + config: oracle::config::Config::default(), + }, + ) { + tracing::error!(?error, "Could not configure the oracle",); + } (Some(eth_receiver), Some(control_sender), oracle) } ethereum_bridge::ledger::Mode::EventsEndpoint => { From 8db236ffdbf25d079e94bda5ad081f1e57a9fec8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 11:58:09 +0100 Subject: [PATCH 1468/1995] Rename mint_contract -> bridge_contract --- apps/src/lib/node/ledger/ethereum_node/oracle/config.rs | 4 ++-- apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs index 5d4ea74851..eaefa9aa84 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/config.rs @@ -10,7 +10,7 @@ pub struct Config { /// before it will be checked for bridge events. pub min_confirmations: NonZeroU64, /// The Ethereum address of the current bridge contract. - pub mint_contract: EthAddress, + pub bridge_contract: EthAddress, /// The Ethereum address of the current governance contract. pub governance_contract: EthAddress, } @@ -23,7 +23,7 @@ impl std::default::Default for Config { // SAFETY: we must always call NonZeroU64::new_unchecked here with a // value that is >= 1 min_confirmations: unsafe { NonZeroU64::new_unchecked(100) }, - mint_contract: EthAddress([0; 20]), + bridge_contract: EthAddress([0; 20]), governance_contract: EthAddress([1; 20]), } } diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index f29f363658..1efcca35c5 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -261,7 +261,7 @@ async fn process( // of confirmations for sig in signatures::SIGNATURES { let addr: Address = match signatures::SigType::from(sig) { - signatures::SigType::Bridge => config.mint_contract.0.into(), + signatures::SigType::Bridge => config.bridge_contract.0.into(), signatures::SigType::Governance => { config.governance_contract.0.into() } From 7f028e287590aa9cb3314246044c86c588591b9d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 11:59:24 +0100 Subject: [PATCH 1469/1995] Panic if we cannot configure the oracle --- apps/src/lib/node/ledger/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 0a350f967e..d996b90329 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -639,13 +639,11 @@ fn maybe_start_ethereum_oracle( // TODO(namada#686): pass `oracle_control_sender` to the shell for // initialization from storage, rather than using a // hardcoded config - if let Err(error) = control_sender.blocking_send( - oracle::control::Command::SendConfig { + control_sender + .blocking_send(oracle::control::Command::SendConfig { config: oracle::config::Config::default(), - }, - ) { - tracing::error!(?error, "Could not configure the oracle",); - } + }) + .expect("Could not send initial configuration to the oracle!"); (Some(eth_receiver), Some(control_sender), oracle) } ethereum_bridge::ledger::Mode::EventsEndpoint => { From 8312503ddb20271310913a96976d7bd2ec227c14 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 12:28:20 +0100 Subject: [PATCH 1470/1995] maybe_start_ethereum_oracle should return an enum type --- apps/src/lib/node/ledger/mod.rs | 62 +++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index d996b90329..dcdb6eee6f 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -233,8 +233,19 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { maybe_start_geth(&mut spawner, &config).await; // Start oracle if necessary - let (eth_receiver, _oracle_control_sender, oracle) = - maybe_start_ethereum_oracle(&config, abort_sender); + let (eth_receiver, oracle) = + match maybe_start_ethereum_oracle(&config, abort_sender) { + EthereumOracle::NotEnabled { handle } => (None, handle), + EthereumOracle::Oracle { + handle, + eth_receiver, + .. + } + | EthereumOracle::EventsEndpoint { + handle, + eth_receiver, + } => (Some(eth_receiver), handle), + }; // Start ABCI server and broadcaster (the latter only if we are a validator // node) @@ -607,17 +618,31 @@ fn start_tendermint( }) } +enum EthereumOracle { + NotEnabled { + handle: task::JoinHandle<()>, + }, + Oracle { + handle: task::JoinHandle<()>, + eth_receiver: mpsc::Receiver, + // TODO(namada#686): will be used by the Shell + _control_sender: oracle::control::Sender, + }, + EventsEndpoint { + handle: task::JoinHandle<()>, + eth_receiver: mpsc::Receiver, + }, +} + /// Potentially starts an Ethereum event oracle. fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, -) -> ( - Option>, - Option, - task::JoinHandle<()>, -) { +) -> EthereumOracle { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { - return (None, None, spawn_dummy_task(())); + return EthereumOracle::NotEnabled { + handle: spawn_dummy_task(()), + }; } let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); @@ -629,7 +654,7 @@ fn maybe_start_ethereum_oracle( ethereum_bridge::ledger::Mode::Managed | ethereum_bridge::ledger::Mode::Remote => { let (control_sender, control_receiver) = oracle::control::channel(); - let oracle = ethereum_node::oracle::run_oracle( + let handle = ethereum_node::oracle::run_oracle( ethereum_url, eth_sender, control_receiver, @@ -644,18 +669,25 @@ fn maybe_start_ethereum_oracle( config: oracle::config::Config::default(), }) .expect("Could not send initial configuration to the oracle!"); - (Some(eth_receiver), Some(control_sender), oracle) + EthereumOracle::Oracle { + handle, + eth_receiver, + _control_sender: control_sender, + } } ethereum_bridge::ledger::Mode::EventsEndpoint => { - let oracle = ethereum_node::test_tools::events_endpoint::serve( + let handle = ethereum_node::test_tools::events_endpoint::serve( eth_sender, abort_sender, ); - (Some(eth_receiver), None, oracle) - } - ethereum_bridge::ledger::Mode::Off => { - (None, None, spawn_dummy_task(())) + EthereumOracle::EventsEndpoint { + handle, + eth_receiver, + } } + ethereum_bridge::ledger::Mode::Off => EthereumOracle::NotEnabled { + handle: spawn_dummy_task(()), + }, } } From 171b317513167ad48acf6b91d93b384b37e1a313 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 28 Oct 2022 13:13:43 +0100 Subject: [PATCH 1471/1995] Rename EthereumOracle -> EthereumOracleTask --- apps/src/lib/node/ledger/mod.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index dcdb6eee6f..ef6d15b861 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -235,13 +235,13 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start oracle if necessary let (eth_receiver, oracle) = match maybe_start_ethereum_oracle(&config, abort_sender) { - EthereumOracle::NotEnabled { handle } => (None, handle), - EthereumOracle::Oracle { + EthereumOracleTask::NotEnabled { handle } => (None, handle), + EthereumOracleTask::Oracle { handle, eth_receiver, .. } - | EthereumOracle::EventsEndpoint { + | EthereumOracleTask::EventsEndpoint { handle, eth_receiver, } => (Some(eth_receiver), handle), @@ -618,7 +618,8 @@ fn start_tendermint( }) } -enum EthereumOracle { +/// Represents an Ethereum oracle task and associated channels. +enum EthereumOracleTask { NotEnabled { handle: task::JoinHandle<()>, }, @@ -638,9 +639,9 @@ enum EthereumOracle { fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, -) -> EthereumOracle { +) -> EthereumOracleTask { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { - return EthereumOracle::NotEnabled { + return EthereumOracleTask::NotEnabled { handle: spawn_dummy_task(()), }; } @@ -669,7 +670,7 @@ fn maybe_start_ethereum_oracle( config: oracle::config::Config::default(), }) .expect("Could not send initial configuration to the oracle!"); - EthereumOracle::Oracle { + EthereumOracleTask::Oracle { handle, eth_receiver, _control_sender: control_sender, @@ -680,12 +681,12 @@ fn maybe_start_ethereum_oracle( eth_sender, abort_sender, ); - EthereumOracle::EventsEndpoint { + EthereumOracleTask::EventsEndpoint { handle, eth_receiver, } } - ethereum_bridge::ledger::Mode::Off => EthereumOracle::NotEnabled { + ethereum_bridge::ledger::Mode::Off => EthereumOracleTask::NotEnabled { handle: spawn_dummy_task(()), }, } From f70b6b1f2affdb89f856bcd8c0951b5a4fb2fb77 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 3 Nov 2022 13:47:57 +0000 Subject: [PATCH 1472/1995] Remove blocking send from maybe_start_ethereum_oracle This was causing tokio to crash --- apps/src/lib/node/ledger/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index ef6d15b861..3324ed80da 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -234,7 +234,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start oracle if necessary let (eth_receiver, oracle) = - match maybe_start_ethereum_oracle(&config, abort_sender) { + match maybe_start_ethereum_oracle(&config, abort_sender).await { EthereumOracleTask::NotEnabled { handle } => (None, handle), EthereumOracleTask::Oracle { handle, @@ -636,7 +636,7 @@ enum EthereumOracleTask { } /// Potentially starts an Ethereum event oracle. -fn maybe_start_ethereum_oracle( +async fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, ) -> EthereumOracleTask { @@ -666,9 +666,10 @@ fn maybe_start_ethereum_oracle( // initialization from storage, rather than using a // hardcoded config control_sender - .blocking_send(oracle::control::Command::SendConfig { + .send(oracle::control::Command::SendConfig { config: oracle::config::Config::default(), }) + .await .expect("Could not send initial configuration to the oracle!"); EthereumOracleTask::Oracle { handle, From c74deb6fcf809b0ab9ccdba16cabd1e35c2c2389 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 3 Nov 2022 14:19:53 +0000 Subject: [PATCH 1473/1995] Return a dummy events channel even when Ethereum bridge is not enabled --- apps/src/lib/node/ledger/mod.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3324ed80da..fe75aa399c 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -235,8 +235,11 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start oracle if necessary let (eth_receiver, oracle) = match maybe_start_ethereum_oracle(&config, abort_sender).await { - EthereumOracleTask::NotEnabled { handle } => (None, handle), - EthereumOracleTask::Oracle { + EthereumOracleTask::NotEnabled { + handle, + eth_receiver, + } + | EthereumOracleTask::Oracle { handle, eth_receiver, .. @@ -621,7 +624,13 @@ fn start_tendermint( /// Represents an Ethereum oracle task and associated channels. enum EthereumOracleTask { NotEnabled { + // TODO(namada#459): we have to return a dummy handle for the moment, + // until `run_aux` is refactored handle: task::JoinHandle<()>, + // TODO(namada#521): we have to pass back a dummy channel here + // unfortunately, as validator shells still expect one even in the case + // where Ethereum bridge componentry is not enabled + eth_receiver: mpsc::Receiver, }, Oracle { handle: task::JoinHandle<()>, @@ -640,12 +649,6 @@ async fn maybe_start_ethereum_oracle( config: &config::Ledger, abort_sender: oneshot::Sender<()>, ) -> EthereumOracleTask { - if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) { - return EthereumOracleTask::NotEnabled { - handle: spawn_dummy_task(()), - }; - } - let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); // Start the oracle for listening to Ethereum events @@ -689,6 +692,7 @@ async fn maybe_start_ethereum_oracle( } ethereum_bridge::ledger::Mode::Off => EthereumOracleTask::NotEnabled { handle: spawn_dummy_task(()), + eth_receiver, }, } } From a4efa1cf0ffa92b35209cb12dd40c4221f99179d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Nov 2022 17:59:49 +0000 Subject: [PATCH 1474/1995] [ci] wasm checksums update --- wasm/checksums.json | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 08916c3fb9..7ea0df7638 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,15 @@ { - "tx_bond.wasm": "tx_bond.4385ddcf475bf5d03cfaa2cc6816a0741b3fd894a592354aae87b19135874e62.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.36d265ef84f11e82c304b965d9852c3aac72bd50061212e8be1d03b632579c1e.wasm", - "tx_init_account.wasm": "tx_init_account.9c0701134b0f1b3b17ff21560b8ee16a761aba2c371a5ab3c4763c7f1c3d8f42.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0246bc54874b4ecf664dbd9441c747b257996c14b229b8c5e74c2f62784a3243.wasm", - "tx_init_validator.wasm": "tx_init_validator.4ea7273d501c6be0f57ff411645fbb02bb35a8be84ec2f3305baf0b5a2d868d4.wasm", - "tx_transfer.wasm": "tx_transfer.ad40979d4a1518c9c7463648cc63212991a149f898e53423569d7eea82f73d09.wasm", - "tx_unbond.wasm": "tx_unbond.cc1425fbb1fc5f9c3db8940f3c8713d79d9224b936f2763acd5f77a129dbfdce.wasm", - "tx_update_vp.wasm": "tx_update_vp.74fbce3cf33900a5b5c6291db82b53bfe5ca2f0d5b6b87bc83667206746e0ca5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c2708194bb19f1b3a2f629dc22a9e11788d35e8a570b9eb77d32f27994280a36.wasm", - "tx_withdraw.wasm": "tx_withdraw.77ab8eb211d24b8713458adc248724c571371e7dfedf22a30f6a13c0c9110ed3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.66aa929d4f17249d9b1b4d3e0053a21f4bc46c0326c082f84e2e02161f00a392.wasm", - "vp_token.wasm": "vp_token.6ac1cf76dd3d8cfef8f2fd1400e2b2488c7553855f3fb009bb7199c085b1d249.wasm", - "vp_user.wasm": "vp_user.74ed31241805844c86e1185746b084ee326b53e6e773b4cdf7a5a925fa804280.wasm" + "tx_bond.wasm": "tx_bond.b75c924274bd962ba2013e8c46f5b769dca1468f14f8378d007b3bbc4e22fa1b.wasm", + "tx_ibc.wasm": "tx_ibc.4184076e93f30f928814f90cc7bb6794f15bd7c3303ecf550667ef238216f963.wasm", + "tx_init_account.wasm": "tx_init_account.318c8b1d89a3652fdd11aeb5cb4ced7619e41cc2fc93b2d4204977dfbb429aad.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.84b58188e652a1e545331e67704f80df263caf1785954a35b50e068aa76f8a2f.wasm", + "tx_init_validator.wasm": "tx_init_validator.5462e73244a1ebc3c152774a0f6b30c5470c81967ba5b9f3776aa29030ca3c04.wasm", + "tx_transfer.wasm": "tx_transfer.44ab1a21f251ae00684045ed1e4eb2d271986ac536e3f81863569f79768096e7.wasm", + "tx_unbond.wasm": "tx_unbond.cfdc392cddc2b07b10898274254f5f8c26f3025e0d839fda884b8dc0825d746e.wasm", + "tx_update_vp.wasm": "tx_update_vp.794a2e5f28c58cf9912d2daf657c5a57f7a33d140899d60ecf5e1db9264a9377.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.882d28fa65a8b55d884c00e87928cf6e645541ed486b5f279ffd10b5bca0d20c.wasm", + "tx_withdraw.wasm": "tx_withdraw.5a38dfd0016d29e73a492244c08c99400c7eeb948ee1926d0dd93aeed949eec6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.26435d515bc51079ab493775bfaa3b40fa079658fe4f12f9f23b25bffb3623fe.wasm", + "vp_token.wasm": "vp_token.f701a7f25b7cda689e28a2d8ac5fd8b805990f2d9befb57064195a36d6c446e0.wasm", + "vp_user.wasm": "vp_user.d131fdfe62991fab06523fdb2b1bf9f1d529f871c3c83bff8c1f979d477029b8.wasm" } \ No newline at end of file From 1efe36997621ed74acaee068e1d6bea96b4ba6b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 7 Nov 2022 10:14:58 +0000 Subject: [PATCH 1475/1995] Return Tokio join handle in oracle tests --- .../node/ledger/ethereum_node/oracle/mod.rs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index 1efcca35c5..ac86472336 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -380,7 +380,6 @@ fn process_queue( #[cfg(test)] mod test_oracle { use std::num::NonZeroU64; - use std::thread::JoinHandle; use namada::types::ethereum_events::{EthAddress, TransferToEthereum}; use tokio::sync::oneshot::{channel, Receiver}; @@ -411,9 +410,16 @@ mod test_oracle { oracle: Oracle, control_sender: control::Sender, config: Config, - ) -> JoinHandle<()> { - let handle = std::thread::spawn(move || { - tokio_test::block_on(run_oracle_aux(oracle)); + ) -> tokio::task::JoinHandle<()> { + let handle = tokio::task::spawn_blocking(move || { + let rt = tokio::runtime::Handle::current(); + rt.block_on(async move { + LocalSet::new() + .run_until(async move { + run_oracle_aux(oracle).await; + }) + .await + }); }); control_sender .send(control::Command::SendConfig { config }) @@ -479,7 +485,7 @@ mod test_oracle { .send(TestCmd::Unresponsive) .expect("Test failed"); drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that if no logs are received from the web3 @@ -510,7 +516,7 @@ mod test_oracle { time -= std::time::Duration::from_millis(10); } drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that if a new block height doesn't increase, @@ -561,7 +567,7 @@ mod test_oracle { time -= std::time::Duration::from_millis(10); } drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that the oracle waits until new logs @@ -625,7 +631,7 @@ mod test_oracle { admin_channel.send(TestCmd::Normal).expect("Test failed"); seen.await.expect("Test failed"); drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that events are only sent when they @@ -743,7 +749,7 @@ mod test_oracle { } drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that Ethereum blocks are processed in sequence up to the latest @@ -806,7 +812,7 @@ mod test_oracle { assert_eq!(block_processed, Uint256::from(confirmed_block_height + 1)); drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } /// Test that if the Ethereum RPC endpoint returns a latest block that is @@ -865,6 +871,6 @@ mod test_oracle { } drop(eth_recv); - oracle.join().expect("Test failed"); + oracle.await.expect("Test failed"); } } From 7e33a99ad8495f00e5ee2377594446efcf05d929 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 8 Nov 2022 12:38:38 +0000 Subject: [PATCH 1476/1995] [ci] wasm checksums update --- wasm/checksums.json | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7ea0df7638..08916c3fb9 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,16 @@ { - "tx_bond.wasm": "tx_bond.b75c924274bd962ba2013e8c46f5b769dca1468f14f8378d007b3bbc4e22fa1b.wasm", - "tx_ibc.wasm": "tx_ibc.4184076e93f30f928814f90cc7bb6794f15bd7c3303ecf550667ef238216f963.wasm", - "tx_init_account.wasm": "tx_init_account.318c8b1d89a3652fdd11aeb5cb4ced7619e41cc2fc93b2d4204977dfbb429aad.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.84b58188e652a1e545331e67704f80df263caf1785954a35b50e068aa76f8a2f.wasm", - "tx_init_validator.wasm": "tx_init_validator.5462e73244a1ebc3c152774a0f6b30c5470c81967ba5b9f3776aa29030ca3c04.wasm", - "tx_transfer.wasm": "tx_transfer.44ab1a21f251ae00684045ed1e4eb2d271986ac536e3f81863569f79768096e7.wasm", - "tx_unbond.wasm": "tx_unbond.cfdc392cddc2b07b10898274254f5f8c26f3025e0d839fda884b8dc0825d746e.wasm", - "tx_update_vp.wasm": "tx_update_vp.794a2e5f28c58cf9912d2daf657c5a57f7a33d140899d60ecf5e1db9264a9377.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.882d28fa65a8b55d884c00e87928cf6e645541ed486b5f279ffd10b5bca0d20c.wasm", - "tx_withdraw.wasm": "tx_withdraw.5a38dfd0016d29e73a492244c08c99400c7eeb948ee1926d0dd93aeed949eec6.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.26435d515bc51079ab493775bfaa3b40fa079658fe4f12f9f23b25bffb3623fe.wasm", - "vp_token.wasm": "vp_token.f701a7f25b7cda689e28a2d8ac5fd8b805990f2d9befb57064195a36d6c446e0.wasm", - "vp_user.wasm": "vp_user.d131fdfe62991fab06523fdb2b1bf9f1d529f871c3c83bff8c1f979d477029b8.wasm" + "tx_bond.wasm": "tx_bond.4385ddcf475bf5d03cfaa2cc6816a0741b3fd894a592354aae87b19135874e62.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_ibc.wasm": "tx_ibc.36d265ef84f11e82c304b965d9852c3aac72bd50061212e8be1d03b632579c1e.wasm", + "tx_init_account.wasm": "tx_init_account.9c0701134b0f1b3b17ff21560b8ee16a761aba2c371a5ab3c4763c7f1c3d8f42.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0246bc54874b4ecf664dbd9441c747b257996c14b229b8c5e74c2f62784a3243.wasm", + "tx_init_validator.wasm": "tx_init_validator.4ea7273d501c6be0f57ff411645fbb02bb35a8be84ec2f3305baf0b5a2d868d4.wasm", + "tx_transfer.wasm": "tx_transfer.ad40979d4a1518c9c7463648cc63212991a149f898e53423569d7eea82f73d09.wasm", + "tx_unbond.wasm": "tx_unbond.cc1425fbb1fc5f9c3db8940f3c8713d79d9224b936f2763acd5f77a129dbfdce.wasm", + "tx_update_vp.wasm": "tx_update_vp.74fbce3cf33900a5b5c6291db82b53bfe5ca2f0d5b6b87bc83667206746e0ca5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c2708194bb19f1b3a2f629dc22a9e11788d35e8a570b9eb77d32f27994280a36.wasm", + "tx_withdraw.wasm": "tx_withdraw.77ab8eb211d24b8713458adc248724c571371e7dfedf22a30f6a13c0c9110ed3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.66aa929d4f17249d9b1b4d3e0053a21f4bc46c0326c082f84e2e02161f00a392.wasm", + "vp_token.wasm": "vp_token.6ac1cf76dd3d8cfef8f2fd1400e2b2488c7553855f3fb009bb7199c085b1d249.wasm", + "vp_user.wasm": "vp_user.74ed31241805844c86e1185746b084ee326b53e6e773b4cdf7a5a925fa804280.wasm" } \ No newline at end of file From 2674b649925225024d265df49e9419058f708a91 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 8 Nov 2022 14:27:52 +0100 Subject: [PATCH 1477/1995] [fix]: Enriched an error msg, removed stray wallet cmds from the namadar cli binary --- apps/src/lib/cli.rs | 4 +-- shared/src/ledger/queries/shell.rs | 39 +++++++++++++++++++----------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f7205f78e7..7749b1320e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1261,7 +1261,7 @@ pub mod cmds { pub struct AddToEthBridgePool(pub args::EthereumBridgePool); impl SubCmd for AddToEthBridgePool { - const CMD: &'static str = "add-transfer"; + const CMD: &'static str = "add-erc20-transfer"; fn parse(matches: &ArgMatches) -> Option { matches @@ -2954,5 +2954,5 @@ fn anoma_relayer_app() -> App { .version(anoma_version()) .about("Anoma Ethereum bridge pool command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); - cmds::AnomaWallet::add_sub(args::Global::def(app)) + cmds::EthBridgePool::add_sub(args::Global::def(app)) } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index b891be7b09..61b4610744 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -10,7 +10,7 @@ use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, MerkleTree, StoreRef, StoreType, DB}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::ledger::storage_api::{self, CustomError, ResultExt, StorageRead}; use crate::types::eth_abi::EncodeCell; use crate::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -376,22 +376,33 @@ where ), ); // from the hashes of the transfers, get the actual values. - let keys: Vec<_> = - transfer_hashes.iter().map(get_key_from_hash).collect(); - let values: Vec = keys + let mut missing_hashes = vec![]; + let (keys, values): (Vec<_>, Vec) = transfer_hashes .iter() - .filter_map(|key| match ctx.storage.read(key) { - Ok((Some(bytes), _)) => { - BorshDeserialize::try_from_slice(&bytes[..]).ok() + .filter_map(|hash| { + let key = get_key_from_hash(hash); + match ctx.storage.read(&key) { + Ok((Some(bytes), _)) => { + PendingTransfer::try_from_slice(&bytes[..]) + .ok() + .map(|transfer| (key, transfer)) + } + _ => { + missing_hashes.push(hash); + None + } } - _ => None, }) - .collect(); - if values.len() != keys.len() { - return Err(storage_api::Error::SimpleMessage( - "One or more of the provided hashes had no corresponding \ - transfer in storage.", - )); + .unzip(); + if !missing_hashes.is_empty() { + return Err(storage_api::Error::Custom(CustomError( + format!( + "One or more of the provided hashes had no corresponding \ + transfer in storage: {:?}", + missing_hashes + ) + .into(), + ))); } // get the membership proof match tree.get_sub_tree_existence_proof( From 7bca9b5d55060b3cde59c49b6fa35da986918313 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 8 Nov 2022 15:12:11 +0100 Subject: [PATCH 1478/1995] [fix]: Renaming xan to nam --- tests/src/native_vp/eth_bridge_pool.rs | 8 ++++---- wasm/wasm_source/src/tx_bridge_pool.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index f2045177c8..f65595dbe3 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -11,7 +11,7 @@ mod test_bridge_pool_vp { use namada::ledger::eth_bridge::storage::wrapped_erc20s; use namada::ledger::eth_bridge::ADDRESS; use namada::proto::Tx; - use namada::types::address::{wnam, xan}; + use namada::types::address::{nam, wnam}; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; @@ -80,18 +80,18 @@ mod test_bridge_pool_vp { // initialize Ethereum bridge storage config.init_storage(&mut env.storage); // initialize Bertha's account - env.spawn_accounts([&albert_address(), &bertha_address(), &xan()]); + env.spawn_accounts([&albert_address(), &bertha_address(), &nam()]); // enrich Albert env.credit_tokens( &albert_address(), - &xan(), + &nam(), None, BERTHA_WEALTH.into(), ); // enrich Bertha env.credit_tokens( &bertha_address(), - &xan(), + &nam(), None, BERTHA_WEALTH.into(), ); diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 768efc39bf..f3dd77dad6 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -16,7 +16,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { ctx, payer, &bridge_pool::BRIDGE_POOL_ADDRESS, - &address::xan(), + &address::nam(), None, amount, )?; @@ -32,7 +32,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { ctx, sender, ð_bridge::ADDRESS, - &address::xan(), + &address::nam(), None, amount, )?; From cf2b06fe638b9adb92a4d316e1de166173554659 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 8 Nov 2022 16:07:14 +0000 Subject: [PATCH 1479/1995] Phase out usage of temp errors in storage --- shared/src/types/storage.rs | 64 +++++++++++++++---------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b525828ff0..b0fc499221 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -27,18 +27,20 @@ use crate::types::time::DateTimeUtc; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { - #[error("TEMPORARY error: {error}")] - Temporary { error: String }, #[error("Error parsing address: {0}")] ParseAddress(address::Error), #[error("Error parsing address from a storage key")] ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), - #[error("Error parsing key segment {0}")] + #[error("Error parsing key segment: {0}")] ParseKeySeg(String), - #[error("Could not parse string into a key segment: {0}")] - ParseError(String), + #[error("Error parsing block hash: {0}")] + ParseBlockHash(String), + #[error("The key is empty")] + EmptyKey, + #[error("They key is missing sub-key segments: {0}")] + MissingSegments(String), } /// Result for functions that may fail @@ -145,13 +147,11 @@ impl TryFrom<&[u8]> for BlockHash { fn try_from(value: &[u8]) -> Result { if value.len() != BLOCK_HASH_LENGTH { - return Err(Error::Temporary { - error: format!( - "Unexpected block hash length {}, expected {}", - value.len(), - BLOCK_HASH_LENGTH - ), - }); + return Err(Error::ParseBlockHash(format!( + "Unexpected block hash length {}, expected {}", + value.len(), + BLOCK_HASH_LENGTH + ))); } let mut hash = [0; 32]; hash.copy_from_slice(value); @@ -163,18 +163,7 @@ impl TryFrom> for BlockHash { type Error = self::Error; fn try_from(value: Vec) -> Result { - if value.len() != BLOCK_HASH_LENGTH { - return Err(Error::Temporary { - error: format!( - "Unexpected block hash length {}, expected {}", - value.len(), - BLOCK_HASH_LENGTH - ), - }); - } - let mut hash = [0; 32]; - hash.copy_from_slice(&value); - Ok(BlockHash(hash)) + value.as_slice().try_into() } } @@ -517,21 +506,14 @@ impl Key { match self.segments.split_first() { Some((_, rest)) => { if rest.is_empty() { - Err(Error::Temporary { - error: format!( - "The key doesn't have the sub segments: {}", - self - ), - }) + Err(Error::MissingSegments(format!("{self}"))) } else { Ok(Self { segments: rest.to_vec(), }) } } - None => Err(Error::Temporary { - error: "The key is empty".to_owned(), - }), + None => Err(Error::EmptyKey), } } @@ -693,12 +675,16 @@ impl KeySeg for Epoch { string .split_once('=') .and_then(|(prefix, epoch)| (prefix == "E").then(|| epoch)) - .ok_or_else(|| Error::Temporary { - error: format!("Invalid epoch prefix on key: {string}"), + .ok_or_else(|| { + Error::ParseKeySeg(format!( + "Invalid epoch prefix on key: {string}" + )) }) .and_then(|epoch| { - epoch.parse::().map_err(|e| Error::Temporary { - error: format!("Unexpected epoch value {epoch}, {e}"), + epoch.parse::().map_err(|e| { + Error::ParseKeySeg(format!( + "Unexpected epoch value {epoch}, {e}" + )) }) }) .map(Epoch) @@ -737,7 +723,7 @@ impl KeySeg for Address { impl KeySeg for Hash { fn parse(seg: String) -> Result { seg.try_into().map_err(|e: crate::types::hash::Error| { - Error::ParseError(e.to_string()) + Error::ParseKeySeg(e.to_string()) }) } @@ -753,7 +739,7 @@ impl KeySeg for Hash { impl KeySeg for KeccakHash { fn parse(seg: String) -> Result { seg.try_into() - .map_err(|e: TryFromError| Error::ParseError(e.to_string())) + .map_err(|e: TryFromError| Error::ParseKeySeg(e.to_string())) } fn raw(&self) -> String { From 1aa55f4569d14b276d5a033207bc1747083bdf71 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 10:25:53 +0000 Subject: [PATCH 1480/1995] Remove duplicate @ --- .github/workflows/build-and-test-bridge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 0ed0437ce4..b09b4d0b3c 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -200,7 +200,7 @@ jobs: env: RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Wait for release binaries - uses: lewagon/wait-on-check-action@@v1.2.0 + uses: lewagon/wait-on-check-action@v1.2.0 with: ref: ${{ github.event.pull_request.head.sha || github.ref }} check-name: ${{ matrix.make.wait_for }} From ff81e8853f0d7bc53a17b743e133f9ab295cc79c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 10:26:06 +0000 Subject: [PATCH 1481/1995] Update wasm image --- .github/workflows/build-and-test-bridge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index b09b4d0b3c..9f7e4be590 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -29,7 +29,7 @@ jobs: timeout-minutes: 30 runs-on: ${{ matrix.os }} container: - image: ghcr.io/anoma/namada:wasm-0.6.1 + image: ghcr.io/anoma/namada:wasm-0.8.0 strategy: fail-fast: false matrix: From 446856cffe0dee01d80832c076868882bd471c6a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 10:28:41 +0000 Subject: [PATCH 1482/1995] Reorder sccache step --- .github/workflows/build-and-test-bridge.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 9f7e4be590..dd5d87265e 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -327,12 +327,12 @@ jobs: ~/.cargo/git key: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- - - name: Start sccache server - run: sccache --start-server - name: Install mold linker run: | wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin + - name: Start sccache server + run: sccache --start-server - name: Build run: make build-release${{ matrix.make.suffix }} env: From d367b6f6031a9dbba51edb8c0bf4a2446ee012e0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 10:39:27 +0000 Subject: [PATCH 1483/1995] Add changelog --- .changelog/unreleased/bug-fixes/765-ethbridge-ci-fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/bug-fixes/765-ethbridge-ci-fix.md diff --git a/.changelog/unreleased/bug-fixes/765-ethbridge-ci-fix.md b/.changelog/unreleased/bug-fixes/765-ethbridge-ci-fix.md new file mode 100644 index 0000000000..9d8b9bd8bd --- /dev/null +++ b/.changelog/unreleased/bug-fixes/765-ethbridge-ci-fix.md @@ -0,0 +1 @@ +- Fix to run again ([#765](https://github.com/anoma/namada/pull/765)) \ No newline at end of file From 1bd7f16aec1b62ea27a997ddb35b9dd911fb04ff Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 10:11:42 +0000 Subject: [PATCH 1484/1995] Revert changes to namadac tx arguments --- apps/src/lib/cli.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 7749b1320e..2d6cdd0e52 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1380,13 +1380,11 @@ pub mod args { const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const FEE_PAYER: Arg = arg("fee-payer"); + const FEE_TOKEN: ArgDefaultFromCtx = + arg_default_from_ctx("fee-token", DefaultFn(|| "NAM".into())); const FORCE: ArgFlag = flag("force"); - const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); - const GAS_TOKEN: ArgDefaultFromCtx = - arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); const HASH_LIST: Arg = arg("hash-list"); @@ -2413,10 +2411,10 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(GAS_AMOUNT.def().about( + .arg(FEE_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(GAS_TOKEN.def().about("The token for paying the fee")) + .arg(FEE_TOKEN.def().about("The token for paying the fee")) .arg( GAS_LIMIT.def().about( "The maximum amount of gas needed to run transaction", @@ -2449,8 +2447,8 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = GAS_AMOUNT.parse(matches); - let fee_token = GAS_TOKEN.parse(matches); + let fee_amount = FEE_AMOUNT.parse(matches); + let fee_token = FEE_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); let signing_key = SIGNING_KEY_OPT.parse(matches); From d0057645eca45c4482400530a7f50b3f53f3688d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Nov 2022 13:27:41 +0000 Subject: [PATCH 1485/1995] Update wasm checksums --- wasm/checksums.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 08916c3fb9..29297d77a1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.4385ddcf475bf5d03cfaa2cc6816a0741b3fd894a592354aae87b19135874e62.wasm", + "tx_bond.wasm": "tx_bond.a2d03adca103b0acc5997b926990ff4de4650268203e9d3350e004eab074634a.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.36d265ef84f11e82c304b965d9852c3aac72bd50061212e8be1d03b632579c1e.wasm", - "tx_init_account.wasm": "tx_init_account.9c0701134b0f1b3b17ff21560b8ee16a761aba2c371a5ab3c4763c7f1c3d8f42.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0246bc54874b4ecf664dbd9441c747b257996c14b229b8c5e74c2f62784a3243.wasm", - "tx_init_validator.wasm": "tx_init_validator.4ea7273d501c6be0f57ff411645fbb02bb35a8be84ec2f3305baf0b5a2d868d4.wasm", - "tx_transfer.wasm": "tx_transfer.ad40979d4a1518c9c7463648cc63212991a149f898e53423569d7eea82f73d09.wasm", - "tx_unbond.wasm": "tx_unbond.cc1425fbb1fc5f9c3db8940f3c8713d79d9224b936f2763acd5f77a129dbfdce.wasm", - "tx_update_vp.wasm": "tx_update_vp.74fbce3cf33900a5b5c6291db82b53bfe5ca2f0d5b6b87bc83667206746e0ca5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c2708194bb19f1b3a2f629dc22a9e11788d35e8a570b9eb77d32f27994280a36.wasm", - "tx_withdraw.wasm": "tx_withdraw.77ab8eb211d24b8713458adc248724c571371e7dfedf22a30f6a13c0c9110ed3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.66aa929d4f17249d9b1b4d3e0053a21f4bc46c0326c082f84e2e02161f00a392.wasm", - "vp_token.wasm": "vp_token.6ac1cf76dd3d8cfef8f2fd1400e2b2488c7553855f3fb009bb7199c085b1d249.wasm", - "vp_user.wasm": "vp_user.74ed31241805844c86e1185746b084ee326b53e6e773b4cdf7a5a925fa804280.wasm" + "tx_ibc.wasm": "tx_ibc.2b467845c55820345ce48e2da4f7c2606566555eac12ee64f8baeb5b63dfae83.wasm", + "tx_init_account.wasm": "tx_init_account.86e02762f60428966c7ab4f780fc4dbcc794ae0aa791930f27e246c2c289e045.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f654f0e4610e03c9ffcc36225f67822200aa89f3633f16a9eb0b47813f8ec15a.wasm", + "tx_init_validator.wasm": "tx_init_validator.b4e6713e69aa341339a836de6901c7c29a532849577514255ecb3092882a12c3.wasm", + "tx_transfer.wasm": "tx_transfer.e3c687a17173af07b4be0fc0af06ebe6dbba1d5195b8e3015c955eeaaa53e59e.wasm", + "tx_unbond.wasm": "tx_unbond.4480c046edf32bf97d2d40d45b2bd05547049cae22df48f3fc433f151b495e2b.wasm", + "tx_update_vp.wasm": "tx_update_vp.b057de2c9f17a634aaf011f6bf6bb0bac198ae522c5a24a44dec71d9b649c7e7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4625c309121620a10b9b87b4f4622b8a899f758c08d5ae0341a6f80ce932e089.wasm", + "tx_withdraw.wasm": "tx_withdraw.6ee49918852f375271a444c111b73ddcf20e237af852579b4fcc4caab17a2715.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e417000da4f7640a5fb5f124755ed02b8654aa67db600bea6e93e1f04e6d71a3.wasm", + "vp_token.wasm": "vp_token.37749e16d9fcd17dc2cf3351b0a140b5821668f80d6cc805caeb3e8e9af06cd9.wasm", + "vp_user.wasm": "vp_user.8c117faa94e44833fb7a16d13067f7626ecfca6a70fec1b7822c7d9a6c3e909e.wasm" } \ No newline at end of file From 8b9402cea4799fe78888f3126821d45fe0d162e4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 14:34:42 +0000 Subject: [PATCH 1486/1995] Log signature when validation fails --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 1 + apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index eb76eed09f..caa9070d01 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -129,6 +129,7 @@ where .map_err(|err| { tracing::error!( ?err, + ?ext.sig, %validator, "Failed to verify the signature of an Ethereum events vote \ extension issued by some validator" diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index c3c1ac9221..f8519b4704 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -150,6 +150,7 @@ where .map_err(|err| { tracing::error!( ?err, + ?ext.sig, %validator, "Failed to verify the signature of a valset upd vote \ extension issued by some validator" From 6348a8e7880fce8059a50afc59ad1f79430900d8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 14:43:23 +0000 Subject: [PATCH 1487/1995] Log existing signature when building vext digests --- .../lib/node/ledger/shell/vote_extensions/eth_events.rs | 8 ++++++-- .../node/ledger/shell/vote_extensions/val_set_update.rs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index caa9070d01..67a33126a9 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -263,12 +263,16 @@ where } #[cfg(not(feature = "abcipp"))] - if let Some(sig) = signatures.insert((addr, block_height), sig) { + if let Some(existing_sig) = + signatures.insert((addr, block_height), sig.clone()) + { tracing::warn!( ?sig, + ?existing_sig, ?validator_addr, "Overwrote old signature from validator while \ - constructing ethereum_events::VextDigest" + constructing ethereum_events::VextDigest - maybe private \ + key of validator is being used by multiple nodes?" ); } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index f8519b4704..b5ec5f97bb 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -266,12 +266,16 @@ where } #[cfg(not(feature = "abcipp"))] - if let Some(sig) = signatures.insert((addr, block_height), sig) { + if let Some(existing_sig) = + signatures.insert((addr, block_height), sig.clone()) + { tracing::warn!( ?sig, + ?existing_sig, ?validator_addr, "Overwrote old signature from validator while \ - constructing validator_set_update::VextDigest" + constructing validator_set_update::VextDigest - maybe \ + private key of validator is being used by multiple nodes?" ); } } From 5236536608746687ebcde9483a8e6e14dc19ac23 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 14:51:19 +0000 Subject: [PATCH 1488/1995] Log public key when signature verification fails --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 1 + apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 67a33126a9..8b7b0e0f16 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -130,6 +130,7 @@ where tracing::error!( ?err, ?ext.sig, + ?pk, %validator, "Failed to verify the signature of an Ethereum events vote \ extension issued by some validator" diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index b5ec5f97bb..e8e9586321 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -151,6 +151,7 @@ where tracing::error!( ?err, ?ext.sig, + ?pk, %validator, "Failed to verify the signature of a valset upd vote \ extension issued by some validator" From 0a1ddc8619c826a6c291e15e89ef42fd21f58a26 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 14:54:13 +0000 Subject: [PATCH 1489/1995] Log inserting signatures into vext digests --- .../node/ledger/shell/vote_extensions/eth_events.rs | 11 ++++++++--- .../ledger/shell/vote_extensions/val_set_update.rs | 12 ++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 8b7b0e0f16..076687ec56 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -263,10 +263,15 @@ where ); } + let key = (addr, block_height); + tracing::debug!( + ?key, + ?sig, + ?validator_addr, + "Inserting signature into ethereum_events::VextDigest" + ); #[cfg(not(feature = "abcipp"))] - if let Some(existing_sig) = - signatures.insert((addr, block_height), sig.clone()) - { + if let Some(existing_sig) = signatures.insert(key, sig.clone()) { tracing::warn!( ?sig, ?existing_sig, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index e8e9586321..985c3ba2a5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -265,11 +265,15 @@ where constructing validator_set_update::VextDigest" ); } - + let key = (addr, block_height); + tracing::debug!( + ?key, + ?sig, + ?validator_addr, + "Inserting signature into validator_set_update::VextDigest" + ); #[cfg(not(feature = "abcipp"))] - if let Some(existing_sig) = - signatures.insert((addr, block_height), sig.clone()) - { + if let Some(existing_sig) = signatures.insert(key, sig.clone()) { tracing::warn!( ?sig, ?existing_sig, From 7eb4abab0a9b0798fa495539b10e7db34c3d5afa Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 14:40:58 +0000 Subject: [PATCH 1490/1995] Remove no longer necessary deduping from apply_update --- .../protocol/transactions/ethereum_events/mod.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index af1af5f2be..b6ddfb437f 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -12,7 +12,7 @@ use eyre::Result; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; use crate::ledger::protocol::transactions::votes::{ - calculate_new, calculate_updated, write, Votes, + calculate_new, calculate_updated, write, }; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -132,17 +132,9 @@ where // is a less arbitrary way to do this let (exists_in_storage, _) = storage.has_key(ð_msg_keys.seen())?; - let mut seen_by = Votes::default(); - for (address, block_height) in update.seen_by.into_iter() { - // TODO: more deterministic deduplication - if let Some(present) = seen_by.insert(address, block_height) { - tracing::warn!(?present, "Duplicate vote in digest"); - } - } - let (vote_tracking, changed, confirmed) = if !exists_in_storage { tracing::debug!(%eth_msg_keys.prefix, "Ethereum event not seen before by any validator"); - let vote_tracking = calculate_new(seen_by, voting_powers)?; + let vote_tracking = calculate_new(update.seen_by, voting_powers)?; let changed = eth_msg_keys.into_iter().collect(); let confirmed = vote_tracking.seen; (vote_tracking, changed, confirmed) @@ -152,7 +144,7 @@ where "Ethereum event already exists in storage", ); let mut votes = HashMap::default(); - seen_by.iter().for_each(|(address, block_height)| { + update.seen_by.iter().for_each(|(address, block_height)| { let voting_power = voting_powers .get(&(address.to_owned(), block_height.to_owned())) .unwrap(); @@ -199,6 +191,7 @@ mod tests { use crate::ledger::pos::namada_proof_of_stake::epoched::Epoched; use crate::ledger::pos::namada_proof_of_stake::PosBase; use crate::ledger::pos::types::{ValidatorSet, WeightedValidator}; + use crate::ledger::protocol::transactions::votes::Votes; use crate::ledger::protocol::transactions::utils::GetVoters; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::testing::TestStorage; From 7c76c88418acf25090577fc8edb9053cecbcc749 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 14:55:27 +0000 Subject: [PATCH 1491/1995] Do vote deduping when we convert from MultiSignedEthEvent --- .../transactions/ethereum_events/eth_msgs.rs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 1c5d93838a..fc2e72f5db 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use crate::ledger::protocol::transactions::votes::{Tally, Votes}; @@ -29,9 +31,28 @@ impl From for EthMsgUpdate { fn from( MultiSignedEthEvent { event, signers }: MultiSignedEthEvent, ) -> Self { + let unique_voters: HashSet<_> = + signers.iter().map(|(addr, _)| addr.to_owned()).collect(); + let mut earliest_votes = Votes::default(); + for voter in unique_voters { + let earliest_vote_height = + signers + .iter() + .filter_map(|(addr, height)| { + if *addr == voter { Some(*height) } else { None } + }) + .min() + .unwrap_or_else(|| { + unreachable!( + "we will always have at least one block height \ + per voter" + ) + }); + _ = earliest_votes.insert(voter, earliest_vote_height); + } Self { body: event, - seen_by: signers.into_iter().collect(), + seen_by: earliest_votes, } } } From f70d0ef77bbdbf7d6026e6d2d5eec501b93f108f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 15:02:15 +0000 Subject: [PATCH 1492/1995] Split out a dedupe fn --- .../transactions/ethereum_events/eth_msgs.rs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index fc2e72f5db..c0772136db 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,9 +1,11 @@ -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use crate::ledger::protocol::transactions::votes::{Tally, Votes}; +use crate::types::address::Address; use crate::types::ethereum_events::EthereumEvent; +use crate::types::storage::BlockHeight; use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; /// Represents an Ethereum event being seen by some validators @@ -31,32 +33,40 @@ impl From for EthMsgUpdate { fn from( MultiSignedEthEvent { event, signers }: MultiSignedEthEvent, ) -> Self { - let unique_voters: HashSet<_> = - signers.iter().map(|(addr, _)| addr.to_owned()).collect(); - let mut earliest_votes = Votes::default(); - for voter in unique_voters { - let earliest_vote_height = - signers - .iter() - .filter_map(|(addr, height)| { - if *addr == voter { Some(*height) } else { None } - }) - .min() - .unwrap_or_else(|| { - unreachable!( - "we will always have at least one block height \ - per voter" - ) - }); - _ = earliest_votes.insert(voter, earliest_vote_height); - } Self { body: event, - seen_by: earliest_votes, + seen_by: dedupe(&signers), } } } +/// Deterministically constructs a `Votes` map from a set of validator addresses +/// and the block heights they signed something at. We arbitrarily take the +/// earliest block height for each validator address encountered. +// TODO: consume `signers` instead of cloning stuff +fn dedupe(signers: &BTreeSet<(Address, BlockHeight)>) -> Votes { + let unique_voters: HashSet<_> = + signers.iter().map(|(addr, _)| addr.to_owned()).collect(); + let mut earliest_votes = Votes::default(); + for voter in unique_voters { + let earliest_vote_height = signers + .iter() + .filter_map( + |(addr, height)| { + if *addr == voter { Some(*height) } else { None } + }, + ) + .min() + .unwrap_or_else(|| { + unreachable!( + "we will always have at least one block height per voter" + ) + }); + _ = earliest_votes.insert(voter, earliest_vote_height); + } + earliest_votes +} + /// Represents an event stored under `eth_msgs` #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, From ee48968ad780514c9398afcd507a8e78028210ba Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 15:30:28 +0000 Subject: [PATCH 1493/1995] Add dedupe tests --- .../transactions/ethereum_events/eth_msgs.rs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index c0772136db..9f4de5fd08 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -111,4 +111,89 @@ mod tests { assert_eq!(update, expected); } + + #[test] + fn test_dedupe_empty() { + let signers = BTreeSet::new(); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::new()); + } + + #[test] + fn test_dedupe_single_vote() { + let sole_validator = address::testing::established_address_1(); + let votes = [(sole_validator, BlockHeight(100))]; + let signers = BTreeSet::from(votes.clone()); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::from(votes)); + } + + #[test] + fn test_dedupe_multiple_votes_same_voter() { + let sole_validator = address::testing::established_address_1(); + let earliest_vote_height = 100; + let earliest_vote = + (sole_validator.clone(), BlockHeight(earliest_vote_height)); + let votes = [ + earliest_vote.clone(), + ( + sole_validator.clone(), + BlockHeight(earliest_vote_height + 1), + ), + (sole_validator, BlockHeight(earliest_vote_height + 100)), + ]; + let signers = BTreeSet::from(votes); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::from([earliest_vote])); + } + + #[test] + fn test_dedupe_multiple_votes_multiple_voters() { + let validator_1 = address::testing::established_address_1(); + let validator_2 = address::testing::established_address_2(); + let validator_1_earliest_vote_height = 100; + let validator_1_earliest_vote = ( + validator_1.clone(), + BlockHeight(validator_1_earliest_vote_height), + ); + let validator_2_earliest_vote_height = 200; + let validator_2_earliest_vote = ( + validator_2.clone(), + BlockHeight(validator_2_earliest_vote_height), + ); + let votes = [ + validator_1_earliest_vote.clone(), + ( + validator_1.clone(), + BlockHeight(validator_1_earliest_vote_height + 1), + ), + ( + validator_1, + BlockHeight(validator_1_earliest_vote_height + 100), + ), + validator_2_earliest_vote.clone(), + ( + validator_2.clone(), + BlockHeight(validator_2_earliest_vote_height + 1), + ), + ( + validator_2, + BlockHeight(validator_2_earliest_vote_height + 100), + ), + ]; + let signers = BTreeSet::from(votes); + + let deduped = dedupe(&signers); + + assert_eq!( + deduped, + Votes::from([validator_1_earliest_vote, validator_2_earliest_vote]) + ); + } } From 40630fe4370c2691c89be087ba01993d8c688792 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 15:39:39 +0000 Subject: [PATCH 1494/1995] Remove TODO --- .../src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 9f4de5fd08..176357d23c 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -43,7 +43,6 @@ impl From for EthMsgUpdate { /// Deterministically constructs a `Votes` map from a set of validator addresses /// and the block heights they signed something at. We arbitrarily take the /// earliest block height for each validator address encountered. -// TODO: consume `signers` instead of cloning stuff fn dedupe(signers: &BTreeSet<(Address, BlockHeight)>) -> Votes { let unique_voters: HashSet<_> = signers.iter().map(|(addr, _)| addr.to_owned()).collect(); From e2174a3c2365b8dc10f4ff791a7709cefb06c2b0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 15:53:09 +0000 Subject: [PATCH 1495/1995] Move dedupe fn to transaction module --- .../transactions/ethereum_events/eth_msgs.rs | 32 +---- .../src/ledger/protocol/transactions/votes.rs | 122 +++++++++++++++++- 2 files changed, 122 insertions(+), 32 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 176357d23c..a682b2b389 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,11 +1,7 @@ -use std::collections::{BTreeSet, HashSet}; - use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use crate::ledger::protocol::transactions::votes::{Tally, Votes}; -use crate::types::address::Address; +use crate::ledger::protocol::transactions::votes::{dedupe, Tally, Votes}; use crate::types::ethereum_events::EthereumEvent; -use crate::types::storage::BlockHeight; use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; /// Represents an Ethereum event being seen by some validators @@ -40,32 +36,6 @@ impl From for EthMsgUpdate { } } -/// Deterministically constructs a `Votes` map from a set of validator addresses -/// and the block heights they signed something at. We arbitrarily take the -/// earliest block height for each validator address encountered. -fn dedupe(signers: &BTreeSet<(Address, BlockHeight)>) -> Votes { - let unique_voters: HashSet<_> = - signers.iter().map(|(addr, _)| addr.to_owned()).collect(); - let mut earliest_votes = Votes::default(); - for voter in unique_voters { - let earliest_vote_height = signers - .iter() - .filter_map( - |(addr, height)| { - if *addr == voter { Some(*height) } else { None } - }, - ) - .min() - .unwrap_or_else(|| { - unreachable!( - "we will always have at least one block height per voter" - ) - }); - _ = earliest_votes.insert(voter, earliest_vote_height); - } - earliest_votes -} - /// Represents an event stored under `eth_msgs` #[derive( Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index c9fc1b41f2..ac8e02029c 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -1,7 +1,7 @@ //! Logic and data types relating to tallying validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; @@ -121,3 +121,123 @@ where storage.write(&keys.voting_power(), &tally.voting_power.try_to_vec()?)?; Ok(()) } + +/// Deterministically constructs a [`Votes`] map from a set of validator +/// addresses and the block heights they signed something at. We arbitrarily +/// take the earliest block height for each validator address encountered. +pub fn dedupe(signers: &BTreeSet<(Address, BlockHeight)>) -> Votes { + let unique_voters: HashSet<_> = + signers.iter().map(|(addr, _)| addr.to_owned()).collect(); + let mut earliest_votes = Votes::default(); + for voter in unique_voters { + let earliest_vote_height = signers + .iter() + .filter_map( + |(addr, height)| { + if *addr == voter { Some(*height) } else { None } + }, + ) + .min() + .unwrap_or_else(|| { + unreachable!( + "we will always have at least one block height per voter" + ) + }); + _ = earliest_votes.insert(voter, earliest_vote_height); + } + earliest_votes +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use super::*; + use crate::types::address; + use crate::types::storage::BlockHeight; + + #[test] + fn test_dedupe_empty() { + let signers = BTreeSet::new(); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::new()); + } + + #[test] + fn test_dedupe_single_vote() { + let sole_validator = address::testing::established_address_1(); + let votes = [(sole_validator, BlockHeight(100))]; + let signers = BTreeSet::from(votes.clone()); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::from(votes)); + } + + #[test] + fn test_dedupe_multiple_votes_same_voter() { + let sole_validator = address::testing::established_address_1(); + let earliest_vote_height = 100; + let earliest_vote = + (sole_validator.clone(), BlockHeight(earliest_vote_height)); + let votes = [ + earliest_vote.clone(), + ( + sole_validator.clone(), + BlockHeight(earliest_vote_height + 1), + ), + (sole_validator, BlockHeight(earliest_vote_height + 100)), + ]; + let signers = BTreeSet::from(votes); + + let deduped = dedupe(&signers); + + assert_eq!(deduped, Votes::from([earliest_vote])); + } + + #[test] + fn test_dedupe_multiple_votes_multiple_voters() { + let validator_1 = address::testing::established_address_1(); + let validator_2 = address::testing::established_address_2(); + let validator_1_earliest_vote_height = 100; + let validator_1_earliest_vote = ( + validator_1.clone(), + BlockHeight(validator_1_earliest_vote_height), + ); + let validator_2_earliest_vote_height = 200; + let validator_2_earliest_vote = ( + validator_2.clone(), + BlockHeight(validator_2_earliest_vote_height), + ); + let votes = [ + validator_1_earliest_vote.clone(), + ( + validator_1.clone(), + BlockHeight(validator_1_earliest_vote_height + 1), + ), + ( + validator_1, + BlockHeight(validator_1_earliest_vote_height + 100), + ), + validator_2_earliest_vote.clone(), + ( + validator_2.clone(), + BlockHeight(validator_2_earliest_vote_height + 1), + ), + ( + validator_2, + BlockHeight(validator_2_earliest_vote_height + 100), + ), + ]; + let signers = BTreeSet::from(votes); + + let deduped = dedupe(&signers); + + assert_eq!( + deduped, + Votes::from([validator_1_earliest_vote, validator_2_earliest_vote]) + ); + } +} From 271eb2f10dcf29f590732881346e87f655b96937 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 16:08:00 +0000 Subject: [PATCH 1496/1995] Simplify code --- .../transactions/ethereum_events/eth_msgs.rs | 10 +++--- .../src/ledger/protocol/transactions/votes.rs | 31 ++++--------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index a682b2b389..82c762e648 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -31,7 +31,7 @@ impl From for EthMsgUpdate { ) -> Self { Self { body: event, - seen_by: dedupe(&signers), + seen_by: dedupe(signers), } } } @@ -85,7 +85,7 @@ mod tests { fn test_dedupe_empty() { let signers = BTreeSet::new(); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::new()); } @@ -96,7 +96,7 @@ mod tests { let votes = [(sole_validator, BlockHeight(100))]; let signers = BTreeSet::from(votes.clone()); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::from(votes)); } @@ -117,7 +117,7 @@ mod tests { ]; let signers = BTreeSet::from(votes); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::from([earliest_vote])); } @@ -158,7 +158,7 @@ mod tests { ]; let signers = BTreeSet::from(votes); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!( deduped, diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index ac8e02029c..45a8a745e5 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -125,27 +125,8 @@ where /// Deterministically constructs a [`Votes`] map from a set of validator /// addresses and the block heights they signed something at. We arbitrarily /// take the earliest block height for each validator address encountered. -pub fn dedupe(signers: &BTreeSet<(Address, BlockHeight)>) -> Votes { - let unique_voters: HashSet<_> = - signers.iter().map(|(addr, _)| addr.to_owned()).collect(); - let mut earliest_votes = Votes::default(); - for voter in unique_voters { - let earliest_vote_height = signers - .iter() - .filter_map( - |(addr, height)| { - if *addr == voter { Some(*height) } else { None } - }, - ) - .min() - .unwrap_or_else(|| { - unreachable!( - "we will always have at least one block height per voter" - ) - }); - _ = earliest_votes.insert(voter, earliest_vote_height); - } - earliest_votes +pub fn dedupe(signers: BTreeSet<(Address, BlockHeight)>) -> Votes { + signers.into_iter().rev().collect() } #[cfg(test)] @@ -160,7 +141,7 @@ mod tests { fn test_dedupe_empty() { let signers = BTreeSet::new(); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::new()); } @@ -171,7 +152,7 @@ mod tests { let votes = [(sole_validator, BlockHeight(100))]; let signers = BTreeSet::from(votes.clone()); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::from(votes)); } @@ -192,7 +173,7 @@ mod tests { ]; let signers = BTreeSet::from(votes); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!(deduped, Votes::from([earliest_vote])); } @@ -233,7 +214,7 @@ mod tests { ]; let signers = BTreeSet::from(votes); - let deduped = dedupe(&signers); + let deduped = dedupe(signers); assert_eq!( deduped, From 1bc03e7323b2eadc8078e4eef9ac4bc87d922c08 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 16:19:15 +0000 Subject: [PATCH 1497/1995] Remove unused import --- shared/src/ledger/protocol/transactions/votes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 45a8a745e5..a931efbb66 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -1,7 +1,7 @@ //! Logic and data types relating to tallying validators' votes for pieces of //! data stored in the ledger, where those pieces of data should only be acted //! on once they have received enough votes -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; From 38571e6ab70aed165b084956da7e658c639c7d47 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 8 Nov 2022 16:47:32 +0000 Subject: [PATCH 1498/1995] Remove duplicate tests --- .../transactions/ethereum_events/eth_msgs.rs | 85 ------------------- 1 file changed, 85 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 82c762e648..8fc9635d15 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -80,89 +80,4 @@ mod tests { assert_eq!(update, expected); } - - #[test] - fn test_dedupe_empty() { - let signers = BTreeSet::new(); - - let deduped = dedupe(signers); - - assert_eq!(deduped, Votes::new()); - } - - #[test] - fn test_dedupe_single_vote() { - let sole_validator = address::testing::established_address_1(); - let votes = [(sole_validator, BlockHeight(100))]; - let signers = BTreeSet::from(votes.clone()); - - let deduped = dedupe(signers); - - assert_eq!(deduped, Votes::from(votes)); - } - - #[test] - fn test_dedupe_multiple_votes_same_voter() { - let sole_validator = address::testing::established_address_1(); - let earliest_vote_height = 100; - let earliest_vote = - (sole_validator.clone(), BlockHeight(earliest_vote_height)); - let votes = [ - earliest_vote.clone(), - ( - sole_validator.clone(), - BlockHeight(earliest_vote_height + 1), - ), - (sole_validator, BlockHeight(earliest_vote_height + 100)), - ]; - let signers = BTreeSet::from(votes); - - let deduped = dedupe(signers); - - assert_eq!(deduped, Votes::from([earliest_vote])); - } - - #[test] - fn test_dedupe_multiple_votes_multiple_voters() { - let validator_1 = address::testing::established_address_1(); - let validator_2 = address::testing::established_address_2(); - let validator_1_earliest_vote_height = 100; - let validator_1_earliest_vote = ( - validator_1.clone(), - BlockHeight(validator_1_earliest_vote_height), - ); - let validator_2_earliest_vote_height = 200; - let validator_2_earliest_vote = ( - validator_2.clone(), - BlockHeight(validator_2_earliest_vote_height), - ); - let votes = [ - validator_1_earliest_vote.clone(), - ( - validator_1.clone(), - BlockHeight(validator_1_earliest_vote_height + 1), - ), - ( - validator_1, - BlockHeight(validator_1_earliest_vote_height + 100), - ), - validator_2_earliest_vote.clone(), - ( - validator_2.clone(), - BlockHeight(validator_2_earliest_vote_height + 1), - ), - ( - validator_2, - BlockHeight(validator_2_earliest_vote_height + 100), - ), - ]; - let signers = BTreeSet::from(votes); - - let deduped = dedupe(signers); - - assert_eq!( - deduped, - Votes::from([validator_1_earliest_vote, validator_2_earliest_vote]) - ); - } } From a79f76c75e0996e5f6b2c80f23107649857757ba Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 09:53:29 +0000 Subject: [PATCH 1499/1995] Add a test that conversion does in fact dedupe --- .../transactions/ethereum_events/eth_msgs.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index 8fc9635d15..aedec3e76b 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -80,4 +80,37 @@ mod tests { assert_eq!(update, expected); } + + #[test] + /// Test that `From` for `EthMsgUpdate` does in fact + /// dedupe votes + fn test_from_multi_signed_eth_event_for_eth_msg_update_dedupes() { + let validator_1 = address::testing::established_address_1(); + let validator_2 = address::testing::established_address_2(); + let signers = BTreeSet::from([ + (validator_1.clone(), BlockHeight(100)), + (validator_2.clone(), BlockHeight(200)), + (validator_1, BlockHeight(300)), + (validator_2, BlockHeight(400)), + ]); + + let event = arbitrary_single_transfer( + arbitrary_nonce(), + address::testing::established_address_3(), + ); + let with_signers = MultiSignedEthEvent { + event: event.clone(), + signers: signers.clone(), + }; + + let update: EthMsgUpdate = with_signers.into(); + + assert_eq!( + update, + EthMsgUpdate { + body: event, + seen_by: dedupe(signers), + } + ); + } } From 2177ff3892753734ff47f434cbc0f1a0d099f3b2 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 13:05:56 +0000 Subject: [PATCH 1500/1995] make fmt --- shared/src/ledger/protocol/transactions/ethereum_events/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index b6ddfb437f..bb9c63bd55 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -191,8 +191,8 @@ mod tests { use crate::ledger::pos::namada_proof_of_stake::epoched::Epoched; use crate::ledger::pos::namada_proof_of_stake::PosBase; use crate::ledger::pos::types::{ValidatorSet, WeightedValidator}; - use crate::ledger::protocol::transactions::votes::Votes; use crate::ledger::protocol::transactions::utils::GetVoters; + use crate::ledger::protocol::transactions::votes::Votes; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::testing::TestStorage; use crate::ledger::storage::traits::Sha256Hasher; From e9a08058b432a29de261531f83804fa44a78f23d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 13:07:42 +0000 Subject: [PATCH 1501/1995] Remove duplicate ChangedKeys type, fix imports --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index bb9c63bd55..e51d53d7b1 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -9,6 +9,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::{EthMsg, EthMsgUpdate}; use eyre::Result; +use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; use crate::ledger::protocol::transactions::votes::{ @@ -17,14 +18,11 @@ use crate::ledger::protocol::transactions::votes::{ use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; -use crate::types::storage::{self, BlockHeight}; +use crate::types::storage::BlockHeight; use crate::types::transaction::TxResult; use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use crate::types::voting_power::FractionalVotingPower; -/// The keys changed while applying a protocol transaction -type ChangedKeys = BTreeSet; - impl utils::GetVoters for [MultiSignedEthEvent] { #[inline] fn get_voters(&self) -> HashSet<(Address, BlockHeight)> { @@ -184,7 +182,6 @@ mod tests { use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; - use storage::BlockHeight; use super::*; use crate::ledger::eth_bridge::storage::wrapped_erc20s; From 255b5ad12e2266b4e091bb389316d4b78ae6f742 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 13:20:13 +0000 Subject: [PATCH 1502/1995] Add test_get_votes_for_events_multiple_signatures_same_validator --- .../transactions/ethereum_events/mod.rs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index e51d53d7b1..5a6612d60d 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -465,4 +465,30 @@ mod tests { ]) ) } + + #[test] + /// Test that we correctly get the votes from a vec of events in the case + /// where there are multiple signatures from the same validator + fn test_get_votes_for_events_multiple_signatures_same_validator() { + let events = vec![MultiSignedEthEvent { + event: arbitrary_single_transfer( + 1.into(), + address::testing::established_address_1(), + ), + signers: BTreeSet::from([ + (address::testing::established_address_1(), BlockHeight(100)), + (address::testing::established_address_1(), BlockHeight(101)), + (address::testing::established_address_2(), BlockHeight(200)), + ]), + }]; + let voters = events.as_slice().get_voters(); + assert_eq!( + voters, + HashSet::from_iter(vec![ + (address::testing::established_address_1(), BlockHeight(100)), + (address::testing::established_address_1(), BlockHeight(101)), + (address::testing::established_address_2(), BlockHeight(200)) + ]) + ) + } } From 75ad88a3ecb1be847405afd4946515439c060cce Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 13:45:34 +0000 Subject: [PATCH 1503/1995] impl utils::GetVoters for HashSet --- .../transactions/ethereum_events/mod.rs | 66 ++++++------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 5a6612d60d..a2640f4391 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -23,11 +23,11 @@ use crate::types::transaction::TxResult; use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use crate::types::voting_power::FractionalVotingPower; -impl utils::GetVoters for [MultiSignedEthEvent] { +impl utils::GetVoters for HashSet { #[inline] fn get_voters(&self) -> HashSet<(Address, BlockHeight)> { - self.iter().fold(HashSet::new(), |mut voters, event| { - voters.extend(event.signers.iter().cloned()); + self.iter().fold(HashSet::new(), |mut voters, update| { + voters.extend(update.seen_by.clone().into_iter()); voters }) } @@ -57,10 +57,10 @@ where protocol transaction" ); - let voting_powers = utils::get_voting_powers(storage, events.as_slice())?; - let updates = events.into_iter().map(Into::::into).collect(); + let voting_powers = utils::get_voting_powers(storage, &updates)?; + let changed_keys = apply_updates(storage, updates, voting_powers)?; Ok(TxResult { @@ -411,22 +411,22 @@ mod tests { #[test] /// Assert we don't return anything if we try to get the votes for an empty - /// vec of events - pub fn test_get_votes_for_events_empty() { - let events = vec![]; - assert!(events.as_slice().get_voters().is_empty()); + /// vec of updates + pub fn test_get_votes_for_updates_empty() { + let updates = HashSet::new(); + assert!(updates.get_voters().is_empty()); } #[test] - /// Test that we correctly get the votes from a vec of events + /// Test that we correctly get the votes from a vec of updates pub fn test_get_votes_for_events() { - let events = vec![ - MultiSignedEthEvent { - event: arbitrary_single_transfer( + let updates = HashSet::from([ + EthMsgUpdate { + body: arbitrary_single_transfer( 1.into(), address::testing::established_address_1(), ), - signers: BTreeSet::from([ + seen_by: Votes::from([ ( address::testing::established_address_1(), BlockHeight(100), @@ -437,12 +437,12 @@ mod tests { ), ]), }, - MultiSignedEthEvent { - event: arbitrary_single_transfer( + EthMsgUpdate { + body: arbitrary_single_transfer( 2.into(), address::testing::established_address_2(), ), - signers: BTreeSet::from([ + seen_by: Votes::from([ ( address::testing::established_address_1(), BlockHeight(101), @@ -453,11 +453,11 @@ mod tests { ), ]), }, - ]; - let voters = events.as_slice().get_voters(); + ]); + let voters = updates.get_voters(); assert_eq!( voters, - HashSet::from_iter(vec![ + HashSet::from([ (address::testing::established_address_1(), BlockHeight(100)), (address::testing::established_address_1(), BlockHeight(101)), (address::testing::established_address_2(), BlockHeight(102)), @@ -465,30 +465,4 @@ mod tests { ]) ) } - - #[test] - /// Test that we correctly get the votes from a vec of events in the case - /// where there are multiple signatures from the same validator - fn test_get_votes_for_events_multiple_signatures_same_validator() { - let events = vec![MultiSignedEthEvent { - event: arbitrary_single_transfer( - 1.into(), - address::testing::established_address_1(), - ), - signers: BTreeSet::from([ - (address::testing::established_address_1(), BlockHeight(100)), - (address::testing::established_address_1(), BlockHeight(101)), - (address::testing::established_address_2(), BlockHeight(200)), - ]), - }]; - let voters = events.as_slice().get_voters(); - assert_eq!( - voters, - HashSet::from_iter(vec![ - (address::testing::established_address_1(), BlockHeight(100)), - (address::testing::established_address_1(), BlockHeight(101)), - (address::testing::established_address_2(), BlockHeight(200)) - ]) - ) - } } From 97eff0379e4d8e84c8f6454f85c9054ec993c999 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 13:46:39 +0000 Subject: [PATCH 1504/1995] Change vec -> set --- .../src/ledger/protocol/transactions/ethereum_events/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index a2640f4391..40130f46df 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -411,14 +411,14 @@ mod tests { #[test] /// Assert we don't return anything if we try to get the votes for an empty - /// vec of updates + /// set of updates pub fn test_get_votes_for_updates_empty() { let updates = HashSet::new(); assert!(updates.get_voters().is_empty()); } #[test] - /// Test that we correctly get the votes from a vec of updates + /// Test that we correctly get the votes from a set of updates pub fn test_get_votes_for_events() { let updates = HashSet::from([ EthMsgUpdate { From b32cd6930389744843ada1bbcd1b46650b5a73b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Nov 2022 17:26:48 +0000 Subject: [PATCH 1505/1995] [ci] 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 08916c3fb9..29297d77a1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.4385ddcf475bf5d03cfaa2cc6816a0741b3fd894a592354aae87b19135874e62.wasm", + "tx_bond.wasm": "tx_bond.a2d03adca103b0acc5997b926990ff4de4650268203e9d3350e004eab074634a.wasm", "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.36d265ef84f11e82c304b965d9852c3aac72bd50061212e8be1d03b632579c1e.wasm", - "tx_init_account.wasm": "tx_init_account.9c0701134b0f1b3b17ff21560b8ee16a761aba2c371a5ab3c4763c7f1c3d8f42.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0246bc54874b4ecf664dbd9441c747b257996c14b229b8c5e74c2f62784a3243.wasm", - "tx_init_validator.wasm": "tx_init_validator.4ea7273d501c6be0f57ff411645fbb02bb35a8be84ec2f3305baf0b5a2d868d4.wasm", - "tx_transfer.wasm": "tx_transfer.ad40979d4a1518c9c7463648cc63212991a149f898e53423569d7eea82f73d09.wasm", - "tx_unbond.wasm": "tx_unbond.cc1425fbb1fc5f9c3db8940f3c8713d79d9224b936f2763acd5f77a129dbfdce.wasm", - "tx_update_vp.wasm": "tx_update_vp.74fbce3cf33900a5b5c6291db82b53bfe5ca2f0d5b6b87bc83667206746e0ca5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c2708194bb19f1b3a2f629dc22a9e11788d35e8a570b9eb77d32f27994280a36.wasm", - "tx_withdraw.wasm": "tx_withdraw.77ab8eb211d24b8713458adc248724c571371e7dfedf22a30f6a13c0c9110ed3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.66aa929d4f17249d9b1b4d3e0053a21f4bc46c0326c082f84e2e02161f00a392.wasm", - "vp_token.wasm": "vp_token.6ac1cf76dd3d8cfef8f2fd1400e2b2488c7553855f3fb009bb7199c085b1d249.wasm", - "vp_user.wasm": "vp_user.74ed31241805844c86e1185746b084ee326b53e6e773b4cdf7a5a925fa804280.wasm" + "tx_ibc.wasm": "tx_ibc.2b467845c55820345ce48e2da4f7c2606566555eac12ee64f8baeb5b63dfae83.wasm", + "tx_init_account.wasm": "tx_init_account.86e02762f60428966c7ab4f780fc4dbcc794ae0aa791930f27e246c2c289e045.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f654f0e4610e03c9ffcc36225f67822200aa89f3633f16a9eb0b47813f8ec15a.wasm", + "tx_init_validator.wasm": "tx_init_validator.b4e6713e69aa341339a836de6901c7c29a532849577514255ecb3092882a12c3.wasm", + "tx_transfer.wasm": "tx_transfer.e3c687a17173af07b4be0fc0af06ebe6dbba1d5195b8e3015c955eeaaa53e59e.wasm", + "tx_unbond.wasm": "tx_unbond.4480c046edf32bf97d2d40d45b2bd05547049cae22df48f3fc433f151b495e2b.wasm", + "tx_update_vp.wasm": "tx_update_vp.b057de2c9f17a634aaf011f6bf6bb0bac198ae522c5a24a44dec71d9b649c7e7.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.4625c309121620a10b9b87b4f4622b8a899f758c08d5ae0341a6f80ce932e089.wasm", + "tx_withdraw.wasm": "tx_withdraw.6ee49918852f375271a444c111b73ddcf20e237af852579b4fcc4caab17a2715.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e417000da4f7640a5fb5f124755ed02b8654aa67db600bea6e93e1f04e6d71a3.wasm", + "vp_token.wasm": "vp_token.37749e16d9fcd17dc2cf3351b0a140b5821668f80d6cc805caeb3e8e9af06cd9.wasm", + "vp_user.wasm": "vp_user.8c117faa94e44833fb7a16d13067f7626ecfca6a70fec1b7822c7d9a6c3e909e.wasm" } \ No newline at end of file From 32c6e5ab55c9975a92837c3a1722641bf4ab71f5 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 10 Nov 2022 12:26:47 +0100 Subject: [PATCH 1506/1995] Update wasm/wasm_source/src/tx_bridge_pool.rs Co-authored-by: James --- wasm/wasm_source/src/tx_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 768efc39bf..70206da588 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -16,7 +16,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { ctx, payer, &bridge_pool::BRIDGE_POOL_ADDRESS, - &address::xan(), + &address::nam(), None, amount, )?; From 80739874b06333d04a1985e1b6b6cdb88eef4eb0 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Thu, 10 Nov 2022 12:26:56 +0100 Subject: [PATCH 1507/1995] Update tests/src/native_vp/eth_bridge_pool.rs Co-authored-by: James --- tests/src/native_vp/eth_bridge_pool.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index f2045177c8..4c3d64a1cb 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -11,7 +11,7 @@ mod test_bridge_pool_vp { use namada::ledger::eth_bridge::storage::wrapped_erc20s; use namada::ledger::eth_bridge::ADDRESS; use namada::proto::Tx; - use namada::types::address::{wnam, xan}; + use namada::types::address::{wnam, nam}; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; From 5addcd84d4f86e45bd243b8f1f91998e48a59584 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 11 Nov 2022 10:52:22 +0100 Subject: [PATCH 1508/1995] [fix]: Changed some xan to nam, fixed some arg strings in e2e tests --- shared/src/types/ethereum_events.rs | 5 +- tests/src/e2e/ledger_tests.rs | 92 +++++++++++++------------- tests/src/native_vp/eth_bridge_pool.rs | 8 +-- wasm/wasm_source/src/tx_bridge_pool.rs | 4 +- 4 files changed, 53 insertions(+), 56 deletions(-) diff --git a/shared/src/types/ethereum_events.rs b/shared/src/types/ethereum_events.rs index 4e23d94fa1..fe08ee5eb1 100644 --- a/shared/src/types/ethereum_events.rs +++ b/shared/src/types/ethereum_events.rs @@ -112,10 +112,7 @@ impl From for String { } impl KeySeg for EthAddress { - fn parse(string: String) -> crate::types::storage::Result - where - Self: Sized, - { + fn parse(string: String) -> crate::types::storage::Result { Self::from_str(string.as_str()) .map_err(|_| crate::types::storage::Error::ParseKeySeg(string)) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c5084d6918..b6b29df79c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -109,11 +109,11 @@ fn test_node_connectivity() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -311,11 +311,11 @@ fn ledger_txs_and_queries() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -328,11 +328,11 @@ fn ledger_txs_and_queries() -> Result<()> { BERTHA, "--code-path", &vp_user, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -346,11 +346,11 @@ fn ledger_txs_and_queries() -> Result<()> { &tx_no_op, "--data-path", "README.md", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc @@ -367,11 +367,11 @@ fn ledger_txs_and_queries() -> Result<()> { &vp_user, "--alias", "Test-Account", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -495,11 +495,11 @@ fn invalid_transactions() -> Result<()> { &tx_data_path, "--signing-key", DAEWON, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -549,11 +549,11 @@ fn invalid_transactions() -> Result<()> { BERTHA, "--amount", "1_000_000.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, // Force to ignore client check that fails on the balance check of the // source address @@ -627,11 +627,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -651,11 +651,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -672,11 +672,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "5.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -696,11 +696,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "3.2", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -737,11 +737,11 @@ fn pos_bonds() -> Result<()> { "withdraw", "--validator", "validator-0", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -759,11 +759,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--source", BERTHA, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -831,11 +831,11 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--unsafe-dont-encrypt", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -857,11 +857,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "0.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -879,11 +879,11 @@ fn pos_init_validator() -> Result<()> { BERTHA, "--amount", "1000.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -904,11 +904,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "10999.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -925,11 +925,11 @@ fn pos_init_validator() -> Result<()> { new_validator, "--amount", "10000", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1004,11 +1004,11 @@ fn ledger_many_txs_in_a_block() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", ]); @@ -1115,11 +1115,11 @@ fn proposal_submission() -> Result<()> { BERTHA, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1471,11 +1471,11 @@ fn proposal_offline() -> Result<()> { ALBERT, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1922,11 +1922,11 @@ fn test_genesis_validators() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2100,11 +2100,11 @@ fn double_signing_gets_slashed() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 4c3d64a1cb..f65595dbe3 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -11,7 +11,7 @@ mod test_bridge_pool_vp { use namada::ledger::eth_bridge::storage::wrapped_erc20s; use namada::ledger::eth_bridge::ADDRESS; use namada::proto::Tx; - use namada::types::address::{wnam, nam}; + use namada::types::address::{nam, wnam}; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; @@ -80,18 +80,18 @@ mod test_bridge_pool_vp { // initialize Ethereum bridge storage config.init_storage(&mut env.storage); // initialize Bertha's account - env.spawn_accounts([&albert_address(), &bertha_address(), &xan()]); + env.spawn_accounts([&albert_address(), &bertha_address(), &nam()]); // enrich Albert env.credit_tokens( &albert_address(), - &xan(), + &nam(), None, BERTHA_WEALTH.into(), ); // enrich Bertha env.credit_tokens( &bertha_address(), - &xan(), + &nam(), None, BERTHA_WEALTH.into(), ); diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 70206da588..9e3e1feab2 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -32,13 +32,13 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { ctx, sender, ð_bridge::ADDRESS, - &address::xan(), + &address::nam(), None, amount, )?; } else { // Otherwise we escrow ERC20 tokens. - let sub_prefix = wrapped_erc20s::sub_prefix(&transfer.transfer.asset); + let sub_prefix = wrapped_erc20s::sub_prefix(asset); token::transfer( ctx, sender, From 8f8f7e96b87a70a12cab74615f4e4211ecc42c6e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 09:52:46 +0000 Subject: [PATCH 1509/1995] Update TODO --- .../ledger/protocol/transactions/validator_set_update/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 498e0bb655..8b77ce328e 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -88,8 +88,9 @@ where let mut seen_by = Votes::default(); for (address, block_height) in ext.signatures.into_keys() { - // TODO: more deterministic deduplication if let Some(present) = seen_by.insert(address, block_height) { + // TODO(namada#770): this shouldn't be happening in any case and we + // should be refactoring to get rid of `BlockHeight` tracing::warn!(?present, "Duplicate vote in digest"); } } From 34bbc758407d29a6f85216217f629bb5acd862ae Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 12:24:56 +0000 Subject: [PATCH 1510/1995] Revert "Revert changes to namadac tx arguments" This reverts commit 1bd7f16aec1b62ea27a997ddb35b9dd911fb04ff. --- apps/src/lib/cli.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 2d6cdd0e52..7749b1320e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1380,11 +1380,13 @@ pub mod args { const FEE_AMOUNT: ArgDefault = arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const FEE_PAYER: Arg = arg("fee-payer"); - const FEE_TOKEN: ArgDefaultFromCtx = - arg_default_from_ctx("fee-token", DefaultFn(|| "NAM".into())); const FORCE: ArgFlag = flag("force"); + const GAS_AMOUNT: ArgDefault = + arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); + const GAS_TOKEN: ArgDefaultFromCtx = + arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); const HASH_LIST: Arg = arg("hash-list"); @@ -2411,10 +2413,10 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(FEE_AMOUNT.def().about( + .arg(GAS_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(FEE_TOKEN.def().about("The token for paying the fee")) + .arg(GAS_TOKEN.def().about("The token for paying the fee")) .arg( GAS_LIMIT.def().about( "The maximum amount of gas needed to run transaction", @@ -2447,8 +2449,8 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = FEE_AMOUNT.parse(matches); - let fee_token = FEE_TOKEN.parse(matches); + let fee_amount = GAS_AMOUNT.parse(matches); + let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); let signing_key = SIGNING_KEY_OPT.parse(matches); From ea04983974114cc37033b5b77dc77e92a1d9dbd3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 12:30:55 +0000 Subject: [PATCH 1511/1995] Use --gas-fee and --gas-token for `namadac tx` in e2e tests --- tests/src/e2e/ledger_tests.rs | 108 +++++++++++++++++----------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c5084d6918..1e2c8f45bb 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -109,11 +109,11 @@ fn test_node_connectivity() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -311,11 +311,11 @@ fn ledger_txs_and_queries() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -324,16 +324,16 @@ fn ledger_txs_and_queries() -> Result<()> { // predicate vec![ "update", - "--address", - BERTHA, - "--code-path", - &vp_user, - "--fee-amount", - "0", - "--gas-limit", - "0", - "--fee-token", - NAM, + "--address", + BERTHA, + "--code-path", + &vp_user, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, "--ledger-address", &validator_one_rpc, ], @@ -346,11 +346,11 @@ fn ledger_txs_and_queries() -> Result<()> { &tx_no_op, "--data-path", "README.md", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc @@ -367,11 +367,11 @@ fn ledger_txs_and_queries() -> Result<()> { &vp_user, "--alias", "Test-Account", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -495,11 +495,11 @@ fn invalid_transactions() -> Result<()> { &tx_data_path, "--signing-key", DAEWON, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -549,11 +549,11 @@ fn invalid_transactions() -> Result<()> { BERTHA, "--amount", "1_000_000.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, // Force to ignore client check that fails on the balance check of the // source address @@ -627,11 +627,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -651,11 +651,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -672,11 +672,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "5.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -696,11 +696,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "3.2", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -737,11 +737,11 @@ fn pos_bonds() -> Result<()> { "withdraw", "--validator", "validator-0", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -759,11 +759,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--source", BERTHA, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -831,11 +831,11 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--unsafe-dont-encrypt", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -857,11 +857,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "0.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -879,11 +879,11 @@ fn pos_init_validator() -> Result<()> { BERTHA, "--amount", "1000.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -904,11 +904,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "10999.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -925,11 +925,11 @@ fn pos_init_validator() -> Result<()> { new_validator, "--amount", "10000", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1004,11 +1004,11 @@ fn ledger_many_txs_in_a_block() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", ]); @@ -1115,11 +1115,11 @@ fn proposal_submission() -> Result<()> { BERTHA, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1471,11 +1471,11 @@ fn proposal_offline() -> Result<()> { ALBERT, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1922,11 +1922,11 @@ fn test_genesis_validators() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2100,11 +2100,11 @@ fn double_signing_gets_slashed() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, From c83e878fd902c46f32d19b01304ed7380f7727ff Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 13:14:16 +0000 Subject: [PATCH 1512/1995] Begin block proposal size optimization From bf9db8c03bc703b0947669a72912e856e175bcfe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 13:14:16 +0000 Subject: [PATCH 1513/1995] Begin block proposal size optimization This commit's only purpose is to allow starting a new PR on GitHub. From 0e02250414de3df62030dc5d5d3aeb61a318e32f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 9 Nov 2022 16:34:53 +0000 Subject: [PATCH 1514/1995] Add allotted space helper struct --- .../lib/node/ledger/shell/prepare_proposal.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 039397e804..babae4ff6b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -22,6 +22,25 @@ use crate::node::ledger::shell::vote_extensions::{ use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; +/// Alloted space for transactions in some proposed block. +/// +/// We keep track of the current space utilized by: +/// +/// - Protocol transactions. +/// - DKG decrypted transactions. +/// - DKG encrypted transactions. +struct TxAllotedSpace { + /// The total space Tendermint has allotted to the + /// application for the current block height. + provided_by_tendermint: u64, + /// The current space utilized by protocol transactions. + current_for_protocol_txs: u64, + /// The current space utilized by DKG encrypted transactions. + current_for_encrypted_txs: u64, + /// The current space utilized by DKG decrypted transactions. + current_for_decrypted_txs: u64, +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, From e88618ecf2b5e1f0dfc4ca501cc074a33783266a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 9 Nov 2022 16:36:32 +0000 Subject: [PATCH 1515/1995] Docstr improvement --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index babae4ff6b..ce2d109f95 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -22,7 +22,8 @@ use crate::node::ledger::shell::vote_extensions::{ use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; -/// Alloted space for transactions in some proposed block. +/// Alloted space for transactions in some proposed block, +/// measured in bytes. /// /// We keep track of the current space utilized by: /// From 1be08dc2a1d93372a22eab36bd58a21f06aba8ee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 9 Nov 2022 16:37:37 +0000 Subject: [PATCH 1516/1995] Add a TODO --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ce2d109f95..54ed223683 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -56,6 +56,8 @@ 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). + // TODO: change second paragraph of the docstr, to reflect new + // alloted space per block design pub fn prepare_proposal( &mut self, req: RequestPrepareProposal, From ba8b7b731bb8f9d48318da74ba855cc5d94a6823 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 9 Nov 2022 16:41:16 +0000 Subject: [PATCH 1517/1995] Add TxAllotedSpace constructor --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 54ed223683..604282a136 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -30,6 +30,8 @@ use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; /// - Protocol transactions. /// - DKG decrypted transactions. /// - DKG encrypted transactions. +#[derive(Default)] +#[allow(dead_code)] struct TxAllotedSpace { /// The total space Tendermint has allotted to the /// application for the current block height. @@ -42,6 +44,19 @@ struct TxAllotedSpace { current_for_decrypted_txs: u64, } +impl TxAllotedSpace { + /// Construct a new [`TxAllotedSpace`], with an upper bound + /// on the max number of txs in a block defined by Tendermint. + #[allow(dead_code)] + #[inline] + fn init_from(req: &RequestPrepareProposal) -> Self { + Self { + provided_by_tendermint: req.max_tx_bytes as u64, + ..Default::default() + } + } +} + impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, From 5510c8b18bfaa33578a2fce70ccf7b3413b9d3f4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:07:49 +0000 Subject: [PATCH 1518/1995] Add num-rational to apps' deps --- Cargo.lock | 1 + apps/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 20d428c9fe..95a7a1ea83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3424,6 +3424,7 @@ dependencies = [ "message-io", "namada", "num-derive", + "num-rational 0.4.1", "num-traits 0.2.15", "num256", "num_cpus", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index a347ab78fa..6b74452609 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -104,6 +104,7 @@ libloading = "0.7.2" message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} num256 = "0.3.5" num-derive = "0.3.3" +num-rational = "0.4.1" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" From e41ffc9a24c2135152639b2f23983e36ac21b84b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:08:11 +0000 Subject: [PATCH 1519/1995] WIP: Alloted space abstraction --- .../lib/node/ledger/shell/prepare_proposal.rs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 604282a136..78f3b613d3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,6 +9,7 @@ use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; +use num_rational::Ratio; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -22,7 +23,7 @@ use crate::node::ledger::shell::vote_extensions::{ use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; -/// Alloted space for transactions in some proposed block, +/// Alloted space for a batch of transactions in some proposed block, /// measured in bytes. /// /// We keep track of the current space utilized by: @@ -37,11 +38,11 @@ struct TxAllotedSpace { /// application for the current block height. provided_by_tendermint: u64, /// The current space utilized by protocol transactions. - current_for_protocol_txs: u64, + protocol_txs: TxBin, /// The current space utilized by DKG encrypted transactions. - current_for_encrypted_txs: u64, + encrypted_txs: TxBin, /// The current space utilized by DKG decrypted transactions. - current_for_decrypted_txs: u64, + decrypted_txs: TxBin, } impl TxAllotedSpace { @@ -50,9 +51,40 @@ impl TxAllotedSpace { #[allow(dead_code)] #[inline] fn init_from(req: &RequestPrepareProposal) -> Self { + // each tx bin gets 1/3 of the alloted space + let thres = Ratio::new(1, 3); + let max = req.max_tx_bytes as u64; Self { - provided_by_tendermint: req.max_tx_bytes as u64, - ..Default::default() + provided_by_tendermint: max, + protocol_txs: TxBin::init_from(max, thres), + encrypted_txs: TxBin::init_from(max, thres), + decrypted_txs: TxBin::init_from(max, thres), + } + } +} + +/// Alloted space for a batch of transactions of the same kind in some +/// proposed block, measured in bytes. +#[derive(Default)] +#[allow(dead_code)] +struct TxBin { + /// The current space utilized by a batch of transactions. + current_space: u64, + /// The maximum space a batch of transactions of the same kind + /// may occupy. + alloted_space: u64, +} + +impl TxBin { + /// Construct a new [`TxBin`], with an upper bound on the max number + /// of txs defined by a ratio over Tendermint's own max. + #[allow(dead_code)] + #[inline] + fn init_from(provided_by_tendermint: u64, frac: Ratio) -> Self { + let alloted_space = (frac * provided_by_tendermint).to_integer(); + Self { + alloted_space, + current_space: 0, } } } From 278a66d9a5fd71b8d244e583125cae186b7feaa2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:15:41 +0000 Subject: [PATCH 1520/1995] Revert "Add num-rational to apps' deps" This reverts commit c7d8ad5f7e1e99f00aef169584b03618e287705d. --- Cargo.lock | 1 - apps/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95a7a1ea83..20d428c9fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3424,7 +3424,6 @@ dependencies = [ "message-io", "namada", "num-derive", - "num-rational 0.4.1", "num-traits 0.2.15", "num256", "num_cpus", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 6b74452609..a347ab78fa 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -104,7 +104,6 @@ libloading = "0.7.2" message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} num256 = "0.3.5" num-derive = "0.3.3" -num-rational = "0.4.1" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" From ccb1a6ef0e628aa760624104566df6c1eb52b965 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:16:30 +0000 Subject: [PATCH 1521/1995] Remove ratio dep --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 78f3b613d3..164816a34e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -9,7 +9,6 @@ use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use num_rational::Ratio; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -52,13 +51,13 @@ impl TxAllotedSpace { #[inline] fn init_from(req: &RequestPrepareProposal) -> Self { // each tx bin gets 1/3 of the alloted space - let thres = Ratio::new(1, 3); + const THRES: f64 = 1.0 / 3.0; let max = req.max_tx_bytes as u64; Self { provided_by_tendermint: max, - protocol_txs: TxBin::init_from(max, thres), - encrypted_txs: TxBin::init_from(max, thres), - decrypted_txs: TxBin::init_from(max, thres), + protocol_txs: TxBin::init_from(max, THRES), + encrypted_txs: TxBin::init_from(max, THRES), + decrypted_txs: TxBin::init_from(max, THRES), } } } @@ -80,8 +79,8 @@ impl TxBin { /// of txs defined by a ratio over Tendermint's own max. #[allow(dead_code)] #[inline] - fn init_from(provided_by_tendermint: u64, frac: Ratio) -> Self { - let alloted_space = (frac * provided_by_tendermint).to_integer(); + fn init_from(provided_by_tendermint: u64, frac: f64) -> Self { + let alloted_space = (provided_by_tendermint as f64 * frac) as u64; Self { alloted_space, current_space: 0, From 945151882e81b58a3a626b687b560bad0e856ed7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:30:33 +0000 Subject: [PATCH 1522/1995] WIP: Counting occupied space by txs --- .../lib/node/ledger/shell/prepare_proposal.rs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 164816a34e..92ba74f4ba 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -60,6 +60,15 @@ impl TxAllotedSpace { decrypted_txs: TxBin::init_from(max, THRES), } } + + /// The total space, in bytes, occupied by each transaction. + #[allow(dead_code)] + #[inline] + fn occupied_space(&self) -> u64 { + self.protocol_txs.current_space + + self.encrypted_txs.current_space + + self.decrypted_txs.current_space + } } /// Alloted space for a batch of transactions of the same kind in some @@ -67,9 +76,9 @@ impl TxAllotedSpace { #[derive(Default)] #[allow(dead_code)] struct TxBin { - /// The current space utilized by a batch of transactions. + /// The current space utilized by the batch of transactions. current_space: u64, - /// The maximum space a batch of transactions of the same kind + /// The maximum space the batch of transactions of the same kind /// may occupy. alloted_space: u64, } @@ -79,13 +88,26 @@ impl TxBin { /// of txs defined by a ratio over Tendermint's own max. #[allow(dead_code)] #[inline] - fn init_from(provided_by_tendermint: u64, frac: f64) -> Self { - let alloted_space = (provided_by_tendermint as f64 * frac) as u64; + fn init_from(tendermint_max_block_space: u64, frac: f64) -> Self { + let alloted_space = (tendermint_max_block_space as f64 * frac) as u64; Self { alloted_space, current_space: 0, } } + + /// Try to dump a new tx into this [`TxBin`]. + #[allow(dead_code)] + #[inline] + fn maybe_dump(&mut self, tx: &[u8]) -> Option<()> { + let new_space = self.current_space + tx.len() as u64; + if new_space > alloted_space { + self.current_space = new_space; + Some(()) + } else { + None + } + } } impl Shell From e104023a05a7c13eb541bfd2693126c0df84fa0b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:40:01 +0000 Subject: [PATCH 1523/1995] Move tx bins abstraction to a new module --- .../lib/node/ledger/shell/prepare_proposal.rs | 90 +----------------- .../ledger/shell/prepare_proposal/tx_bins.rs | 95 +++++++++++++++++++ 2 files changed, 97 insertions(+), 88 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 92ba74f4ba..5fd50995f2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,5 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell +mod tx_bins; + use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; @@ -22,94 +24,6 @@ use crate::node::ledger::shell::vote_extensions::{ use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; -/// Alloted space for a batch of transactions in some proposed block, -/// measured in bytes. -/// -/// We keep track of the current space utilized by: -/// -/// - Protocol transactions. -/// - DKG decrypted transactions. -/// - DKG encrypted transactions. -#[derive(Default)] -#[allow(dead_code)] -struct TxAllotedSpace { - /// The total space Tendermint has allotted to the - /// application for the current block height. - provided_by_tendermint: u64, - /// The current space utilized by protocol transactions. - protocol_txs: TxBin, - /// The current space utilized by DKG encrypted transactions. - encrypted_txs: TxBin, - /// The current space utilized by DKG decrypted transactions. - decrypted_txs: TxBin, -} - -impl TxAllotedSpace { - /// Construct a new [`TxAllotedSpace`], with an upper bound - /// on the max number of txs in a block defined by Tendermint. - #[allow(dead_code)] - #[inline] - fn init_from(req: &RequestPrepareProposal) -> Self { - // each tx bin gets 1/3 of the alloted space - const THRES: f64 = 1.0 / 3.0; - let max = req.max_tx_bytes as u64; - Self { - provided_by_tendermint: max, - protocol_txs: TxBin::init_from(max, THRES), - encrypted_txs: TxBin::init_from(max, THRES), - decrypted_txs: TxBin::init_from(max, THRES), - } - } - - /// The total space, in bytes, occupied by each transaction. - #[allow(dead_code)] - #[inline] - fn occupied_space(&self) -> u64 { - self.protocol_txs.current_space - + self.encrypted_txs.current_space - + self.decrypted_txs.current_space - } -} - -/// Alloted space for a batch of transactions of the same kind in some -/// proposed block, measured in bytes. -#[derive(Default)] -#[allow(dead_code)] -struct TxBin { - /// The current space utilized by the batch of transactions. - current_space: u64, - /// The maximum space the batch of transactions of the same kind - /// may occupy. - alloted_space: u64, -} - -impl TxBin { - /// Construct a new [`TxBin`], with an upper bound on the max number - /// of txs defined by a ratio over Tendermint's own max. - #[allow(dead_code)] - #[inline] - fn init_from(tendermint_max_block_space: u64, frac: f64) -> Self { - let alloted_space = (tendermint_max_block_space as f64 * frac) as u64; - Self { - alloted_space, - current_space: 0, - } - } - - /// Try to dump a new tx into this [`TxBin`]. - #[allow(dead_code)] - #[inline] - fn maybe_dump(&mut self, tx: &[u8]) -> Option<()> { - let new_space = self.current_space + tx.len() as u64; - if new_space > alloted_space { - self.current_space = new_space; - Some(()) - } else { - None - } - } -} - impl Shell where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs new file mode 100644 index 0000000000..2153f105dd --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -0,0 +1,95 @@ +//! Primitives that facilitate keeping track of the number +//! of bytes utilized by the current consensus round's proposal. +//! +//! This is important, because Tendermint places an upper bound +//! on the size of a block. + +use crate::facade::tendermint_proto::abci::RequestPrepareProposal; + +/// Alloted space for a batch of transactions in some proposed block, +/// measured in bytes. +/// +/// We keep track of the current space utilized by: +/// +/// - Protocol transactions. +/// - DKG decrypted transactions. +/// - DKG encrypted transactions. +#[derive(Default)] +#[allow(dead_code)] +struct TxAllotedSpace { + /// The total space Tendermint has allotted to the + /// application for the current block height. + provided_by_tendermint: u64, + /// The current space utilized by protocol transactions. + protocol_txs: TxBin, + /// The current space utilized by DKG encrypted transactions. + encrypted_txs: TxBin, + /// The current space utilized by DKG decrypted transactions. + decrypted_txs: TxBin, +} + +impl TxAllotedSpace { + /// Construct a new [`TxAllotedSpace`], with an upper bound + /// on the max number of txs in a block defined by Tendermint. + #[allow(dead_code)] + #[inline] + fn init_from(req: &RequestPrepareProposal) -> Self { + // each tx bin gets 1/3 of the alloted space + const THRES: f64 = 1.0 / 3.0; + let max = req.max_tx_bytes as u64; + Self { + provided_by_tendermint: max, + protocol_txs: TxBin::init_from(max, THRES), + encrypted_txs: TxBin::init_from(max, THRES), + decrypted_txs: TxBin::init_from(max, THRES), + } + } + + /// The total space, in bytes, occupied by each transaction. + #[allow(dead_code)] + #[inline] + fn occupied_space(&self) -> u64 { + self.protocol_txs.current_space + + self.encrypted_txs.current_space + + self.decrypted_txs.current_space + } +} + +/// Alloted space for a batch of transactions of the same kind in some +/// proposed block, measured in bytes. +#[derive(Default)] +#[allow(dead_code)] +struct TxBin { + /// The current space utilized by the batch of transactions. + current_space: u64, + /// The maximum space the batch of transactions of the same kind + /// may occupy. + alloted_space: u64, +} + +impl TxBin { + /// Construct a new [`TxBin`], with an upper bound on the max number + /// of txs defined by a ratio over Tendermint's own max. + #[allow(dead_code)] + #[inline] + fn init_from(tendermint_max_block_space: u64, frac: f64) -> Self { + let alloted_space = (tendermint_max_block_space as f64 * frac) as u64; + Self { + alloted_space, + current_space: 0, + } + } + + /// Try to dump a new tx into this [`TxBin`]. + #[allow(dead_code)] + #[inline] + fn maybe_dump(&mut self, tx: &[u8]) -> Option<()> { + let new_space = self.current_space + tx.len() as u64; + if new_space > self.alloted_space { + self.current_space = new_space; + Some(()) + } else { + None + } + } +} From 7b32f9e96e35602fd6f2df969194ce02a070bdc5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:57:28 +0000 Subject: [PATCH 1524/1995] Tx bin refactoring --- .../ledger/shell/prepare_proposal/tx_bins.rs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 2153f105dd..259919b5cb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -16,7 +16,7 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// - DKG encrypted transactions. #[derive(Default)] #[allow(dead_code)] -struct TxAllotedSpace { +pub struct TxAllotedSpace { /// The total space Tendermint has allotted to the /// application for the current block height. provided_by_tendermint: u64, @@ -33,7 +33,7 @@ impl TxAllotedSpace { /// on the max number of txs in a block defined by Tendermint. #[allow(dead_code)] #[inline] - fn init_from(req: &RequestPrepareProposal) -> Self { + pub fn init_from(req: &RequestPrepareProposal) -> Self { // each tx bin gets 1/3 of the alloted space const THRES: f64 = 1.0 / 3.0; let max = req.max_tx_bytes as u64; @@ -45,10 +45,31 @@ impl TxAllotedSpace { } } + /// Try to allocate space for a new protocol transaction. + #[allow(dead_code)] + #[inline] + pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { + self.protocol_txs.try_dump(tx) + } + + /// Try to allocate space for a new DKG encrypted transaction. + #[allow(dead_code)] + #[inline] + pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { + self.encrypted_txs.try_dump(tx) + } + + /// Try to allocate space for a new DKG decrypted transaction. + #[allow(dead_code)] + #[inline] + pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { + self.decrypted_txs.try_dump(tx) + } + /// The total space, in bytes, occupied by each transaction. #[allow(dead_code)] #[inline] - fn occupied_space(&self) -> u64 { + pub fn occupied_space(&self) -> u64 { self.protocol_txs.current_space + self.encrypted_txs.current_space + self.decrypted_txs.current_space @@ -62,8 +83,7 @@ impl TxAllotedSpace { struct TxBin { /// The current space utilized by the batch of transactions. current_space: u64, - /// The maximum space the batch of transactions of the same kind - /// may occupy. + /// The maximum space the batch of transactions may occupy. alloted_space: u64, } @@ -80,16 +100,16 @@ impl TxBin { } } - /// Try to dump a new tx into this [`TxBin`]. + /// Try to dump a new transaction into this [`TxBin`]. #[allow(dead_code)] #[inline] - fn maybe_dump(&mut self, tx: &[u8]) -> Option<()> { + fn try_dump(&mut self, tx: &[u8]) -> bool { let new_space = self.current_space + tx.len() as u64; if new_space > self.alloted_space { self.current_space = new_space; - Some(()) + true } else { - None + false } } } From 4952f09939d1e5bc56a095e89fcc8c7a4ac464d0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 10:01:13 +0000 Subject: [PATCH 1525/1995] Improve docstring --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 259919b5cb..d94fea4b50 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -89,7 +89,7 @@ struct TxBin { impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number - /// of txs defined by a ratio over Tendermint's own max. + /// of storable txs defined by a ratio over Tendermint max block size. #[allow(dead_code)] #[inline] fn init_from(tendermint_max_block_space: u64, frac: f64) -> Self { From e5be35aa284f1382476f568780c1579c7c785604 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 11:00:56 +0000 Subject: [PATCH 1526/1995] Move tx allotment thresholds to a new mod --- .../ledger/shell/prepare_proposal/tx_bins.rs | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index d94fea4b50..e068b55c18 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -2,7 +2,11 @@ //! of bytes utilized by the current consensus round's proposal. //! //! This is important, because Tendermint places an upper bound -//! on the size of a block. +//! on the size of a block, rejecting blocks whose size exceeds +//! the limit stated in [`RequestPrepareProposal`]. +//! +//! In the current implementation, each kind of transaction in +//! Namada gets a portion of the total alloted space. use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -34,14 +38,12 @@ impl TxAllotedSpace { #[allow(dead_code)] #[inline] pub fn init_from(req: &RequestPrepareProposal) -> Self { - // each tx bin gets 1/3 of the alloted space - const THRES: f64 = 1.0 / 3.0; let max = req.max_tx_bytes as u64; Self { provided_by_tendermint: max, - protocol_txs: TxBin::init_from(max, THRES), - encrypted_txs: TxBin::init_from(max, THRES), - decrypted_txs: TxBin::init_from(max, THRES), + protocol_txs: TxBin::init_from(max, thres::PROTOCOL_TX), + encrypted_txs: TxBin::init_from(max, thres::ENCRYPTED_TX), + decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), } } @@ -113,3 +115,43 @@ impl TxBin { } } } + +mod thres { + //! Transaction allotment thresholds. + + /// The threshold over Tendermint's alloted space for protocol txs. + pub const PROTOCOL_TX: f64 = 1.0 / 3.0; + + /// The threshold over Tendermint's alloted space for DKG encrypted txs. + pub const ENCRYPTED_TX: f64 = 1.0 / 3.0; + + /// The threshold over Tendermint's alloted space for DKG decrypted txs. + pub const DECRYPTED_TX: f64 = 1.0 / 3.0; + + /// Return the sum of all individual transaction allotment thresholds, + /// defined for each kind of transaction in Namada. + #[allow(dead_code)] + pub fn sum_individual_tx_thresholds() -> f64 { + // NOTE: VERY IMPORTANT !! !!11!!!!ONE! + // ==================================== + // remember to add new kinds of transactions here, + // as the codebase mutates and evolves over time + PROTOCOL_TX + ENCRYPTED_TX + DECRYPTED_TX + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Check if the sum of all individual tx thresholds does + /// not exceed one. + /// + /// This is important, because we do not want to exceed + /// the maximum block size in Tendermint, and get randomly + /// rejected blocks. + #[test] + fn test_tx_thres_doesnt_exceed_one() { + assert!(thres::sum_individual_tx_thresholds() <= 1.0) + } +} From 4acea334ebe956c45ea91a665acb9a142220448e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 11:02:28 +0000 Subject: [PATCH 1527/1995] Module docstring improvements for tx_bins --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index e068b55c18..588edd855e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -1,12 +1,13 @@ //! Primitives that facilitate keeping track of the number -//! of bytes utilized by the current consensus round's proposal. +//! of bytes utilized by some Tendermint consensus round's proposal. //! //! This is important, because Tendermint places an upper bound //! on the size of a block, rejecting blocks whose size exceeds //! the limit stated in [`RequestPrepareProposal`]. //! //! In the current implementation, each kind of transaction in -//! Namada gets a portion of the total alloted space. +//! Namada gets a portion of (i.e. threshold over) the total +//! alloted space. use crate::facade::tendermint_proto::abci::RequestPrepareProposal; From 702c6e08d2e7220d4693bab61180d66417549142 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 11:25:30 +0000 Subject: [PATCH 1528/1995] Test bin spaces --- .../ledger/shell/prepare_proposal/tx_bins.rs | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 588edd855e..290a1711ac 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -33,13 +33,21 @@ pub struct TxAllotedSpace { decrypted_txs: TxBin, } +impl From<&RequestPrepareProposal> for TxAllotedSpace { + #[inline] + fn from(req: &RequestPrepareProposal) -> Self { + let tendermint_max_block_space = req.max_tx_bytes as u64; + Self::init(tendermint_max_block_space) + } +} + impl TxAllotedSpace { /// Construct a new [`TxAllotedSpace`], with an upper bound /// on the max number of txs in a block defined by Tendermint. #[allow(dead_code)] #[inline] - pub fn init_from(req: &RequestPrepareProposal) -> Self { - let max = req.max_tx_bytes as u64; + pub fn init(tendermint_max_block_space: u64) -> Self { + let max = tendermint_max_block_space; Self { provided_by_tendermint: max, protocol_txs: TxBin::init_from(max, thres::PROTOCOL_TX), @@ -77,6 +85,21 @@ impl TxAllotedSpace { + self.encrypted_txs.current_space + self.decrypted_txs.current_space } + + /// Return the amount, in bytes, of free space in this + /// [`TxAllotedSpace`]. + #[allow(dead_code)] + #[inline] + pub fn free_space(&self) -> u64 { + self.provided_by_tendermint - self.occupied_space() + } + + /// Checks if this [`TxAllotedSpace`] has any free space remaining. + #[allow(dead_code)] + #[inline] + pub fn has_free_space(&self) -> bool { + self.free_space() > 0 + } } /// Alloted space for a batch of transactions of the same kind in some @@ -128,21 +151,12 @@ mod thres { /// The threshold over Tendermint's alloted space for DKG decrypted txs. pub const DECRYPTED_TX: f64 = 1.0 / 3.0; - - /// Return the sum of all individual transaction allotment thresholds, - /// defined for each kind of transaction in Namada. - #[allow(dead_code)] - pub fn sum_individual_tx_thresholds() -> f64 { - // NOTE: VERY IMPORTANT !! !!11!!!!ONE! - // ==================================== - // remember to add new kinds of transactions here, - // as the codebase mutates and evolves over time - PROTOCOL_TX + ENCRYPTED_TX + DECRYPTED_TX - } } #[cfg(test)] mod tests { + use proptest::prelude::*; + use super::*; /// Check if the sum of all individual tx thresholds does @@ -153,6 +167,29 @@ mod tests { /// rejected blocks. #[test] fn test_tx_thres_doesnt_exceed_one() { - assert!(thres::sum_individual_tx_thresholds() <= 1.0) + let sum = + thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; + assert!(sum <= 1.0) + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space: u64, + ) { + let bins = TxAllotedSpace::init(tendermint_max_block_space); + let total_bin_space = bins.protocol_txs.alloted_space + + bins.encrypted_txs.alloted_space + + bins.decrypted_txs.alloted_space; + assert_eq!(bins.provided_by_tendermint, total_bin_space); + } + + proptest! { + /// Check if the sum of all individual bin allotments for a + /// [`TxAllotedSpace`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in 0..u64::MAX) { + proptest_bin_capacity_eq_provided_space(max) + } } } From 38ab83c55052c59101f726d47211d938e5f02ec6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 13:26:04 +0000 Subject: [PATCH 1529/1995] Concede leftover space to protocol txs --- .../node/ledger/shell/prepare_proposal/tx_bins.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 290a1711ac..5111ad0930 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -48,12 +48,23 @@ impl TxAllotedSpace { #[inline] pub fn init(tendermint_max_block_space: u64) -> Self { let max = tendermint_max_block_space; - Self { + let mut bins = Self { provided_by_tendermint: max, protocol_txs: TxBin::init_from(max, thres::PROTOCOL_TX), encrypted_txs: TxBin::init_from(max, thres::ENCRYPTED_TX), decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), - } + }; + // concede leftover space to protocol txs + bins.protocol_txs.alloted_space += bins.leftover_space(); + bins + } + + /// Return leftover space in bins, resulting from float conversions. + fn leftover_space(&self) -> u64 { + let total_bin_space = self.protocol_txs.alloted_space + + self.encrypted_txs.alloted_space + + self.decrypted_txs.alloted_space; + self.provided_by_tendermint - total_bin_space } /// Try to allocate space for a new protocol transaction. From 058b32eb8e107dad4f169f0dbad8b14b1fa06fba Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 09:07:49 +0000 Subject: [PATCH 1530/1995] Add num-rational to apps' deps --- Cargo.lock | 1 + apps/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 20d428c9fe..95a7a1ea83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3424,6 +3424,7 @@ dependencies = [ "message-io", "namada", "num-derive", + "num-rational 0.4.1", "num-traits 0.2.15", "num256", "num_cpus", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index a347ab78fa..6b74452609 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -104,6 +104,7 @@ libloading = "0.7.2" message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} num256 = "0.3.5" num-derive = "0.3.3" +num-rational = "0.4.1" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" From 7c4d7938b67d356fedf42cb51741a98ad14dadbd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 13:37:39 +0000 Subject: [PATCH 1531/1995] Fix underflow bugs --- .../ledger/shell/prepare_proposal/tx_bins.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 5111ad0930..3c6f920048 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -9,6 +9,8 @@ //! Namada gets a portion of (i.e. threshold over) the total //! alloted space. +use num_rational::Ratio; + use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// Alloted space for a batch of transactions in some proposed block, @@ -59,7 +61,7 @@ impl TxAllotedSpace { bins } - /// Return leftover space in bins, resulting from float conversions. + /// Return leftover space in bins, resulting from ratio conversions. fn leftover_space(&self) -> u64 { let total_bin_space = self.protocol_txs.alloted_space + self.encrypted_txs.alloted_space @@ -129,8 +131,8 @@ impl TxBin { /// of storable txs defined by a ratio over Tendermint max block size. #[allow(dead_code)] #[inline] - fn init_from(tendermint_max_block_space: u64, frac: f64) -> Self { - let alloted_space = (tendermint_max_block_space as f64 * frac) as u64; + fn init_from(tendermint_max_block_space: u64, frac: Ratio) -> Self { + let alloted_space = (frac * tendermint_max_block_space).to_integer(); Self { alloted_space, current_space: 0, @@ -154,14 +156,16 @@ impl TxBin { mod thres { //! Transaction allotment thresholds. + use num_rational::Ratio; + /// The threshold over Tendermint's alloted space for protocol txs. - pub const PROTOCOL_TX: f64 = 1.0 / 3.0; + pub const PROTOCOL_TX: Ratio = Ratio::new_raw(1, 3); /// The threshold over Tendermint's alloted space for DKG encrypted txs. - pub const ENCRYPTED_TX: f64 = 1.0 / 3.0; + pub const ENCRYPTED_TX: Ratio = Ratio::new_raw(1, 3); /// The threshold over Tendermint's alloted space for DKG decrypted txs. - pub const DECRYPTED_TX: f64 = 1.0 / 3.0; + pub const DECRYPTED_TX: Ratio = Ratio::new_raw(1, 3); } #[cfg(test)] @@ -180,7 +184,7 @@ mod tests { fn test_tx_thres_doesnt_exceed_one() { let sum = thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert!(sum <= 1.0) + assert_eq!(sum.to_integer(), 1); } /// Implementation of [`test_bin_capacity_eq_provided_space`]. From eb1cc04cf4d730c8d59bb7fb7238510f441bf2d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 13:40:15 +0000 Subject: [PATCH 1532/1995] Refactor test_bin_capacity_eq_provided_space() to use leftover_space() --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 3c6f920048..77cdd10e62 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -192,10 +192,7 @@ mod tests { tendermint_max_block_space: u64, ) { let bins = TxAllotedSpace::init(tendermint_max_block_space); - let total_bin_space = bins.protocol_txs.alloted_space - + bins.encrypted_txs.alloted_space - + bins.decrypted_txs.alloted_space; - assert_eq!(bins.provided_by_tendermint, total_bin_space); + assert_eq!(0, bins.leftover_space()); } proptest! { From 91b644c18b693f6703a5dcc9b01a0717d98933da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 13:50:47 +0000 Subject: [PATCH 1533/1995] Improve TODO comment --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5fd50995f2..0e7ac9828f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -158,7 +158,8 @@ where validator_set_update, }) .map(|tx| tx.sign(protocol_key).to_bytes()) - // TODO: remove this later, when we get rid of `abciplus` + // TODO(feature = "abcipp"): remove this later, when we get rid of + // `abciplus` .chain(protocol_txs.into_iter()) .collect() } From 53b14bd2abbe0eb19b449b235a6d6b26d3055549 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 13:58:04 +0000 Subject: [PATCH 1534/1995] Add a TODO item --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 77cdd10e62..0d9b2b9b95 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -9,6 +9,13 @@ //! Namada gets a portion of (i.e. threshold over) the total //! alloted space. +// TODO: what if a tx has a size greater than the threshold for +// its bin? how do we handle this? if we keep it in the mempool +// forever, it'll be a DoS vec, as we can make nodes run out of +// memory! maybe we should allow block decisions for txs that are +// too big to fit in their respective bin? in these special block +// decisions, we would only decide proposals with "large" txs + use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; From edd5eb8d94fab719337ff7a9afac37364b4fd1ee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 10 Nov 2022 16:23:56 +0000 Subject: [PATCH 1535/1995] WIP: Prop compose strat to generate arb txs --- .../ledger/shell/prepare_proposal/tx_bins.rs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 0d9b2b9b95..168c222a8a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -16,6 +16,9 @@ // too big to fit in their respective bin? in these special block // decisions, we would only decide proposals with "large" txs +// TODO: refactor our measure of space to also reflect gas costs! +// we can only pick txs up to a certain cumulative gas cost + use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -207,8 +210,31 @@ mod tests { /// [`TxAllotedSpace`] corresponds to the total space ceded /// by Tendermint. #[test] - fn test_bin_capacity_eq_provided_space(max in 0..u64::MAX) { + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { proptest_bin_capacity_eq_provided_space(max) } } + + prop_compose! { + // WIP: generate vecs of vecs, each inner vec with a random length + // (contains tx payloads of arb length) + fn arb_transactions_with_min_block_space + // the minimum block space Tendermint will give us + (min_block_space: u64) + // create base strategies + ( + tendermint_max_block_space in min_block_space..u64::MAX, + no_of_mempool_txs in prop::num::u64::ANY, + ) + // compose strategies + ( + max_length in Just((Ratio::new_raw(1, 3) * tendermint_max_block_space).to_integer()), + tx in prop::collection::vec(0u8.., max_length), + tx_kinds in prop::collection::vec(0u8..3, no_of_mempool_txs), + ) + -> { + () + } + + } } From 96a41c27e8c4af6ab95cd2d144accc73dfe7546f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 10:16:50 +0000 Subject: [PATCH 1536/1995] Add proptest strategy that yields arb txs --- .../ledger/shell/prepare_proposal/tx_bins.rs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 168c222a8a..45f5720188 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -16,8 +16,9 @@ // too big to fit in their respective bin? in these special block // decisions, we would only decide proposals with "large" txs -// TODO: refactor our measure of space to also reflect gas costs! -// we can only pick txs up to a certain cumulative gas cost +// TODO: refactor our measure of space to also reflect gas costs. +// the total gas of all chosen txs cannot exceed the configured max +// gas per block, otherwise a proposal will be rejected! use num_rational::Ratio; @@ -223,18 +224,37 @@ mod tests { (min_block_space: u64) // create base strategies ( - tendermint_max_block_space in min_block_space..u64::MAX, - no_of_mempool_txs in prop::num::u64::ANY, + (tendermint_max_block_space, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(min_block_space), ) // compose strategies ( - max_length in Just((Ratio::new_raw(1, 3) * tendermint_max_block_space).to_integer()), - tx in prop::collection::vec(0u8.., max_length), - tx_kinds in prop::collection::vec(0u8..3, no_of_mempool_txs), + tendermint_max_block_space in Just(tendermint_max_block_space), + protocol_txs in prop::collection::vec(prop::num::u8::ANY, 0..=protocol_tx_max_bin_size), + encrypted_txs in prop::collection::vec(prop::num::u8::ANY, 0..=encrypted_tx_max_bin_size), + decrypted_txs in prop::collection::vec(prop::num::u8::ANY, 0..=decrypted_tx_max_bin_size), ) - -> { - () + -> (u64, Vec, Vec, Vec) { + (tendermint_max_block_space, protocol_txs, encrypted_txs, decrypted_txs) } } + + /// Return random bin sizes for a [`TxAllotedSpace`]. + #[allow(dead_code)] + fn arb_max_bin_sizes( + min_block_space: u64, + ) -> impl Strategy { + (min_block_space..u64::MAX).prop_map(|tendermint_max_block_space| { + ( + tendermint_max_block_space, + (thres::PROTOCOL_TX * tendermint_max_block_space).to_integer() + as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space).to_integer() + as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space).to_integer() + as usize, + ) + }) + } } From 31a6d39cbedb69cd24974b3d95a54529517b9ec9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 10:26:33 +0000 Subject: [PATCH 1537/1995] Remove min block space param --- .../ledger/shell/prepare_proposal/tx_bins.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 45f5720188..47ce5133c5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -217,15 +217,12 @@ mod tests { } prop_compose! { - // WIP: generate vecs of vecs, each inner vec with a random length - // (contains tx payloads of arb length) - fn arb_transactions_with_min_block_space - // the minimum block space Tendermint will give us - (min_block_space: u64) + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions_with_min_block_space() // create base strategies ( (tendermint_max_block_space, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, - decrypted_tx_max_bin_size) in arb_max_bin_sizes(min_block_space), + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), ) // compose strategies ( @@ -237,15 +234,13 @@ mod tests { -> (u64, Vec, Vec, Vec) { (tendermint_max_block_space, protocol_txs, encrypted_txs, decrypted_txs) } - } /// Return random bin sizes for a [`TxAllotedSpace`]. #[allow(dead_code)] - fn arb_max_bin_sizes( - min_block_space: u64, - ) -> impl Strategy { - (min_block_space..u64::MAX).prop_map(|tendermint_max_block_space| { + fn arb_max_bin_sizes() -> impl Strategy + { + (1..=u64::MAX).prop_map(|tendermint_max_block_space| { ( tendermint_max_block_space, (thres::PROTOCOL_TX * tendermint_max_block_space).to_integer() From 0578b22e6a5d4dcdb2d2d428395958cd81631fe4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 10:56:46 +0000 Subject: [PATCH 1538/1995] Write tx_dump_doesnt_overflow_bin() test --- .../ledger/shell/prepare_proposal/tx_bins.rs | 99 ++++++++++++++++--- 1 file changed, 85 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 47ce5133c5..04a7c77594 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -181,9 +181,21 @@ mod thres { #[cfg(test)] mod tests { + use std::cell::RefCell; + use proptest::prelude::*; use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } /// Check if the sum of all individual tx thresholds does /// not exceed one. @@ -198,6 +210,23 @@ mod tests { assert_eq!(sum.to_integer(), 1); } + proptest! { + /// Check if the sum of all individual bin allotments for a + /// [`TxAllotedSpace`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not overflow the bin. + #[test] + fn test_tx_dump_doesnt_overflow_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_overflow_bin(args) + } + } + /// Implementation of [`test_bin_capacity_eq_provided_space`]. fn proptest_bin_capacity_eq_provided_space( tendermint_max_block_space: u64, @@ -206,19 +235,50 @@ mod tests { assert_eq!(0, bins.leftover_space()); } - proptest! { - /// Check if the sum of all individual bin allotments for a - /// [`TxAllotedSpace`] corresponds to the total space ceded - /// by Tendermint. - #[test] - fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { - proptest_bin_capacity_eq_provided_space(max) + /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. + fn proptest_tx_dump_doesnt_overflow_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + let bins = + RefCell::new(TxAllotedSpace::init(tendermint_max_block_space)); + + // produce new txs until we overflow the bins + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bins = bins.borrow(); + let new_size = bins.protocol_txs.current_space + tx.len() as u64; + new_size < bins.protocol_txs.alloted_space + }); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bins = bins.borrow(); + let new_size = bins.encrypted_txs.current_space + tx.len() as u64; + new_size < bins.encrypted_txs.alloted_space + }); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bins = bins.borrow(); + let new_size = bins.decrypted_txs.current_space + tx.len() as u64; + new_size < bins.decrypted_txs.alloted_space + }); + + // make sure we can keep dumping txs, + // without overflowing the bins + for tx in protocol_txs { + assert!(bins.borrow_mut().try_alloc_protocol_tx(&tx)); + } + for tx in encrypted_txs { + assert!(bins.borrow_mut().try_alloc_encrypted_tx(&tx)); + } + for tx in decrypted_txs { + assert!(bins.borrow_mut().try_alloc_decrypted_tx(&tx)); } } prop_compose! { /// Generate arbitrarily sized txs of different kinds. - fn arb_transactions_with_min_block_space() + fn arb_transactions() // create base strategies ( (tendermint_max_block_space, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, @@ -227,17 +287,21 @@ mod tests { // compose strategies ( tendermint_max_block_space in Just(tendermint_max_block_space), - protocol_txs in prop::collection::vec(prop::num::u8::ANY, 0..=protocol_tx_max_bin_size), - encrypted_txs in prop::collection::vec(prop::num::u8::ANY, 0..=encrypted_tx_max_bin_size), - decrypted_txs in prop::collection::vec(prop::num::u8::ANY, 0..=decrypted_tx_max_bin_size), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), ) - -> (u64, Vec, Vec, Vec) { - (tendermint_max_block_space, protocol_txs, encrypted_txs, decrypted_txs) + -> PropTx { + PropTx { + tendermint_max_block_space, + protocol_txs, + encrypted_txs, + decrypted_txs, + } } } /// Return random bin sizes for a [`TxAllotedSpace`]. - #[allow(dead_code)] fn arb_max_bin_sizes() -> impl Strategy { (1..=u64::MAX).prop_map(|tendermint_max_block_space| { @@ -252,4 +316,11 @@ mod tests { ) }) } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 8; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } } From d8523c35b3cbaa9e15a4b28a204bbf9920ee00b3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 11:12:46 +0000 Subject: [PATCH 1539/1995] Fix tx_dump_doesnt_overflow_bin() test --- .../ledger/shell/prepare_proposal/tx_bins.rs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 04a7c77594..b9fb10ca51 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -128,7 +128,7 @@ impl TxAllotedSpace { /// Alloted space for a batch of transactions of the same kind in some /// proposed block, measured in bytes. -#[derive(Default)] +#[derive(Copy, Clone, Default)] #[allow(dead_code)] struct TxBin { /// The current space utilized by the batch of transactions. @@ -247,20 +247,23 @@ mod tests { RefCell::new(TxAllotedSpace::init(tendermint_max_block_space)); // produce new txs until we overflow the bins + // + // TODO: ideally the proptest strategy would already return + // txs whose total added size would be bounded let protocol_txs = protocol_txs.into_iter().take_while(|tx| { - let bins = bins.borrow(); - let new_size = bins.protocol_txs.current_space + tx.len() as u64; - new_size < bins.protocol_txs.alloted_space + let bin = bins.borrow().protocol_txs; + let new_size = bin.current_space + tx.len() as u64; + new_size < bin.alloted_space }); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { - let bins = bins.borrow(); - let new_size = bins.encrypted_txs.current_space + tx.len() as u64; - new_size < bins.encrypted_txs.alloted_space + let bin = bins.borrow().encrypted_txs; + let new_size = bin.current_space + tx.len() as u64; + new_size < bin.alloted_space }); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bins = bins.borrow(); - let new_size = bins.decrypted_txs.current_space + tx.len() as u64; - new_size < bins.decrypted_txs.alloted_space + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space + tx.len() as u64; + new_size < bin.alloted_space }); // make sure we can keep dumping txs, @@ -304,7 +307,8 @@ mod tests { /// Return random bin sizes for a [`TxAllotedSpace`]. fn arb_max_bin_sizes() -> impl Strategy { - (1..=u64::MAX).prop_map(|tendermint_max_block_space| { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map(|tendermint_max_block_space| { ( tendermint_max_block_space, (thres::PROTOCOL_TX * tendermint_max_block_space).to_integer() @@ -319,8 +323,8 @@ mod tests { /// Return a list of txs. fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { - const MAX_TX_NUM: usize = 8; - let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 1..=max_bin_size); prop::collection::vec(tx, 0..=MAX_TX_NUM) } } From facf4dc9abe49dc57e60fc9852f6f19741b0cd86 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 11:13:10 +0000 Subject: [PATCH 1540/1995] Fix logic error caught by proptest --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index b9fb10ca51..ffd83a0408 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -154,9 +154,9 @@ impl TxBin { #[allow(dead_code)] #[inline] fn try_dump(&mut self, tx: &[u8]) -> bool { - let new_space = self.current_space + tx.len() as u64; - if new_space > self.alloted_space { - self.current_space = new_space; + let occupied = self.current_space + tx.len() as u64; + if occupied <= self.alloted_space { + self.current_space = occupied; true } else { false From fc725ce7c5c62817e67b0e6b63059e737c23ac5d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 11:16:12 +0000 Subject: [PATCH 1541/1995] Fix proptest strat with invalid value range --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ffd83a0408..da3beb9559 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -324,7 +324,7 @@ mod tests { /// Return a list of txs. fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { const MAX_TX_NUM: usize = 64; - let tx = prop::collection::vec(prop::num::u8::ANY, 1..=max_bin_size); + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); prop::collection::vec(tx, 0..=MAX_TX_NUM) } } From 5558dd5d13f79bc4316342abe1161d3ab0a8c9ac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 11:28:55 +0000 Subject: [PATCH 1542/1995] Add a TODO item --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index da3beb9559..989b8bca58 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -179,6 +179,7 @@ mod thres { pub const DECRYPTED_TX: Ratio = Ratio::new_raw(1, 3); } +// TOOD: write bin dump rejected test (tests full cap of bin) #[cfg(test)] mod tests { use std::cell::RefCell; From df64b1054984f840a5038bbb7c1f288e2fdc0ab5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 12:54:29 +0000 Subject: [PATCH 1543/1995] Check if we reject txs when a bin is all filled up --- .../ledger/shell/prepare_proposal/tx_bins.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 989b8bca58..33efe4457c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -179,7 +179,6 @@ mod thres { pub const DECRYPTED_TX: Ratio = Ratio::new_raw(1, 3); } -// TOOD: write bin dump rejected test (tests full cap of bin) #[cfg(test)] mod tests { use std::cell::RefCell; @@ -212,6 +211,13 @@ mod tests { } proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`TxAllotedSpace`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + /// Check if the sum of all individual bin allotments for a /// [`TxAllotedSpace`] corresponds to the total space ceded /// by Tendermint. @@ -228,6 +234,17 @@ mod tests { } } + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached(tendermint_max_block_space: u64) { + let mut bins = TxAllotedSpace::init(tendermint_max_block_space); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.current_space = bins.decrypted_txs.alloted_space; + + // make sure we can't + assert!(!bins.try_alloc_decrypted_tx(b"arbitrary tx bytes")); + } + /// Implementation of [`test_bin_capacity_eq_provided_space`]. fn proptest_bin_capacity_eq_provided_space( tendermint_max_block_space: u64, From 5a831488b997e98928771a98f644bbea10cbafca Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 12:56:55 +0000 Subject: [PATCH 1544/1995] Remove unnecessary #[allow(dead_code)] --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 33efe4457c..3e3431d520 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -33,7 +33,6 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// - DKG decrypted transactions. /// - DKG encrypted transactions. #[derive(Default)] -#[allow(dead_code)] pub struct TxAllotedSpace { /// The total space Tendermint has allotted to the /// application for the current block height. @@ -57,7 +56,6 @@ impl From<&RequestPrepareProposal> for TxAllotedSpace { impl TxAllotedSpace { /// Construct a new [`TxAllotedSpace`], with an upper bound /// on the max number of txs in a block defined by Tendermint. - #[allow(dead_code)] #[inline] pub fn init(tendermint_max_block_space: u64) -> Self { let max = tendermint_max_block_space; @@ -102,7 +100,6 @@ impl TxAllotedSpace { } /// The total space, in bytes, occupied by each transaction. - #[allow(dead_code)] #[inline] pub fn occupied_space(&self) -> u64 { self.protocol_txs.current_space @@ -112,7 +109,6 @@ impl TxAllotedSpace { /// Return the amount, in bytes, of free space in this /// [`TxAllotedSpace`]. - #[allow(dead_code)] #[inline] pub fn free_space(&self) -> u64 { self.provided_by_tendermint - self.occupied_space() @@ -129,7 +125,6 @@ impl TxAllotedSpace { /// Alloted space for a batch of transactions of the same kind in some /// proposed block, measured in bytes. #[derive(Copy, Clone, Default)] -#[allow(dead_code)] struct TxBin { /// The current space utilized by the batch of transactions. current_space: u64, @@ -140,7 +135,6 @@ struct TxBin { impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number /// of storable txs defined by a ratio over Tendermint max block size. - #[allow(dead_code)] #[inline] fn init_from(tendermint_max_block_space: u64, frac: Ratio) -> Self { let alloted_space = (frac * tendermint_max_block_space).to_integer(); @@ -151,7 +145,6 @@ impl TxBin { } /// Try to dump a new transaction into this [`TxBin`]. - #[allow(dead_code)] #[inline] fn try_dump(&mut self, tx: &[u8]) -> bool { let occupied = self.current_space + tx.len() as u64; From adddcfa90fe9eb42490e25be80f1b4cfaf20f231 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 13:24:35 +0000 Subject: [PATCH 1545/1995] Fix comment --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 3e3431d520..a44371bb39 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -234,7 +234,7 @@ mod tests { // fill the entire bin of decrypted txs bins.decrypted_txs.current_space = bins.decrypted_txs.alloted_space; - // make sure we can't + // make sure we can't dump any new decrypted txs in the bin assert!(!bins.try_alloc_decrypted_tx(b"arbitrary tx bytes")); } From e3976eb308200b58294c45612f2c35ea494b82b2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 13:27:40 +0000 Subject: [PATCH 1546/1995] Improve method name --- .../lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index a44371bb39..d0eaded013 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -66,12 +66,15 @@ impl TxAllotedSpace { decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), }; // concede leftover space to protocol txs - bins.protocol_txs.alloted_space += bins.leftover_space(); + bins.protocol_txs.alloted_space += bins.init_leftover_space(); bins } /// Return leftover space in bins, resulting from ratio conversions. - fn leftover_space(&self) -> u64 { + /// + /// This method should not be used outside of [`TxAllotedSpace`] + /// instance construction or unit testing. + fn init_leftover_space(&self) -> u64 { let total_bin_space = self.protocol_txs.alloted_space + self.encrypted_txs.alloted_space + self.decrypted_txs.alloted_space; @@ -243,7 +246,7 @@ mod tests { tendermint_max_block_space: u64, ) { let bins = TxAllotedSpace::init(tendermint_max_block_space); - assert_eq!(0, bins.leftover_space()); + assert_eq!(0, bins.init_leftover_space()); } /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. From 4046d8a89c211de214ec3d10b66ebd510ca8729c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 13:31:11 +0000 Subject: [PATCH 1547/1995] Docstr fix --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index d0eaded013..4934acd7ff 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -137,7 +137,7 @@ struct TxBin { impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number - /// of storable txs defined by a ratio over Tendermint max block size. + /// of storable txs defined by a ratio over Tendermint's max block size. #[inline] fn init_from(tendermint_max_block_space: u64, frac: Ratio) -> Self { let alloted_space = (frac * tendermint_max_block_space).to_integer(); From dc0064995ed2c3c6903aa2f7c40c47b8da621c3b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 14:06:10 +0000 Subject: [PATCH 1548/1995] Move votes::write to votes::storage::write --- .../transactions/ethereum_events/mod.rs | 4 ++-- .../transactions/validator_set_update/mod.rs | 7 +++++- .../src/ledger/protocol/transactions/votes.rs | 20 ++-------------- .../protocol/transactions/votes/storage.rs | 24 +++++++++++++++++++ 4 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 shared/src/ledger/protocol/transactions/votes/storage.rs diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 40130f46df..125dbeda30 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -13,7 +13,7 @@ use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; use crate::ledger::protocol::transactions::votes::{ - calculate_new, calculate_updated, write, + self, calculate_new, calculate_updated, }; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -168,7 +168,7 @@ where votes: vote_tracking, }; tracing::debug!("writing EthMsg - {:#?}", ð_msg_post); - write( + votes::storage::write( storage, ð_msg_keys, ð_msg_post.body, diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 8b77ce328e..22591cd834 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -137,7 +137,12 @@ where ?ext.voting_powers, "Applying validator set update state changes" ); - votes::write(storage, &valset_upd_keys, &ext.voting_powers, &tally)?; + votes::storage::write( + storage, + &valset_upd_keys, + &ext.voting_powers, + &tally, + )?; if confirmed { tracing::debug!( diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index a931efbb66..baac41584e 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -15,6 +15,8 @@ use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; +pub(super) mod storage; + /// The addresses of validators that voted for something, and the block /// heights at which they voted. We use a [`BTreeMap`] to enforce that a /// validator (as uniquely identified by an [`Address`]) may vote at most once, @@ -104,24 +106,6 @@ where Ok((tally, ChangedKeys::default())) } -pub fn write( - storage: &mut Storage, - keys: &vote_tallies::Keys, - body: &T, - tally: &Tally, -) -> Result<()> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - T: BorshSerialize, -{ - storage.write(&keys.body(), &body.try_to_vec()?)?; - storage.write(&keys.seen(), &tally.seen.try_to_vec()?)?; - storage.write(&keys.seen_by(), &tally.seen_by.try_to_vec()?)?; - storage.write(&keys.voting_power(), &tally.voting_power.try_to_vec()?)?; - Ok(()) -} - /// Deterministically constructs a [`Votes`] map from a set of validator /// addresses and the block heights they signed something at. We arbitrarily /// take the earliest block height for each validator address encountered. diff --git a/shared/src/ledger/protocol/transactions/votes/storage.rs b/shared/src/ledger/protocol/transactions/votes/storage.rs new file mode 100644 index 0000000000..22e738e626 --- /dev/null +++ b/shared/src/ledger/protocol/transactions/votes/storage.rs @@ -0,0 +1,24 @@ +use borsh::BorshSerialize; +use eyre::Result; + +use super::Tally; +use crate::ledger::eth_bridge::storage::vote_tallies; +use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; + +pub fn write( + storage: &mut Storage, + keys: &vote_tallies::Keys, + body: &T, + tally: &Tally, +) -> Result<()> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + T: BorshSerialize, +{ + storage.write(&keys.body(), &body.try_to_vec()?)?; + storage.write(&keys.seen(), &tally.seen.try_to_vec()?)?; + storage.write(&keys.seen_by(), &tally.seen_by.try_to_vec()?)?; + storage.write(&keys.voting_power(), &tally.voting_power.try_to_vec()?)?; + Ok(()) +} From 0ca9c7cea8d484359aa6b189336796349fd65ef1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:08:34 +0000 Subject: [PATCH 1549/1995] Fix typo: alloted -> allotted --- .../ledger/shell/prepare_proposal/tx_bins.rs | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 4934acd7ff..ffaf164696 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -7,7 +7,7 @@ //! //! In the current implementation, each kind of transaction in //! Namada gets a portion of (i.e. threshold over) the total -//! alloted space. +//! allotted space. // TODO: what if a tx has a size greater than the threshold for // its bin? how do we handle this? if we keep it in the mempool @@ -24,7 +24,7 @@ use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; -/// Alloted space for a batch of transactions in some proposed block, +/// Allotted space for a batch of transactions in some proposed block, /// measured in bytes. /// /// We keep track of the current space utilized by: @@ -33,7 +33,7 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// - DKG decrypted transactions. /// - DKG encrypted transactions. #[derive(Default)] -pub struct TxAllotedSpace { +pub struct TxAllottedSpace { /// The total space Tendermint has allotted to the /// application for the current block height. provided_by_tendermint: u64, @@ -45,7 +45,7 @@ pub struct TxAllotedSpace { decrypted_txs: TxBin, } -impl From<&RequestPrepareProposal> for TxAllotedSpace { +impl From<&RequestPrepareProposal> for TxAllottedSpace { #[inline] fn from(req: &RequestPrepareProposal) -> Self { let tendermint_max_block_space = req.max_tx_bytes as u64; @@ -53,8 +53,8 @@ impl From<&RequestPrepareProposal> for TxAllotedSpace { } } -impl TxAllotedSpace { - /// Construct a new [`TxAllotedSpace`], with an upper bound +impl TxAllottedSpace { + /// Construct a new [`TxAllottedSpace`], with an upper bound /// on the max number of txs in a block defined by Tendermint. #[inline] pub fn init(tendermint_max_block_space: u64) -> Self { @@ -66,18 +66,18 @@ impl TxAllotedSpace { decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), }; // concede leftover space to protocol txs - bins.protocol_txs.alloted_space += bins.init_leftover_space(); + bins.protocol_txs.allotted_space += bins.init_leftover_space(); bins } /// Return leftover space in bins, resulting from ratio conversions. /// - /// This method should not be used outside of [`TxAllotedSpace`] + /// This method should not be used outside of [`TxAllottedSpace`] /// instance construction or unit testing. fn init_leftover_space(&self) -> u64 { - let total_bin_space = self.protocol_txs.alloted_space - + self.encrypted_txs.alloted_space - + self.decrypted_txs.alloted_space; + let total_bin_space = self.protocol_txs.allotted_space + + self.encrypted_txs.allotted_space + + self.decrypted_txs.allotted_space; self.provided_by_tendermint - total_bin_space } @@ -111,13 +111,13 @@ impl TxAllotedSpace { } /// Return the amount, in bytes, of free space in this - /// [`TxAllotedSpace`]. + /// [`TxAllottedSpace`]. #[inline] pub fn free_space(&self) -> u64 { self.provided_by_tendermint - self.occupied_space() } - /// Checks if this [`TxAllotedSpace`] has any free space remaining. + /// Checks if this [`TxAllottedSpace`] has any free space remaining. #[allow(dead_code)] #[inline] pub fn has_free_space(&self) -> bool { @@ -125,14 +125,14 @@ impl TxAllotedSpace { } } -/// Alloted space for a batch of transactions of the same kind in some +/// Allotted space for a batch of transactions of the same kind in some /// proposed block, measured in bytes. #[derive(Copy, Clone, Default)] struct TxBin { /// The current space utilized by the batch of transactions. current_space: u64, /// The maximum space the batch of transactions may occupy. - alloted_space: u64, + allotted_space: u64, } impl TxBin { @@ -140,9 +140,9 @@ impl TxBin { /// of storable txs defined by a ratio over Tendermint's max block size. #[inline] fn init_from(tendermint_max_block_space: u64, frac: Ratio) -> Self { - let alloted_space = (frac * tendermint_max_block_space).to_integer(); + let allotted_space = (frac * tendermint_max_block_space).to_integer(); Self { - alloted_space, + allotted_space, current_space: 0, } } @@ -151,7 +151,7 @@ impl TxBin { #[inline] fn try_dump(&mut self, tx: &[u8]) -> bool { let occupied = self.current_space + tx.len() as u64; - if occupied <= self.alloted_space { + if occupied <= self.allotted_space { self.current_space = occupied; true } else { @@ -165,13 +165,13 @@ mod thres { use num_rational::Ratio; - /// The threshold over Tendermint's alloted space for protocol txs. + /// The threshold over Tendermint's allotted space for protocol txs. pub const PROTOCOL_TX: Ratio = Ratio::new_raw(1, 3); - /// The threshold over Tendermint's alloted space for DKG encrypted txs. + /// The threshold over Tendermint's allotted space for DKG encrypted txs. pub const ENCRYPTED_TX: Ratio = Ratio::new_raw(1, 3); - /// The threshold over Tendermint's alloted space for DKG decrypted txs. + /// The threshold over Tendermint's allotted space for DKG decrypted txs. pub const DECRYPTED_TX: Ratio = Ratio::new_raw(1, 3); } @@ -208,14 +208,14 @@ mod tests { proptest! { /// Check if we reject a tx when its respective bin - /// capacity has been reached on a [`TxAllotedSpace`]. + /// capacity has been reached on a [`TxAllottedSpace`]. #[test] fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { proptest_reject_tx_on_bin_cap_reached(max) } /// Check if the sum of all individual bin allotments for a - /// [`TxAllotedSpace`] corresponds to the total space ceded + /// [`TxAllottedSpace`] corresponds to the total space ceded /// by Tendermint. #[test] fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { @@ -232,10 +232,10 @@ mod tests { /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. fn proptest_reject_tx_on_bin_cap_reached(tendermint_max_block_space: u64) { - let mut bins = TxAllotedSpace::init(tendermint_max_block_space); + let mut bins = TxAllottedSpace::init(tendermint_max_block_space); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space = bins.decrypted_txs.alloted_space; + bins.decrypted_txs.current_space = bins.decrypted_txs.allotted_space; // make sure we can't dump any new decrypted txs in the bin assert!(!bins.try_alloc_decrypted_tx(b"arbitrary tx bytes")); @@ -245,7 +245,7 @@ mod tests { fn proptest_bin_capacity_eq_provided_space( tendermint_max_block_space: u64, ) { - let bins = TxAllotedSpace::init(tendermint_max_block_space); + let bins = TxAllottedSpace::init(tendermint_max_block_space); assert_eq!(0, bins.init_leftover_space()); } @@ -258,7 +258,7 @@ mod tests { decrypted_txs, } = args; let bins = - RefCell::new(TxAllotedSpace::init(tendermint_max_block_space)); + RefCell::new(TxAllottedSpace::init(tendermint_max_block_space)); // produce new txs until we overflow the bins // @@ -267,17 +267,17 @@ mod tests { let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; let new_size = bin.current_space + tx.len() as u64; - new_size < bin.alloted_space + new_size < bin.allotted_space }); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; let new_size = bin.current_space + tx.len() as u64; - new_size < bin.alloted_space + new_size < bin.allotted_space }); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; let new_size = bin.current_space + tx.len() as u64; - new_size < bin.alloted_space + new_size < bin.allotted_space }); // make sure we can keep dumping txs, @@ -318,7 +318,7 @@ mod tests { } } - /// Return random bin sizes for a [`TxAllotedSpace`]. + /// Return random bin sizes for a [`TxAllottedSpace`]. fn arb_max_bin_sizes() -> impl Strategy { const MAX_BLOCK_SIZE_BYTES: u64 = 1000; From 6998ce96934ff27cf1cf881f1ee5e79dc19f08bb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:10:48 +0000 Subject: [PATCH 1550/1995] Rename init_leftover_space() to uninitialized_space() --- .../lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ffaf164696..ebdfbab209 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -65,16 +65,16 @@ impl TxAllottedSpace { encrypted_txs: TxBin::init_from(max, thres::ENCRYPTED_TX), decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), }; - // concede leftover space to protocol txs - bins.protocol_txs.allotted_space += bins.init_leftover_space(); + // concede all uninitialized space to protocol txs + bins.protocol_txs.allotted_space += bins.uninitialized_space(); bins } - /// Return leftover space in bins, resulting from ratio conversions. + /// Return uninitialized space in tx bins, resulting from ratio conversions. /// /// This method should not be used outside of [`TxAllottedSpace`] /// instance construction or unit testing. - fn init_leftover_space(&self) -> u64 { + fn uninitialized_space(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space + self.encrypted_txs.allotted_space + self.decrypted_txs.allotted_space; @@ -246,7 +246,7 @@ mod tests { tendermint_max_block_space: u64, ) { let bins = TxAllottedSpace::init(tendermint_max_block_space); - assert_eq!(0, bins.init_leftover_space()); + assert_eq!(0, bins.uninitialized_space()); } /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. From 65db578980b74c41074a8fe09219acdee058c7da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:22:15 +0000 Subject: [PATCH 1551/1995] Add bytes prefixes and suffixes to tx bins --- .../ledger/shell/prepare_proposal/tx_bins.rs | 123 ++++++++++-------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ebdfbab209..17a5eae138 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -36,7 +36,7 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; pub struct TxAllottedSpace { /// The total space Tendermint has allotted to the /// application for the current block height. - provided_by_tendermint: u64, + bytes_provided_by_tendermint: u64, /// The current space utilized by protocol transactions. protocol_txs: TxBin, /// The current space utilized by DKG encrypted transactions. @@ -48,8 +48,8 @@ pub struct TxAllottedSpace { impl From<&RequestPrepareProposal> for TxAllottedSpace { #[inline] fn from(req: &RequestPrepareProposal) -> Self { - let tendermint_max_block_space = req.max_tx_bytes as u64; - Self::init(tendermint_max_block_space) + let tendermint_max_block_space_in_bytes = req.max_tx_bytes as u64; + Self::init(tendermint_max_block_space_in_bytes) } } @@ -57,16 +57,16 @@ impl TxAllottedSpace { /// Construct a new [`TxAllottedSpace`], with an upper bound /// on the max number of txs in a block defined by Tendermint. #[inline] - pub fn init(tendermint_max_block_space: u64) -> Self { - let max = tendermint_max_block_space; + pub fn init(tendermint_max_block_space_in_bytes: u64) -> Self { + let max = tendermint_max_block_space_in_bytes; let mut bins = Self { - provided_by_tendermint: max, + bytes_provided_by_tendermint: max, protocol_txs: TxBin::init_from(max, thres::PROTOCOL_TX), encrypted_txs: TxBin::init_from(max, thres::ENCRYPTED_TX), decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), }; // concede all uninitialized space to protocol txs - bins.protocol_txs.allotted_space += bins.uninitialized_space(); + bins.protocol_txs.allotted_space_in_bytes += bins.uninitialized_space(); bins } @@ -75,10 +75,10 @@ impl TxAllottedSpace { /// This method should not be used outside of [`TxAllottedSpace`] /// instance construction or unit testing. fn uninitialized_space(&self) -> u64 { - let total_bin_space = self.protocol_txs.allotted_space - + self.encrypted_txs.allotted_space - + self.decrypted_txs.allotted_space; - self.provided_by_tendermint - total_bin_space + let total_bin_space = self.protocol_txs.allotted_space_in_bytes + + self.encrypted_txs.allotted_space_in_bytes + + self.decrypted_txs.allotted_space_in_bytes; + self.bytes_provided_by_tendermint - total_bin_space } /// Try to allocate space for a new protocol transaction. @@ -104,24 +104,24 @@ impl TxAllottedSpace { /// The total space, in bytes, occupied by each transaction. #[inline] - pub fn occupied_space(&self) -> u64 { - self.protocol_txs.current_space - + self.encrypted_txs.current_space - + self.decrypted_txs.current_space + pub fn occupied_space_in_bytes(&self) -> u64 { + self.protocol_txs.current_space_in_bytes + + self.encrypted_txs.current_space_in_bytes + + self.decrypted_txs.current_space_in_bytes } /// Return the amount, in bytes, of free space in this /// [`TxAllottedSpace`]. #[inline] - pub fn free_space(&self) -> u64 { - self.provided_by_tendermint - self.occupied_space() + pub fn free_space_in_bytes(&self) -> u64 { + self.bytes_provided_by_tendermint - self.occupied_space_in_bytes() } /// Checks if this [`TxAllottedSpace`] has any free space remaining. #[allow(dead_code)] #[inline] pub fn has_free_space(&self) -> bool { - self.free_space() > 0 + self.free_space_in_bytes() > 0 } } @@ -130,29 +130,33 @@ impl TxAllottedSpace { #[derive(Copy, Clone, Default)] struct TxBin { /// The current space utilized by the batch of transactions. - current_space: u64, + current_space_in_bytes: u64, /// The maximum space the batch of transactions may occupy. - allotted_space: u64, + allotted_space_in_bytes: u64, } impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number /// of storable txs defined by a ratio over Tendermint's max block size. #[inline] - fn init_from(tendermint_max_block_space: u64, frac: Ratio) -> Self { - let allotted_space = (frac * tendermint_max_block_space).to_integer(); + fn init_from( + tendermint_max_block_space_in_bytes: u64, + frac: Ratio, + ) -> Self { + let allotted_space_in_bytes = + (frac * tendermint_max_block_space_in_bytes).to_integer(); Self { - allotted_space, - current_space: 0, + allotted_space_in_bytes, + current_space_in_bytes: 0, } } /// Try to dump a new transaction into this [`TxBin`]. #[inline] fn try_dump(&mut self, tx: &[u8]) -> bool { - let occupied = self.current_space + tx.len() as u64; - if occupied <= self.allotted_space { - self.current_space = occupied; + let occupied = self.current_space_in_bytes + tx.len() as u64; + if occupied <= self.allotted_space_in_bytes { + self.current_space_in_bytes = occupied; true } else { false @@ -187,7 +191,7 @@ mod tests { /// Proptest generated txs. #[derive(Debug)] struct PropTx { - tendermint_max_block_space: u64, + tendermint_max_block_space_in_bytes: u64, protocol_txs: Vec, encrypted_txs: Vec, decrypted_txs: Vec, @@ -231,11 +235,15 @@ mod tests { } /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. - fn proptest_reject_tx_on_bin_cap_reached(tendermint_max_block_space: u64) { - let mut bins = TxAllottedSpace::init(tendermint_max_block_space); + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + TxAllottedSpace::init(tendermint_max_block_space_in_bytes); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space = bins.decrypted_txs.allotted_space; + bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin assert!(!bins.try_alloc_decrypted_tx(b"arbitrary tx bytes")); @@ -243,22 +251,23 @@ mod tests { /// Implementation of [`test_bin_capacity_eq_provided_space`]. fn proptest_bin_capacity_eq_provided_space( - tendermint_max_block_space: u64, + tendermint_max_block_space_in_bytes: u64, ) { - let bins = TxAllottedSpace::init(tendermint_max_block_space); + let bins = TxAllottedSpace::init(tendermint_max_block_space_in_bytes); assert_eq!(0, bins.uninitialized_space()); } /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. fn proptest_tx_dump_doesnt_overflow_bin(args: PropTx) { let PropTx { - tendermint_max_block_space, + tendermint_max_block_space_in_bytes, protocol_txs, encrypted_txs, decrypted_txs, } = args; - let bins = - RefCell::new(TxAllottedSpace::init(tendermint_max_block_space)); + let bins = RefCell::new(TxAllottedSpace::init( + tendermint_max_block_space_in_bytes, + )); // produce new txs until we overflow the bins // @@ -266,18 +275,18 @@ mod tests { // txs whose total added size would be bounded let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space + tx.len() as u64; - new_size < bin.allotted_space + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes }); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space + tx.len() as u64; - new_size < bin.allotted_space + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes }); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space + tx.len() as u64; - new_size < bin.allotted_space + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes }); // make sure we can keep dumping txs, @@ -298,19 +307,19 @@ mod tests { fn arb_transactions() // create base strategies ( - (tendermint_max_block_space, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, decrypted_tx_max_bin_size) in arb_max_bin_sizes(), ) // compose strategies ( - tendermint_max_block_space in Just(tendermint_max_block_space), + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), protocol_txs in arb_tx_list(protocol_tx_max_bin_size), encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), ) -> PropTx { PropTx { - tendermint_max_block_space, + tendermint_max_block_space_in_bytes, protocol_txs, encrypted_txs, decrypted_txs, @@ -322,17 +331,19 @@ mod tests { fn arb_max_bin_sizes() -> impl Strategy { const MAX_BLOCK_SIZE_BYTES: u64 = 1000; - (1..=MAX_BLOCK_SIZE_BYTES).prop_map(|tendermint_max_block_space| { - ( - tendermint_max_block_space, - (thres::PROTOCOL_TX * tendermint_max_block_space).to_integer() - as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space).to_integer() - as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space).to_integer() - as usize, - ) - }) + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + ) + }, + ) } /// Return a list of txs. From aae550a58a01a64c9c8363e011724f7d7603b112 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 14:28:07 +0000 Subject: [PATCH 1552/1995] Add a test for votes::storage::write --- .../protocol/transactions/votes/storage.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/storage.rs b/shared/src/ledger/protocol/transactions/votes/storage.rs index 22e738e626..79359c6b26 100644 --- a/shared/src/ledger/protocol/transactions/votes/storage.rs +++ b/shared/src/ledger/protocol/transactions/votes/storage.rs @@ -22,3 +22,47 @@ where storage.write(&keys.voting_power(), &tally.voting_power.try_to_vec()?)?; Ok(()) } + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::*; + use crate::ledger::storage::testing::TestStorage; + use crate::types::address; + use crate::types::ethereum_events::EthereumEvent; + use crate::types::voting_power::FractionalVotingPower; + + #[test] + fn test_write_tally() { + let mut storage = TestStorage::default(); + let event = EthereumEvent::TransfersToNamada { + nonce: 0.into(), + transfers: vec![], + }; + let keys = vote_tallies::Keys::from(&event); + let tally = Tally { + voting_power: FractionalVotingPower::new(1, 3).unwrap(), + seen_by: BTreeMap::from([( + address::testing::established_address_1(), + 10.into(), + )]), + seen: false, + }; + + let result = write(&mut storage, &keys, &event, &tally); + + assert!(result.is_ok()); + let (body, _) = storage.read(&keys.body()).unwrap(); + assert_eq!(body, Some(event.try_to_vec().unwrap())); + let (seen, _) = storage.read(&keys.seen()).unwrap(); + assert_eq!(seen, Some(tally.seen.try_to_vec().unwrap())); + let (seen_by, _) = storage.read(&keys.seen_by()).unwrap(); + assert_eq!(seen_by, Some(tally.seen_by.try_to_vec().unwrap())); + let (voting_power, _) = storage.read(&keys.voting_power()).unwrap(); + assert_eq!( + voting_power, + Some(tally.voting_power.try_to_vec().unwrap()) + ); + } +} From cadd1cbbcf38a826c7c265918baa1d36a0de0c3a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 14:40:31 +0000 Subject: [PATCH 1553/1995] Add votes::storage::read --- .../protocol/transactions/votes/storage.rs | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/votes/storage.rs b/shared/src/ledger/protocol/transactions/votes/storage.rs index 79359c6b26..33fca417f8 100644 --- a/shared/src/ledger/protocol/transactions/votes/storage.rs +++ b/shared/src/ledger/protocol/transactions/votes/storage.rs @@ -1,9 +1,10 @@ use borsh::BorshSerialize; use eyre::Result; -use super::Tally; +use super::{Tally, Votes}; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use crate::types::voting_power::FractionalVotingPower; pub fn write( storage: &mut Storage, @@ -23,6 +24,27 @@ where Ok(()) } +#[allow(dead_code)] +pub fn read( + storage: &mut Storage, + keys: &vote_tallies::Keys, +) -> Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let seen: bool = super::read::value(storage, &keys.seen())?; + let seen_by: Votes = super::read::value(storage, &keys.seen_by())?; + let voting_power: FractionalVotingPower = + super::read::value(storage, &keys.voting_power())?; + + Ok(Tally { + voting_power, + seen_by, + seen, + }) +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; @@ -65,4 +87,42 @@ mod tests { Some(tally.voting_power.try_to_vec().unwrap()) ); } + + #[test] + fn test_read_tally() { + let mut storage = TestStorage::default(); + let event = EthereumEvent::TransfersToNamada { + nonce: 0.into(), + transfers: vec![], + }; + let keys = vote_tallies::Keys::from(&event); + let tally = Tally { + voting_power: FractionalVotingPower::new(1, 3).unwrap(), + seen_by: BTreeMap::from([( + address::testing::established_address_1(), + 10.into(), + )]), + seen: false, + }; + storage + .write(&keys.body(), &event.try_to_vec().unwrap()) + .unwrap(); + storage + .write(&keys.seen(), &tally.seen.try_to_vec().unwrap()) + .unwrap(); + storage + .write(&keys.seen_by(), &tally.seen_by.try_to_vec().unwrap()) + .unwrap(); + storage + .write( + &keys.voting_power(), + &tally.voting_power.try_to_vec().unwrap(), + ) + .unwrap(); + + let result = read(&mut storage, &keys); + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), tally); + } } From b0f00883c42d265e14ccccabdaee55a23b2aa406 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 14 Nov 2022 14:49:57 +0000 Subject: [PATCH 1554/1995] [ci] wasm checksums update --- wasm/checksums.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 29297d77a1..6df1d206a1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,15 @@ { - "tx_bond.wasm": "tx_bond.a2d03adca103b0acc5997b926990ff4de4650268203e9d3350e004eab074634a.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.2b467845c55820345ce48e2da4f7c2606566555eac12ee64f8baeb5b63dfae83.wasm", - "tx_init_account.wasm": "tx_init_account.86e02762f60428966c7ab4f780fc4dbcc794ae0aa791930f27e246c2c289e045.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f654f0e4610e03c9ffcc36225f67822200aa89f3633f16a9eb0b47813f8ec15a.wasm", - "tx_init_validator.wasm": "tx_init_validator.b4e6713e69aa341339a836de6901c7c29a532849577514255ecb3092882a12c3.wasm", - "tx_transfer.wasm": "tx_transfer.e3c687a17173af07b4be0fc0af06ebe6dbba1d5195b8e3015c955eeaaa53e59e.wasm", + "tx_bond.wasm": "tx_bond.4e64b7884c8df422fc00db221fbe0eb48bfb494c2baf385c5552e041cacdea64.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.233f790468cc70489111201f6f066280d7bc579b2884d94448e357bf243d0a14.wasm", + "tx_ibc.wasm": "tx_ibc.e4dccee1e462e84f2b2a5c2cdbc06dc1570679363d7480e37c86e505583ca6fa.wasm", + "tx_init_account.wasm": "tx_init_account.db84fb031ffac56b59dc0383f0ad3e528e5d7635292de02e7cda4c4545b2cbe5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3227005afc52c2f002fee13de88ea6b4e1b26ab5e53d66453bda45230f670565.wasm", + "tx_init_validator.wasm": "tx_init_validator.419ce3750a8a70087cb5b4010418570f73534e68ebb918a16d93b346c5189de5.wasm", + "tx_transfer.wasm": "tx_transfer.a0fe87d8e4b48ed679d8e16a8800085cb3d7ac9556fb5fe4b37cff4cd66fee81.wasm", "tx_unbond.wasm": "tx_unbond.4480c046edf32bf97d2d40d45b2bd05547049cae22df48f3fc433f151b495e2b.wasm", "tx_update_vp.wasm": "tx_update_vp.b057de2c9f17a634aaf011f6bf6bb0bac198ae522c5a24a44dec71d9b649c7e7.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.4625c309121620a10b9b87b4f4622b8a899f758c08d5ae0341a6f80ce932e089.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ee49918852f375271a444c111b73ddcf20e237af852579b4fcc4caab17a2715.wasm", + "tx_withdraw.wasm": "tx_withdraw.98503dc2b6d931e9303567567de4d395ae7689f0ef852810e75a904953684d08.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.e417000da4f7640a5fb5f124755ed02b8654aa67db600bea6e93e1f04e6d71a3.wasm", "vp_token.wasm": "vp_token.37749e16d9fcd17dc2cf3351b0a140b5821668f80d6cc805caeb3e8e9af06cd9.wasm", "vp_user.wasm": "vp_user.8c117faa94e44833fb7a16d13067f7626ecfca6a70fec1b7822c7d9a6c3e909e.wasm" From 6f24fae45b9aca640103eb4099dd677f4f1f624c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 14 Nov 2022 15:39:31 +0000 Subject: [PATCH 1555/1995] [ci] wasm checksums update --- wasm/checksums.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 29297d77a1..6df1d206a1 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,15 @@ { - "tx_bond.wasm": "tx_bond.a2d03adca103b0acc5997b926990ff4de4650268203e9d3350e004eab074634a.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", - "tx_ibc.wasm": "tx_ibc.2b467845c55820345ce48e2da4f7c2606566555eac12ee64f8baeb5b63dfae83.wasm", - "tx_init_account.wasm": "tx_init_account.86e02762f60428966c7ab4f780fc4dbcc794ae0aa791930f27e246c2c289e045.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f654f0e4610e03c9ffcc36225f67822200aa89f3633f16a9eb0b47813f8ec15a.wasm", - "tx_init_validator.wasm": "tx_init_validator.b4e6713e69aa341339a836de6901c7c29a532849577514255ecb3092882a12c3.wasm", - "tx_transfer.wasm": "tx_transfer.e3c687a17173af07b4be0fc0af06ebe6dbba1d5195b8e3015c955eeaaa53e59e.wasm", + "tx_bond.wasm": "tx_bond.4e64b7884c8df422fc00db221fbe0eb48bfb494c2baf385c5552e041cacdea64.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.233f790468cc70489111201f6f066280d7bc579b2884d94448e357bf243d0a14.wasm", + "tx_ibc.wasm": "tx_ibc.e4dccee1e462e84f2b2a5c2cdbc06dc1570679363d7480e37c86e505583ca6fa.wasm", + "tx_init_account.wasm": "tx_init_account.db84fb031ffac56b59dc0383f0ad3e528e5d7635292de02e7cda4c4545b2cbe5.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3227005afc52c2f002fee13de88ea6b4e1b26ab5e53d66453bda45230f670565.wasm", + "tx_init_validator.wasm": "tx_init_validator.419ce3750a8a70087cb5b4010418570f73534e68ebb918a16d93b346c5189de5.wasm", + "tx_transfer.wasm": "tx_transfer.a0fe87d8e4b48ed679d8e16a8800085cb3d7ac9556fb5fe4b37cff4cd66fee81.wasm", "tx_unbond.wasm": "tx_unbond.4480c046edf32bf97d2d40d45b2bd05547049cae22df48f3fc433f151b495e2b.wasm", "tx_update_vp.wasm": "tx_update_vp.b057de2c9f17a634aaf011f6bf6bb0bac198ae522c5a24a44dec71d9b649c7e7.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.4625c309121620a10b9b87b4f4622b8a899f758c08d5ae0341a6f80ce932e089.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ee49918852f375271a444c111b73ddcf20e237af852579b4fcc4caab17a2715.wasm", + "tx_withdraw.wasm": "tx_withdraw.98503dc2b6d931e9303567567de4d395ae7689f0ef852810e75a904953684d08.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.e417000da4f7640a5fb5f124755ed02b8654aa67db600bea6e93e1f04e6d71a3.wasm", "vp_token.wasm": "vp_token.37749e16d9fcd17dc2cf3351b0a140b5821668f80d6cc805caeb3e8e9af06cd9.wasm", "vp_user.wasm": "vp_user.8c117faa94e44833fb7a16d13067f7626ecfca6a70fec1b7822c7d9a6c3e909e.wasm" From 82c004c14770be18112bab07d5fc1f24516b0f01 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 14:09:35 +0000 Subject: [PATCH 1556/1995] Dispatch tx kind into its respective bin --- .../ledger/shell/prepare_proposal/tx_bins.rs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 17a5eae138..ac876a5c34 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -20,6 +20,8 @@ // the total gas of all chosen txs cannot exceed the configured max // gas per block, otherwise a proposal will be rejected! +use namada::proto::Tx; +use namada::types::transaction::{process_tx, TxError, TxType}; use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -81,24 +83,39 @@ impl TxAllottedSpace { self.bytes_provided_by_tendermint - total_bin_space } - /// Try to allocate space for a new protocol transaction. + /// Try to allocate space for a new transaction. #[allow(dead_code)] + pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> Result { + let tx = Tx::try_from(tx_bytes) + .map_err(|err| TxError::Deserialization(format!("{err}"))) + .and_then(process_tx)?; + + Ok(match tx { + TxType::Raw(_) => { + // nothing to do for raw txs + true + } + TxType::Protocol(_) => self.try_alloc_protocol_tx(tx_bytes), + TxType::Wrapper(_) => self.try_alloc_encrypted_tx(tx_bytes), + TxType::Decrypted(_) => self.try_alloc_decrypted_tx(tx_bytes), + }) + } + + /// Try to allocate space for a new protocol transaction. #[inline] - pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { + fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { self.protocol_txs.try_dump(tx) } /// Try to allocate space for a new DKG encrypted transaction. - #[allow(dead_code)] #[inline] - pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { + fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { self.encrypted_txs.try_dump(tx) } /// Try to allocate space for a new DKG decrypted transaction. - #[allow(dead_code)] #[inline] - pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { + fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { self.decrypted_txs.try_dump(tx) } From 04f3379762bccf7dde8de440155d26f6c68dc718 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 14:31:12 +0000 Subject: [PATCH 1557/1995] All txs in our mempool should be deserializable --- .../node/ledger/shell/prepare_proposal/tx_bins.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ac876a5c34..99b05273de 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -85,12 +85,16 @@ impl TxAllottedSpace { /// Try to allocate space for a new transaction. #[allow(dead_code)] - pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> Result { + pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> bool { let tx = Tx::try_from(tx_bytes) .map_err(|err| TxError::Deserialization(format!("{err}"))) - .and_then(process_tx)?; + .and_then(process_tx) + .expect( + "Mempool validation passed for the given tx, so it should be \ + a valid tx", + ); - Ok(match tx { + match tx { TxType::Raw(_) => { // nothing to do for raw txs true @@ -98,7 +102,7 @@ impl TxAllottedSpace { TxType::Protocol(_) => self.try_alloc_protocol_tx(tx_bytes), TxType::Wrapper(_) => self.try_alloc_encrypted_tx(tx_bytes), TxType::Decrypted(_) => self.try_alloc_decrypted_tx(tx_bytes), - }) + } } /// Try to allocate space for a new protocol transaction. From 15909984518b7a063258d8d0c0c4b939e7591bba Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:11:12 +0000 Subject: [PATCH 1558/1995] Start counting tx space usage --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0e7ac9828f..6722ae353c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -13,6 +13,7 @@ use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; use super::super::*; +use self::tx_bins::TxAllotedSpace; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ @@ -51,6 +52,9 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // TODO: add some info logging? + // start counting allotted space for txs + let mut bins = TxAllotedSpace::from(&req); + // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] let txs = self.build_vote_extensions_txs(req.local_last_commit); From 43099a74a5dac76e71e657970c75534117b3213d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:11:29 +0000 Subject: [PATCH 1559/1995] Revert "All txs in our mempool should be deserializable" This reverts commit 9b11bde05b23a6f971c56792ce9eab54d13bffe5. --- .../node/ledger/shell/prepare_proposal/tx_bins.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 99b05273de..ac876a5c34 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -85,16 +85,12 @@ impl TxAllottedSpace { /// Try to allocate space for a new transaction. #[allow(dead_code)] - pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> bool { + pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> Result { let tx = Tx::try_from(tx_bytes) .map_err(|err| TxError::Deserialization(format!("{err}"))) - .and_then(process_tx) - .expect( - "Mempool validation passed for the given tx, so it should be \ - a valid tx", - ); + .and_then(process_tx)?; - match tx { + Ok(match tx { TxType::Raw(_) => { // nothing to do for raw txs true @@ -102,7 +98,7 @@ impl TxAllottedSpace { TxType::Protocol(_) => self.try_alloc_protocol_tx(tx_bytes), TxType::Wrapper(_) => self.try_alloc_encrypted_tx(tx_bytes), TxType::Decrypted(_) => self.try_alloc_decrypted_tx(tx_bytes), - } + }) } /// Try to allocate space for a new protocol transaction. From 7420a8ad6e32a250460981b774e1a762923199ee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:11:44 +0000 Subject: [PATCH 1560/1995] Revert "Dispatch tx kind into its respective bin" This reverts commit d06c8458e6f85bd110cdd1229aace365ca1c09d8. --- .../ledger/shell/prepare_proposal/tx_bins.rs | 29 ++++--------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ac876a5c34..17a5eae138 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -20,8 +20,6 @@ // the total gas of all chosen txs cannot exceed the configured max // gas per block, otherwise a proposal will be rejected! -use namada::proto::Tx; -use namada::types::transaction::{process_tx, TxError, TxType}; use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -83,39 +81,24 @@ impl TxAllottedSpace { self.bytes_provided_by_tendermint - total_bin_space } - /// Try to allocate space for a new transaction. - #[allow(dead_code)] - pub fn try_alloc_tx(&mut self, tx_bytes: &[u8]) -> Result { - let tx = Tx::try_from(tx_bytes) - .map_err(|err| TxError::Deserialization(format!("{err}"))) - .and_then(process_tx)?; - - Ok(match tx { - TxType::Raw(_) => { - // nothing to do for raw txs - true - } - TxType::Protocol(_) => self.try_alloc_protocol_tx(tx_bytes), - TxType::Wrapper(_) => self.try_alloc_encrypted_tx(tx_bytes), - TxType::Decrypted(_) => self.try_alloc_decrypted_tx(tx_bytes), - }) - } - /// Try to allocate space for a new protocol transaction. + #[allow(dead_code)] #[inline] - fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { self.protocol_txs.try_dump(tx) } /// Try to allocate space for a new DKG encrypted transaction. + #[allow(dead_code)] #[inline] - fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { self.encrypted_txs.try_dump(tx) } /// Try to allocate space for a new DKG decrypted transaction. + #[allow(dead_code)] #[inline] - fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { self.decrypted_txs.try_dump(tx) } From 65bf0c1c0e18b41d99994c0f90276de2f2596af1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:24:30 +0000 Subject: [PATCH 1561/1995] Run make fmt --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6722ae353c..7f12acaa83 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,8 +12,8 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use super::super::*; use self::tx_bins::TxAllotedSpace; +use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ From 4f299f0111b28b807e368fbdf903dc4ccf7b6244 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:27:49 +0000 Subject: [PATCH 1562/1995] Clippy --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7f12acaa83..545edd3c70 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,7 +12,9 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use self::tx_bins::TxAllotedSpace; +// ```ignore +// use self::tx_bins::TxAllotedSpace; +// ``` use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -53,7 +55,9 @@ where // TODO: add some info logging? // start counting allotted space for txs - let mut bins = TxAllotedSpace::from(&req); + // ```ignore + // let mut bins = TxAllotedSpace::from(&req); + // ``` // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] From be611cce96d7858013befca131100993416bd2c6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:28:17 +0000 Subject: [PATCH 1563/1995] Refactor return type of tx allocs --- .../ledger/shell/prepare_proposal/tx_bins.rs | 57 ++++++++++++++----- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 17a5eae138..d316a344d5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -24,6 +24,18 @@ use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +/// All status responses from trying to allocate block space for a tx. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AllocStatus { + /// The transaction is able to be included in the current block. + Accepted, + /// The transaction can only be included in an upcoming block. + Rejected, + /// The transaction would overflow the allotted bin space, + /// therefore it needs to be handled separately. + OverflowsBin, +} + /// Allotted space for a batch of transactions in some proposed block, /// measured in bytes. /// @@ -84,21 +96,21 @@ impl TxAllottedSpace { /// Try to allocate space for a new protocol transaction. #[allow(dead_code)] #[inline] - pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> AllocStatus { self.protocol_txs.try_dump(tx) } /// Try to allocate space for a new DKG encrypted transaction. #[allow(dead_code)] #[inline] - pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { self.encrypted_txs.try_dump(tx) } /// Try to allocate space for a new DKG decrypted transaction. #[allow(dead_code)] #[inline] - pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> bool { + pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { self.decrypted_txs.try_dump(tx) } @@ -152,14 +164,21 @@ impl TxBin { } /// Try to dump a new transaction into this [`TxBin`]. + /// + /// Signal the caller if the tx is larger than its max + /// allotted bin space. #[inline] - fn try_dump(&mut self, tx: &[u8]) -> bool { - let occupied = self.current_space_in_bytes + tx.len() as u64; - if occupied <= self.allotted_space_in_bytes { - self.current_space_in_bytes = occupied; - true + fn try_dump(&mut self, tx: &[u8]) -> AllocStatus { + let tx_len = tx.len() as u64; + if tx_len > self.alloted_space { + return AllocStatus::OverflowsBin; + } + let occupied = self.current_space + tx_len; + if occupied <= self.alloted_space { + self.current_space = occupied; + AllocStatus::Accepted } else { - false + AllocStatus::Rejected } } } @@ -246,7 +265,10 @@ mod tests { bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin - assert!(!bins.try_alloc_decrypted_tx(b"arbitrary tx bytes")); + assert_eq!( + bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + AllocStatus::Rejected + ); } /// Implementation of [`test_bin_capacity_eq_provided_space`]. @@ -292,13 +314,22 @@ mod tests { // make sure we can keep dumping txs, // without overflowing the bins for tx in protocol_txs { - assert!(bins.borrow_mut().try_alloc_protocol_tx(&tx)); + assert_eq!( + bins.borrow_mut().try_alloc_protocol_tx(&tx), + AllocStatus::Accepted + ); } for tx in encrypted_txs { - assert!(bins.borrow_mut().try_alloc_encrypted_tx(&tx)); + assert_eq!( + bins.borrow_mut().try_alloc_encrypted_tx(&tx), + AllocStatus::Accepted + ); } for tx in decrypted_txs { - assert!(bins.borrow_mut().try_alloc_decrypted_tx(&tx)); + assert_eq!( + bins.borrow_mut().try_alloc_decrypted_tx(&tx), + AllocStatus::Accepted + ); } } From 61bde7e43db0d62480dd87dea79915016390e20f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:56:25 +0000 Subject: [PATCH 1564/1995] WIP: Add tx bins to PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 545edd3c70..990b7a74a3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,9 +12,7 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -// ```ignore -// use self::tx_bins::TxAllotedSpace; -// ``` +use self::tx_bins::TxAllotedSpace; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -55,15 +53,14 @@ where // TODO: add some info logging? // start counting allotted space for txs - // ```ignore - // let mut bins = TxAllotedSpace::from(&req); - // ``` + let mut bins = TxAllotedSpace::from(&req); // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] - let txs = self.build_vote_extensions_txs(req.local_last_commit); + let txs = self + .build_vote_extensions_txs(&mut bins, req.local_last_commit); #[cfg(not(feature = "abcipp"))] - let mut txs = self.build_vote_extensions_txs(&req.txs); + let mut txs = self.build_vote_extensions_txs(&mut bins, &req.txs); #[cfg(feature = "abcipp")] let mut txs: Vec = txs.into_iter().map(record::add).collect(); @@ -108,6 +105,7 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, + _bins: &mut TxAllotedSpace, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -170,6 +168,22 @@ where // `abciplus` .chain(protocol_txs.into_iter()) .collect() + + // ```ignore + // for tx in txs.iter().map(Vec::as_slice) { + // match bins.try_alloc_protocol_tx(tx) { + // AllocStatus::Accepted => (), + // AllocStatus::Rejected => { + // // TODO: handle bin space full for protocol txs; + // // if we include a vote extension digest, we need + // // to include its corresponding protocol tx votes! + // // otherwise, we will get the same votes in future + // // block proposals + // tracing::debug!("No more space left for protocol transactions"); + // }. + // } + // } + // ``` } /// Builds a batch of mempool transactions From d9d06519fc6e8edf8845b15d788f92e17d60cc6a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 15:56:41 +0000 Subject: [PATCH 1565/1995] Add bactch of txs to a bin --- .../ledger/shell/prepare_proposal/tx_bins.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index d316a344d5..2b8e7b5e09 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -181,6 +181,30 @@ impl TxBin { AllocStatus::Rejected } } + + /// Try to dump a new batch of transactions into this [`TxBin`]. + /// + /// If an allocation fails, rollback the state of the [`TxBin`], + /// and return the respective status of the failure. + #[inline] + #[allow(dead_code)] + fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + let mut space_diff = 0; + for tx in txs { + match self.try_dump(tx) { + AllocStatus::Accepted => space_diff += tx.len() as u64, + status + @ (AllocStatus::Rejected | AllocStatus::OverflowsBin) => { + self.current_space -= space_diff; + return status; + } + } + } + AllocStatus::Accepted + } } mod thres { From 87296c89fcae90a693565ddf3b6ff18723c05ce6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 11 Nov 2022 16:12:24 +0000 Subject: [PATCH 1566/1995] Add boilerplate allocation code for batches of txs of different kinds --- .../ledger/shell/prepare_proposal/tx_bins.rs | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 2b8e7b5e09..8b8ff172bb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -93,6 +93,32 @@ impl TxAllottedSpace { self.bytes_provided_by_tendermint - total_bin_space } + /// The total space, in bytes, occupied by each transaction. + #[inline] + pub fn occupied_space(&self) -> u64 { + self.protocol_txs.current_space + + self.encrypted_txs.current_space + + self.decrypted_txs.current_space + } + + /// Return the amount, in bytes, of free space in this + /// [`TxAllotedSpace`]. + #[inline] + pub fn free_space(&self) -> u64 { + self.provided_by_tendermint - self.occupied_space() + } + + /// Checks if this [`TxAllotedSpace`] has any free space remaining. + #[allow(dead_code)] + #[inline] + pub fn has_free_space(&self) -> bool { + self.free_space() > 0 + } +} + +// all allocation boilerplate code shall +// be shunned to this impl block -- shame! +impl TxAllotedSpace { /// Try to allocate space for a new protocol transaction. #[allow(dead_code)] #[inline] @@ -100,6 +126,18 @@ impl TxAllottedSpace { self.protocol_txs.try_dump(tx) } + /// Try to allocate space for a new batch of protocol transactions. + #[allow(dead_code)] + #[inline] + pub fn try_alloc_protocol_tx_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.protocol_txs.try_dump_all(txs) + } + + // --------------------------------------------------- // + /// Try to allocate space for a new DKG encrypted transaction. #[allow(dead_code)] #[inline] @@ -107,11 +145,17 @@ impl TxAllottedSpace { self.encrypted_txs.try_dump(tx) } - /// Try to allocate space for a new DKG decrypted transaction. + /// Try to allocate space for a new batch of DKG encrypted transactions. #[allow(dead_code)] #[inline] - pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { - self.decrypted_txs.try_dump(tx) + pub fn try_alloc_encrypted_tx_batch<'tx, T>( + &mut self, + txs: T, + ) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.encrypted_txs.try_dump_all(txs) } /// The total space, in bytes, occupied by each transaction. @@ -187,7 +231,6 @@ impl TxBin { /// If an allocation fails, rollback the state of the [`TxBin`], /// and return the respective status of the failure. #[inline] - #[allow(dead_code)] fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx, From fc3998a076873b81fbac47aeeb0bfcd326b15acc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 09:51:33 +0000 Subject: [PATCH 1567/1995] WIP: Use tx bins in prepare proposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 100 ++++++++++-------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 990b7a74a3..54560a8a5a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,7 +12,7 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use self::tx_bins::TxAllotedSpace; +use self::tx_bins::{AllocStatus, TxAllotedSpace}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -66,11 +66,11 @@ where txs.into_iter().map(record::add).collect(); // add mempool txs - let mut mempool_txs = self.build_mempool_txs(req.txs); + let mut mempool_txs = self.build_mempool_txs(&mut bins, req.txs); txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(); + let decrypted_txs = self.build_decrypted_txs(&mut bins); #[cfg(feature = "abcipp")] let decrypted_txs: Vec = decrypted_txs.into_iter().map(record::add).collect(); @@ -105,7 +105,7 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - _bins: &mut TxAllotedSpace, + bins: &mut TxAllotedSpace, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -159,7 +159,7 @@ where .get_protocol_key() .expect("Validators should always have a protocol key"); - iter_protocol_txs(VoteExtensionDigest { + let txs: Vec<_> = iter_protocol_txs(VoteExtensionDigest { ethereum_events, validator_set_update, }) @@ -167,53 +167,51 @@ where // TODO(feature = "abcipp"): remove this later, when we get rid of // `abciplus` .chain(protocol_txs.into_iter()) - .collect() - - // ```ignore - // for tx in txs.iter().map(Vec::as_slice) { - // match bins.try_alloc_protocol_tx(tx) { - // AllocStatus::Accepted => (), - // AllocStatus::Rejected => { - // // TODO: handle bin space full for protocol txs; - // // if we include a vote extension digest, we need - // // to include its corresponding protocol tx votes! - // // otherwise, we will get the same votes in future - // // block proposals - // tracing::debug!("No more space left for protocol transactions"); - // }. - // } - // } - // ``` + .collect(); + + match bins.try_alloc_protocol_tx_batch(txs.iter().map(Vec::as_slice)) { + AllocStatus::Accepted => txs, + AllocStatus::Rejected => { + // no space left for tx batch, so we + // do not include any protocol tx in + // this block + // + // TODO: maybe we should find a way to include + // validator set updates all the time. for instance, + // we could have recursive bins -> bin space within + // a bin is partitioned into yet more bins. so, we + // could have, say, 2/3 of the bin space available + // for eth events, and 1/3 available for valset + // upds + vec![] + } + AllocStatus::OverflowsBin => { + // TODO: handle tx whose size is greater + // than bin size + vec![] + } + } } /// Builds a batch of mempool transactions #[cfg(feature = "abcipp")] - fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + txs.len() / 2; - 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() + fn build_mempool_txs( + &mut self, + _bins: &mut TxAllotedSpace, + txs: Vec>, + ) -> Vec { + // TODO(feature = "abcipp"): implement building batch of mempool txs + todo!() } /// Builds a batch of mempool transactions #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + txs.len() / 2; + fn build_mempool_txs( + &mut self, + bins: &mut TxAllotedSpace, + txs: Vec>, + ) -> Vec { txs.into_iter() - .take(number_of_new_txs) .filter_map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) @@ -223,6 +221,10 @@ where None } }) + // TODO: handle bin overflows + .take_while(|tx_bytes| { + bins.try_alloc_encrypted_tx(&*tx_bytes) == AllocStatus::Accepted + }) .collect() } @@ -234,9 +236,13 @@ where // sources: // - https://specs.anoma.net/main/releases/v2.html // - https://github.com/anoma/ferveo - fn build_decrypted_txs(&mut self) -> Vec { + fn build_decrypted_txs( + &mut self, + bins: &mut TxAllotedSpace, + ) -> Vec { // TODO: This should not be hardcoded - let privkey = ::G2Affine::prime_subgroup_generator(); + let privkey = + ::G2Affine::prime_subgroup_generator(); self.storage .tx_queue @@ -248,6 +254,10 @@ where }) .to_bytes() }) + // TODO: handle bin overflows + .take_while(|tx_bytes| { + bins.try_alloc_decrypted_tx(&*tx_bytes) == AllocStatus::Accepted + }) .collect() } } From f4be2303d9c097c8b341114a37ac96bc4155e9da Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 10:50:33 +0000 Subject: [PATCH 1568/1995] Decrypted tx bin space depends on encrypted txs' --- .../lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 8b8ff172bb..f37e57eea6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -262,7 +262,16 @@ mod thres { pub const ENCRYPTED_TX: Ratio = Ratio::new_raw(1, 3); /// The threshold over Tendermint's allotted space for DKG decrypted txs. - pub const DECRYPTED_TX: Ratio = Ratio::new_raw(1, 3); + /// + /// Do not edit this threshold value. We should always have the + /// same or less space for decrypted txs as the one reserved + /// for encrypted txs. + /// + /// The reason for this is that during the decision process of + /// block height `H`, we must include the same number of decrypted + /// txs in the block as the number of encrypted txs proposed during + /// block height `H - 1`. + pub const DECRYPTED_TX: Ratio = ENCRYPTED_TX; } #[cfg(test)] From df16fda8349aafc769ae352521ec7f24b943192b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 10:51:24 +0000 Subject: [PATCH 1569/1995] Some design notes on block space usage --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 54560a8a5a..81cf3a9386 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -55,6 +55,19 @@ where // start counting allotted space for txs let mut bins = TxAllotedSpace::from(&req); + // NOTE: AD-HOC SOLUTION + // ====================== + // TODO: choose txs in this order: + // - decrypted txs (ALL OF THEM) + // - protocol txs (we should give priority to valset upds) + // - encrypted txs (it's fine if this bin is empty) + // + // at the beginning of an epoch, do not pick any + // encrypted txs :) inspired by solana + // + // `tracing::warn!()` log we are not accepting encrypted + // txs for a given block height + // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] let txs = self From 2446ce321dca9f42720491b93facedd72a63f978 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:07:04 +0000 Subject: [PATCH 1570/1995] Begin tx allotments state machine --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index f37e57eea6..3cc549e7d3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -274,6 +274,10 @@ mod thres { pub const DECRYPTED_TX: Ratio = ENCRYPTED_TX; } +mod states { + //! Different states of the tx bin allotments state machine. +} + #[cfg(test)] mod tests { use std::cell::RefCell; From 2aef45ae0d34ef8ebae3c5719575e19aaf6b5718 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:37:29 +0000 Subject: [PATCH 1571/1995] Rebase fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 14 ++-- .../ledger/shell/prepare_proposal/tx_bins.rs | 65 ++++++++++--------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 81cf3a9386..c26f923eca 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,7 +12,7 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use self::tx_bins::{AllocStatus, TxAllotedSpace}; +use self::tx_bins::{AllocStatus, TxAllottedSpace}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -40,7 +40,7 @@ where /// the proposal is rejected (unless we can simply overwrite /// them in the next block). // TODO: change second paragraph of the docstr, to reflect new - // alloted space per block design + // allotted space per block design pub fn prepare_proposal( &mut self, req: RequestPrepareProposal, @@ -53,7 +53,7 @@ where // TODO: add some info logging? // start counting allotted space for txs - let mut bins = TxAllotedSpace::from(&req); + let mut bins = TxAllottedSpace::from(&req); // NOTE: AD-HOC SOLUTION // ====================== @@ -118,7 +118,7 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - bins: &mut TxAllotedSpace, + bins: &mut TxAllottedSpace, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -210,7 +210,7 @@ where #[cfg(feature = "abcipp")] fn build_mempool_txs( &mut self, - _bins: &mut TxAllotedSpace, + _bins: &mut TxAllottedSpace, txs: Vec>, ) -> Vec { // TODO(feature = "abcipp"): implement building batch of mempool txs @@ -221,7 +221,7 @@ where #[cfg(not(feature = "abcipp"))] fn build_mempool_txs( &mut self, - bins: &mut TxAllotedSpace, + bins: &mut TxAllottedSpace, txs: Vec>, ) -> Vec { txs.into_iter() @@ -251,7 +251,7 @@ where // - https://github.com/anoma/ferveo fn build_decrypted_txs( &mut self, - bins: &mut TxAllotedSpace, + bins: &mut TxAllottedSpace, ) -> Vec { // TODO: This should not be hardcoded let privkey = diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 3cc549e7d3..b326131d51 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -78,7 +78,8 @@ impl TxAllottedSpace { decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), }; // concede all uninitialized space to protocol txs - bins.protocol_txs.allotted_space_in_bytes += bins.uninitialized_space(); + bins.protocol_txs.allotted_space_in_bytes += + bins.uninitialized_space_in_bytes(); bins } @@ -86,7 +87,7 @@ impl TxAllottedSpace { /// /// This method should not be used outside of [`TxAllottedSpace`] /// instance construction or unit testing. - fn uninitialized_space(&self) -> u64 { + fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes + self.decrypted_txs.allotted_space_in_bytes; @@ -95,30 +96,30 @@ impl TxAllottedSpace { /// The total space, in bytes, occupied by each transaction. #[inline] - pub fn occupied_space(&self) -> u64 { - self.protocol_txs.current_space - + self.encrypted_txs.current_space - + self.decrypted_txs.current_space + pub fn occupied_space_in_bytes(&self) -> u64 { + self.protocol_txs.current_space_in_bytes + + self.encrypted_txs.current_space_in_bytes + + self.decrypted_txs.current_space_in_bytes } /// Return the amount, in bytes, of free space in this - /// [`TxAllotedSpace`]. + /// [`TxAllottedSpace`]. #[inline] - pub fn free_space(&self) -> u64 { - self.provided_by_tendermint - self.occupied_space() + pub fn free_space_in_bytes(&self) -> u64 { + self.bytes_provided_by_tendermint - self.occupied_space_in_bytes() } - /// Checks if this [`TxAllotedSpace`] has any free space remaining. + /// Checks if this [`TxAllottedSpace`] has any free space remaining. #[allow(dead_code)] #[inline] pub fn has_free_space(&self) -> bool { - self.free_space() > 0 + self.free_space_in_bytes() > 0 } } // all allocation boilerplate code shall // be shunned to this impl block -- shame! -impl TxAllotedSpace { +impl TxAllottedSpace { /// Try to allocate space for a new protocol transaction. #[allow(dead_code)] #[inline] @@ -158,26 +159,26 @@ impl TxAllotedSpace { self.encrypted_txs.try_dump_all(txs) } - /// The total space, in bytes, occupied by each transaction. - #[inline] - pub fn occupied_space_in_bytes(&self) -> u64 { - self.protocol_txs.current_space_in_bytes - + self.encrypted_txs.current_space_in_bytes - + self.decrypted_txs.current_space_in_bytes - } + // --------------------------------------------------- // - /// Return the amount, in bytes, of free space in this - /// [`TxAllottedSpace`]. + /// Try to allocate space for a new DKG decrypted transaction. + #[allow(dead_code)] #[inline] - pub fn free_space_in_bytes(&self) -> u64 { - self.bytes_provided_by_tendermint - self.occupied_space_in_bytes() + pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { + self.decrypted_txs.try_dump(tx) } - /// Checks if this [`TxAllottedSpace`] has any free space remaining. + /// Try to allocate space for a new batch of DKG decrypted transactions. #[allow(dead_code)] #[inline] - pub fn has_free_space(&self) -> bool { - self.free_space_in_bytes() > 0 + pub fn try_alloc_decrypted_tx_batch<'tx, T>( + &mut self, + txs: T, + ) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.decrypted_txs.try_dump_all(txs) } } @@ -214,12 +215,12 @@ impl TxBin { #[inline] fn try_dump(&mut self, tx: &[u8]) -> AllocStatus { let tx_len = tx.len() as u64; - if tx_len > self.alloted_space { + if tx_len > self.allotted_space_in_bytes { return AllocStatus::OverflowsBin; } - let occupied = self.current_space + tx_len; - if occupied <= self.alloted_space { - self.current_space = occupied; + let occupied = self.current_space_in_bytes + tx_len; + if occupied <= self.allotted_space_in_bytes { + self.current_space_in_bytes = occupied; AllocStatus::Accepted } else { AllocStatus::Rejected @@ -241,7 +242,7 @@ impl TxBin { AllocStatus::Accepted => space_diff += tx.len() as u64, status @ (AllocStatus::Rejected | AllocStatus::OverflowsBin) => { - self.current_space -= space_diff; + self.current_space_in_bytes -= space_diff; return status; } } @@ -356,7 +357,7 @@ mod tests { tendermint_max_block_space_in_bytes: u64, ) { let bins = TxAllottedSpace::init(tendermint_max_block_space_in_bytes); - assert_eq!(0, bins.uninitialized_space()); + assert_eq!(0, bins.uninitialized_space_in_bytes()); } /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. From 6fc2d4689ac7953005a6de565a8a24d3388ff6f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:41:39 +0000 Subject: [PATCH 1572/1995] Revert tx bins PrepareProposal changes --- .../lib/node/ledger/shell/prepare_proposal.rs | 105 +++++------------- 1 file changed, 30 insertions(+), 75 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c26f923eca..0e7ac9828f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,7 +12,6 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use self::tx_bins::{AllocStatus, TxAllottedSpace}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -40,7 +39,7 @@ where /// the proposal is rejected (unless we can simply overwrite /// them in the next block). // TODO: change second paragraph of the docstr, to reflect new - // allotted space per block design + // alloted space per block design pub fn prepare_proposal( &mut self, req: RequestPrepareProposal, @@ -52,38 +51,21 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // TODO: add some info logging? - // start counting allotted space for txs - let mut bins = TxAllottedSpace::from(&req); - - // NOTE: AD-HOC SOLUTION - // ====================== - // TODO: choose txs in this order: - // - decrypted txs (ALL OF THEM) - // - protocol txs (we should give priority to valset upds) - // - encrypted txs (it's fine if this bin is empty) - // - // at the beginning of an epoch, do not pick any - // encrypted txs :) inspired by solana - // - // `tracing::warn!()` log we are not accepting encrypted - // txs for a given block height - // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] - let txs = self - .build_vote_extensions_txs(&mut bins, req.local_last_commit); + let txs = self.build_vote_extensions_txs(req.local_last_commit); #[cfg(not(feature = "abcipp"))] - let mut txs = self.build_vote_extensions_txs(&mut bins, &req.txs); + let mut txs = self.build_vote_extensions_txs(&req.txs); #[cfg(feature = "abcipp")] let mut txs: Vec = txs.into_iter().map(record::add).collect(); // add mempool txs - let mut mempool_txs = self.build_mempool_txs(&mut bins, req.txs); + let mut mempool_txs = self.build_mempool_txs(req.txs); txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(&mut bins); + let decrypted_txs = self.build_decrypted_txs(); #[cfg(feature = "abcipp")] let decrypted_txs: Vec = decrypted_txs.into_iter().map(record::add).collect(); @@ -118,7 +100,6 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - bins: &mut TxAllottedSpace, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -172,7 +153,7 @@ where .get_protocol_key() .expect("Validators should always have a protocol key"); - let txs: Vec<_> = iter_protocol_txs(VoteExtensionDigest { + iter_protocol_txs(VoteExtensionDigest { ethereum_events, validator_set_update, }) @@ -180,51 +161,37 @@ where // TODO(feature = "abcipp"): remove this later, when we get rid of // `abciplus` .chain(protocol_txs.into_iter()) - .collect(); - - match bins.try_alloc_protocol_tx_batch(txs.iter().map(Vec::as_slice)) { - AllocStatus::Accepted => txs, - AllocStatus::Rejected => { - // no space left for tx batch, so we - // do not include any protocol tx in - // this block - // - // TODO: maybe we should find a way to include - // validator set updates all the time. for instance, - // we could have recursive bins -> bin space within - // a bin is partitioned into yet more bins. so, we - // could have, say, 2/3 of the bin space available - // for eth events, and 1/3 available for valset - // upds - vec![] - } - AllocStatus::OverflowsBin => { - // TODO: handle tx whose size is greater - // than bin size - vec![] - } - } + .collect() } /// Builds a batch of mempool transactions #[cfg(feature = "abcipp")] - fn build_mempool_txs( - &mut self, - _bins: &mut TxAllottedSpace, - txs: Vec>, - ) -> Vec { - // TODO(feature = "abcipp"): implement building batch of mempool txs - todo!() + fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { + // filter in half of the new txs from Tendermint, only keeping + // wrappers + let number_of_new_txs = 1 + txs.len() / 2; + 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() } /// Builds a batch of mempool transactions #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs( - &mut self, - bins: &mut TxAllottedSpace, - txs: Vec>, - ) -> Vec { + fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { + // filter in half of the new txs from Tendermint, only keeping + // wrappers + let number_of_new_txs = 1 + txs.len() / 2; txs.into_iter() + .take(number_of_new_txs) .filter_map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) @@ -234,10 +201,6 @@ where None } }) - // TODO: handle bin overflows - .take_while(|tx_bytes| { - bins.try_alloc_encrypted_tx(&*tx_bytes) == AllocStatus::Accepted - }) .collect() } @@ -249,13 +212,9 @@ where // sources: // - https://specs.anoma.net/main/releases/v2.html // - https://github.com/anoma/ferveo - fn build_decrypted_txs( - &mut self, - bins: &mut TxAllottedSpace, - ) -> Vec { + fn build_decrypted_txs(&mut self) -> Vec { // TODO: This should not be hardcoded - let privkey = - ::G2Affine::prime_subgroup_generator(); + let privkey = ::G2Affine::prime_subgroup_generator(); self.storage .tx_queue @@ -267,10 +226,6 @@ where }) .to_bytes() }) - // TODO: handle bin overflows - .take_while(|tx_bytes| { - bins.try_alloc_decrypted_tx(&*tx_bytes) == AllocStatus::Accepted - }) .collect() } } From 012af04ece0531a70df0e481402eac2f16db9210 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 14:55:23 +0000 Subject: [PATCH 1573/1995] Fix confusing terminology in tx bin unit tests --- .../node/ledger/shell/prepare_proposal/tx_bins.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index b326131d51..f0fe43c045 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -327,10 +327,10 @@ mod tests { } /// Test that dumping txs whose total combined size - /// is less than the bin cap does not overflow the bin. + /// is less than the bin cap does not fill up the bin. #[test] - fn test_tx_dump_doesnt_overflow_bin(args in arb_transactions()) { - proptest_tx_dump_doesnt_overflow_bin(args) + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) } } @@ -360,8 +360,8 @@ mod tests { assert_eq!(0, bins.uninitialized_space_in_bytes()); } - /// Implementation of [`test_tx_dump_doesnt_overflow_bin`]. - fn proptest_tx_dump_doesnt_overflow_bin(args: PropTx) { + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { let PropTx { tendermint_max_block_space_in_bytes, protocol_txs, @@ -372,7 +372,7 @@ mod tests { tendermint_max_block_space_in_bytes, )); - // produce new txs until we overflow the bins + // produce new txs until we fill up the bins // // TODO: ideally the proptest strategy would already return // txs whose total added size would be bounded @@ -393,7 +393,7 @@ mod tests { }); // make sure we can keep dumping txs, - // without overflowing the bins + // without filling up the bins for tx in protocol_txs { assert_eq!( bins.borrow_mut().try_alloc_protocol_tx(&tx), From 82e8a007ff993cd23f596d94192777dfa16e7b1b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 15:42:45 +0000 Subject: [PATCH 1574/1995] Add all possible tx bin states --- .../ledger/shell/prepare_proposal/tx_bins.rs | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index f0fe43c045..d613cf6b6c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -275,8 +275,96 @@ mod thres { pub const DECRYPTED_TX: Ratio = ENCRYPTED_TX; } +// hacky workaround to get module docstrings formatted properly +#[rustfmt::skip] mod states { - //! Different states of the tx bin allotments state machine. + //! All the states of the [`TxAllottedSpace`] state machine, + //! over the extent of a Tendermint consensus round + //! block proposal. + //! + //! # States + //! + //! The state machine moves through the following states: + //! + //! 1. [`BuildingDecryptedTxBatch`] - the initial state. In + //! this state, we populate a block with DKG decrypted txs. + //! 2. [`BuildingProtocolTxBatch`] - the second state. In + //! this state, we populate a block with protocol txs. + //! 3. [`BuildingEncryptedTxBatch`] - the third state. In + //! this state, we populate a block with DKG encrypted txs. + //! This state supports two modes of operation, which you can + //! think of as two states diverging from [`BuildingProtocolTxBatch`]: + //! 1. [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs + //! are included in a block proposal. + //! 2. [`WithEncryptedTxs`] - When this mode is active, we are able + //! to include encrypted txs in a block proposal. + //! 4. [`FillingRemainingSpace`] - the fourth and final state. + //! During this phase, we fill all remaining block space with arbitrary + //! transactions that haven't been included yet. This state supports the + //! same two modes of operation defined above. + + #[allow(unused_imports)] + use super::TxAllottedSpace; + + #[doc(inline)] + pub use super::states_impl::*; +} + +mod states_impl { + //! Implements [`super::states`]. + + /// The leader of the current Tendermint round is building + /// a new batch of DKG decrypted transactions. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub enum BuildingDecryptedTxBatch {} + + /// The leader of the current Tendermint round is building + /// a new batch of Namada protocol transactions. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub enum BuildingProtocolTxBatch {} + + /// The leader of the current Tendermint round is building + /// a new batch of DKG encrypted transactions. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub struct BuildingEncryptedTxBatch { + /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. + _mode: Mode, + } + + /// The leader of the current Tendermint round is populating + /// all remaining space in a block proposal with arbitrary + /// transactions. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub struct FillingRemainingSpace { + /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. + _mode: Mode, + } + + /// Allow block proposals to include encrypted txs. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub enum WithEncryptedTxs {} + + /// Prohibit block proposals from including encrypted txs. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + pub enum WithoutEncryptedTxs {} } #[cfg(test)] From 7d831c5a0ecb9010f35fe23d96f076e87212af05 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 15:55:28 +0000 Subject: [PATCH 1575/1995] Temporarily disable tx bins unit tests --- .../ledger/shell/prepare_proposal/tx_bins.rs | 374 +++++++++--------- 1 file changed, 188 insertions(+), 186 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index d613cf6b6c..fa8ba01e7e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -367,189 +367,191 @@ mod states_impl { pub enum WithoutEncryptedTxs {} } -#[cfg(test)] -mod tests { - use std::cell::RefCell; - - use proptest::prelude::*; - - use super::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - - /// Proptest generated txs. - #[derive(Debug)] - struct PropTx { - tendermint_max_block_space_in_bytes: u64, - protocol_txs: Vec, - encrypted_txs: Vec, - decrypted_txs: Vec, - } - - /// Check if the sum of all individual tx thresholds does - /// not exceed one. - /// - /// This is important, because we do not want to exceed - /// the maximum block size in Tendermint, and get randomly - /// rejected blocks. - #[test] - fn test_tx_thres_doesnt_exceed_one() { - let sum = - thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert_eq!(sum.to_integer(), 1); - } - - proptest! { - /// Check if we reject a tx when its respective bin - /// capacity has been reached on a [`TxAllottedSpace`]. - #[test] - fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { - proptest_reject_tx_on_bin_cap_reached(max) - } - - /// Check if the sum of all individual bin allotments for a - /// [`TxAllottedSpace`] corresponds to the total space ceded - /// by Tendermint. - #[test] - fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { - proptest_bin_capacity_eq_provided_space(max) - } - - /// Test that dumping txs whose total combined size - /// is less than the bin cap does not fill up the bin. - #[test] - fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { - proptest_tx_dump_doesnt_fill_up_bin(args) - } - } - - /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. - fn proptest_reject_tx_on_bin_cap_reached( - tendermint_max_block_space_in_bytes: u64, - ) { - let mut bins = - TxAllottedSpace::init(tendermint_max_block_space_in_bytes); - - // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space_in_bytes = - bins.decrypted_txs.allotted_space_in_bytes; - - // make sure we can't dump any new decrypted txs in the bin - assert_eq!( - bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), - AllocStatus::Rejected - ); - } - - /// Implementation of [`test_bin_capacity_eq_provided_space`]. - fn proptest_bin_capacity_eq_provided_space( - tendermint_max_block_space_in_bytes: u64, - ) { - let bins = TxAllottedSpace::init(tendermint_max_block_space_in_bytes); - assert_eq!(0, bins.uninitialized_space_in_bytes()); - } - - /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. - fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { - let PropTx { - tendermint_max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } = args; - let bins = RefCell::new(TxAllottedSpace::init( - tendermint_max_block_space_in_bytes, - )); - - // produce new txs until we fill up the bins - // - // TODO: ideally the proptest strategy would already return - // txs whose total added size would be bounded - let protocol_txs = protocol_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - - // make sure we can keep dumping txs, - // without filling up the bins - for tx in protocol_txs { - assert_eq!( - bins.borrow_mut().try_alloc_protocol_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in encrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_encrypted_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in decrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_decrypted_tx(&tx), - AllocStatus::Accepted - ); - } - } - - prop_compose! { - /// Generate arbitrarily sized txs of different kinds. - fn arb_transactions() - // create base strategies - ( - (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, - decrypted_tx_max_bin_size) in arb_max_bin_sizes(), - ) - // compose strategies - ( - tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), - protocol_txs in arb_tx_list(protocol_tx_max_bin_size), - encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), - decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), - ) - -> PropTx { - PropTx { - tendermint_max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } - } - } - - /// Return random bin sizes for a [`TxAllottedSpace`]. - fn arb_max_bin_sizes() -> impl Strategy - { - const MAX_BLOCK_SIZE_BYTES: u64 = 1000; - (1..=MAX_BLOCK_SIZE_BYTES).prop_map( - |tendermint_max_block_space_in_bytes| { - ( - tendermint_max_block_space_in_bytes, - (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - ) - }, - ) - } - - /// Return a list of txs. - fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { - const MAX_TX_NUM: usize = 64; - let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); - prop::collection::vec(tx, 0..=MAX_TX_NUM) - } -} +// ```ignore +// #[cfg(test)] +// mod tests { +// use std::cell::RefCell; +// +// use proptest::prelude::*; +// +// use super::*; +// use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; +// +// /// Proptest generated txs. +// #[derive(Debug)] +// struct PropTx { +// tendermint_max_block_space_in_bytes: u64, +// protocol_txs: Vec, +// encrypted_txs: Vec, +// decrypted_txs: Vec, +// } +// +// /// Check if the sum of all individual tx thresholds does +// /// not exceed one. +// /// +// /// This is important, because we do not want to exceed +// /// the maximum block size in Tendermint, and get randomly +// /// rejected blocks. +// #[test] +// fn test_tx_thres_doesnt_exceed_one() { +// let sum = +// thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; +// assert_eq!(sum.to_integer(), 1); +// } +// +// proptest! { +// /// Check if we reject a tx when its respective bin +// /// capacity has been reached on a [`TxAllottedSpace`]. +// #[test] +// fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { +// proptest_reject_tx_on_bin_cap_reached(max) +// } +// +// /// Check if the sum of all individual bin allotments for a +// /// [`TxAllottedSpace`] corresponds to the total space ceded +// /// by Tendermint. +// #[test] +// fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { +// proptest_bin_capacity_eq_provided_space(max) +// } +// +// /// Test that dumping txs whose total combined size +// /// is less than the bin cap does not fill up the bin. +// #[test] +// fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { +// proptest_tx_dump_doesnt_fill_up_bin(args) +// } +// } +// +// /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. +// fn proptest_reject_tx_on_bin_cap_reached( +// tendermint_max_block_space_in_bytes: u64, +// ) { +// let mut bins = +// TxAllottedSpace::init(tendermint_max_block_space_in_bytes); +// +// // fill the entire bin of decrypted txs +// bins.decrypted_txs.current_space_in_bytes = +// bins.decrypted_txs.allotted_space_in_bytes; +// +// // make sure we can't dump any new decrypted txs in the bin +// assert_eq!( +// bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), +// AllocStatus::Rejected +// ); +// } +// +// /// Implementation of [`test_bin_capacity_eq_provided_space`]. +// fn proptest_bin_capacity_eq_provided_space( +// tendermint_max_block_space_in_bytes: u64, +// ) { +// let bins = TxAllottedSpace::init(tendermint_max_block_space_in_bytes); +// assert_eq!(0, bins.uninitialized_space_in_bytes()); +// } +// +// /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. +// fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { +// let PropTx { +// tendermint_max_block_space_in_bytes, +// protocol_txs, +// encrypted_txs, +// decrypted_txs, +// } = args; +// let bins = RefCell::new(TxAllottedSpace::init( +// tendermint_max_block_space_in_bytes, +// )); +// +// // produce new txs until we fill up the bins +// // +// // TODO: ideally the proptest strategy would already return +// // txs whose total added size would be bounded +// let protocol_txs = protocol_txs.into_iter().take_while(|tx| { +// let bin = bins.borrow().protocol_txs; +// let new_size = bin.current_space_in_bytes + tx.len() as u64; +// new_size < bin.allotted_space_in_bytes +// }); +// let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { +// let bin = bins.borrow().encrypted_txs; +// let new_size = bin.current_space_in_bytes + tx.len() as u64; +// new_size < bin.allotted_space_in_bytes +// }); +// let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { +// let bin = bins.borrow().decrypted_txs; +// let new_size = bin.current_space_in_bytes + tx.len() as u64; +// new_size < bin.allotted_space_in_bytes +// }); +// +// // make sure we can keep dumping txs, +// // without filling up the bins +// for tx in protocol_txs { +// assert_eq!( +// bins.borrow_mut().try_alloc_protocol_tx(&tx), +// AllocStatus::Accepted +// ); +// } +// for tx in encrypted_txs { +// assert_eq!( +// bins.borrow_mut().try_alloc_encrypted_tx(&tx), +// AllocStatus::Accepted +// ); +// } +// for tx in decrypted_txs { +// assert_eq!( +// bins.borrow_mut().try_alloc_decrypted_tx(&tx), +// AllocStatus::Accepted +// ); +// } +// } +// +// prop_compose! { +// /// Generate arbitrarily sized txs of different kinds. +// fn arb_transactions() +// // create base strategies +// ( +// (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, +// decrypted_tx_max_bin_size) in arb_max_bin_sizes(), +// ) +// // compose strategies +// ( +// tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), +// protocol_txs in arb_tx_list(protocol_tx_max_bin_size), +// encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), +// decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), +// ) +// -> PropTx { +// PropTx { +// tendermint_max_block_space_in_bytes, +// protocol_txs, +// encrypted_txs, +// decrypted_txs, +// } +// } +// } +// +// /// Return random bin sizes for a [`TxAllottedSpace`]. +// fn arb_max_bin_sizes() -> impl Strategy +// { +// const MAX_BLOCK_SIZE_BYTES: u64 = 1000; +// (1..=MAX_BLOCK_SIZE_BYTES).prop_map( +// |tendermint_max_block_space_in_bytes| { +// ( +// tendermint_max_block_space_in_bytes, +// (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) +// .to_integer() as usize, +// (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) +// .to_integer() as usize, +// (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) +// .to_integer() as usize, +// ) +// }, +// ) +// } +// +// /// Return a list of txs. +// fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { +// const MAX_TX_NUM: usize = 64; +// let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); +// prop::collection::vec(tx, 0..=MAX_TX_NUM) +// } +// } +// ``` From 28ab01e6d4f3b7effe1a0207b47437cc8d34daec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 16:23:54 +0000 Subject: [PATCH 1576/1995] WIP: Tx bins state machine impl --- .../ledger/shell/prepare_proposal/tx_bins.rs | 135 +++++++++++------- 1 file changed, 86 insertions(+), 49 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index fa8ba01e7e..17cd033244 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -20,6 +20,8 @@ // the total gas of all chosen txs cannot exceed the configured max // gas per block, otherwise a proposal will be rejected! +use std::marker::PhantomData; + use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -45,7 +47,9 @@ pub enum AllocStatus { /// - DKG decrypted transactions. /// - DKG encrypted transactions. #[derive(Default)] -pub struct TxAllottedSpace { +pub struct TxAllottedSpace { + /// The current state of the [`TxAllottedSpace`] state machine. + _state: PhantomData<*const State>, /// The total space Tendermint has allotted to the /// application for the current block height. bytes_provided_by_tendermint: u64, @@ -57,7 +61,9 @@ pub struct TxAllottedSpace { decrypted_txs: TxBin, } -impl From<&RequestPrepareProposal> for TxAllottedSpace { +impl From<&RequestPrepareProposal> + for TxAllottedSpace +{ #[inline] fn from(req: &RequestPrepareProposal) -> Self { let tendermint_max_block_space_in_bytes = req.max_tx_bytes as u64; @@ -65,28 +71,73 @@ impl From<&RequestPrepareProposal> for TxAllottedSpace { } } -impl TxAllottedSpace { +impl TxAllottedSpace { /// Construct a new [`TxAllottedSpace`], with an upper bound /// on the max number of txs in a block defined by Tendermint. #[inline] pub fn init(tendermint_max_block_space_in_bytes: u64) -> Self { let max = tendermint_max_block_space_in_bytes; - let mut bins = Self { + Self { + _state: PhantomData, bytes_provided_by_tendermint: max, - protocol_txs: TxBin::init_from(max, thres::PROTOCOL_TX), - encrypted_txs: TxBin::init_from(max, thres::ENCRYPTED_TX), - decrypted_txs: TxBin::init_from(max, thres::DECRYPTED_TX), - }; - // concede all uninitialized space to protocol txs - bins.protocol_txs.allotted_space_in_bytes += - bins.uninitialized_space_in_bytes(); - bins + protocol_txs: TxBin::default(), + encrypted_txs: TxBin::default(), + decrypted_txs: TxBin::init(max), + } + } + + /// Try to allocate space for a new DKG decrypted transaction. + #[allow(dead_code)] + #[inline] + pub fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.decrypted_txs.try_dump(tx) + } + + /// Try to allocate space for a new batch of DKG decrypted transactions. + #[allow(dead_code)] + #[inline] + pub fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.decrypted_txs.try_dump_all(txs) } + /// Transition to the next state in the [`TxAllottedSpace`] state machine. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + #[allow(dead_code)] + #[inline] + pub fn next_state( + self, + ) -> TxAllottedSpace { + let Self { + bytes_provided_by_tendermint, + mut protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + // TODO: reserve space for protocol txs + protocol_txs.allotted_space_in_bytes = 0; + TxAllottedSpace { + _state: PhantomData, + bytes_provided_by_tendermint, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} + +// WIP +impl TxAllottedSpace { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// /// This method should not be used outside of [`TxAllottedSpace`] /// instance construction or unit testing. + #[allow(dead_code)] fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes @@ -119,7 +170,9 @@ impl TxAllottedSpace { // all allocation boilerplate code shall // be shunned to this impl block -- shame! -impl TxAllottedSpace { +// +// WIP +impl TxAllottedSpace { /// Try to allocate space for a new protocol transaction. #[allow(dead_code)] #[inline] @@ -158,28 +211,6 @@ impl TxAllottedSpace { { self.encrypted_txs.try_dump_all(txs) } - - // --------------------------------------------------- // - - /// Try to allocate space for a new DKG decrypted transaction. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_decrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { - self.decrypted_txs.try_dump(tx) - } - - /// Try to allocate space for a new batch of DKG decrypted transactions. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_decrypted_tx_batch<'tx, T>( - &mut self, - txs: T, - ) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.decrypted_txs.try_dump_all(txs) - } } /// Allotted space for a batch of transactions of the same kind in some @@ -194,20 +225,26 @@ struct TxBin { impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number - /// of storable txs defined by a ratio over Tendermint's max block size. + /// of storable txs defined by a ratio over `max_bytes`. #[inline] - fn init_from( - tendermint_max_block_space_in_bytes: u64, - frac: Ratio, - ) -> Self { - let allotted_space_in_bytes = - (frac * tendermint_max_block_space_in_bytes).to_integer(); + #[allow(dead_code)] + fn init_over_ratio(max_bytes: u64, frac: Ratio) -> Self { + let allotted_space_in_bytes = (frac * max_bytes).to_integer(); Self { allotted_space_in_bytes, current_space_in_bytes: 0, } } + /// Construct a new [`TxBin`], with a capacity of `max_bytes`. + #[inline] + fn init(max_bytes: u64) -> Self { + Self { + allotted_space_in_bytes: max_bytes, + current_space_in_bytes: 0, + } + } + /// Try to dump a new transaction into this [`TxBin`]. /// /// Signal the caller if the tx is larger than its max @@ -257,21 +294,21 @@ mod thres { use num_rational::Ratio; /// The threshold over Tendermint's allotted space for protocol txs. + #[allow(dead_code)] pub const PROTOCOL_TX: Ratio = Ratio::new_raw(1, 3); /// The threshold over Tendermint's allotted space for DKG encrypted txs. + #[allow(dead_code)] pub const ENCRYPTED_TX: Ratio = Ratio::new_raw(1, 3); /// The threshold over Tendermint's allotted space for DKG decrypted txs. /// - /// Do not edit this threshold value. We should always have the - /// same or less space for decrypted txs as the one reserved - /// for encrypted txs. - /// - /// The reason for this is that during the decision process of + /// This value should always be the same as [`ENCRYPTED_TX`]. + /// The reason for which is that during the decision process of /// block height `H`, we must include the same number of decrypted - /// txs in the block as the number of encrypted txs proposed during - /// block height `H - 1`. + /// txs as the number of encrypted txs proposed during block height + /// `H - 1`. + #[allow(dead_code)] pub const DECRYPTED_TX: Ratio = ENCRYPTED_TX; } From 283c374a6234a4c6eb3b490efc959ff2e5212bbd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 16:32:59 +0000 Subject: [PATCH 1577/1995] TODO items --- .../lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 17cd033244..6a814ea4cb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -14,7 +14,16 @@ // forever, it'll be a DoS vec, as we can make nodes run out of // memory! maybe we should allow block decisions for txs that are // too big to fit in their respective bin? in these special block -// decisions, we would only decide proposals with "large" txs +// decisions, we would only decide proposals with "large" txs?? +// +// MAYBE: in the state machine impl, reset to beginning state, and +// and alloc space for large tx right at the start. the problem with +// this is that then we may not have enough space for decrypted txs + +// TODO: panic if we don't have enough space reserved for a +// decrypted tx; in theory, we should always have enough space +// reserved for decrypted txs, given the invariants of the state +// machine // TODO: refactor our measure of space to also reflect gas costs. // the total gas of all chosen txs cannot exceed the configured max From 17c7c787752efbc1fe5ea4bcde97393f8ba5fbbe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 16:39:22 +0000 Subject: [PATCH 1578/1995] Code review suggestions --- .../node/ledger/shell/prepare_proposal/tx_bins.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 6a814ea4cb..2a2426a5b8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -61,7 +61,7 @@ pub struct TxAllottedSpace { _state: PhantomData<*const State>, /// The total space Tendermint has allotted to the /// application for the current block height. - bytes_provided_by_tendermint: u64, + max_block_space_in_bytes: u64, /// The current space utilized by protocol transactions. protocol_txs: TxBin, /// The current space utilized by DKG encrypted transactions. @@ -82,13 +82,13 @@ impl From<&RequestPrepareProposal> impl TxAllottedSpace { /// Construct a new [`TxAllottedSpace`], with an upper bound - /// on the max number of txs in a block defined by Tendermint. + /// on the max size of all txs in a block defined by Tendermint. #[inline] pub fn init(tendermint_max_block_space_in_bytes: u64) -> Self { let max = tendermint_max_block_space_in_bytes; Self { _state: PhantomData, - bytes_provided_by_tendermint: max, + max_block_space_in_bytes: max, protocol_txs: TxBin::default(), encrypted_txs: TxBin::default(), decrypted_txs: TxBin::init(max), @@ -122,7 +122,7 @@ impl TxAllottedSpace { self, ) -> TxAllottedSpace { let Self { - bytes_provided_by_tendermint, + max_block_space_in_bytes, mut protocol_txs, encrypted_txs, decrypted_txs, @@ -132,7 +132,7 @@ impl TxAllottedSpace { protocol_txs.allotted_space_in_bytes = 0; TxAllottedSpace { _state: PhantomData, - bytes_provided_by_tendermint, + max_block_space_in_bytes, protocol_txs, encrypted_txs, decrypted_txs, @@ -151,7 +151,7 @@ impl TxAllottedSpace { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes + self.decrypted_txs.allotted_space_in_bytes; - self.bytes_provided_by_tendermint - total_bin_space + self.max_block_space_in_bytes - total_bin_space } /// The total space, in bytes, occupied by each transaction. @@ -166,7 +166,7 @@ impl TxAllottedSpace { /// [`TxAllottedSpace`]. #[inline] pub fn free_space_in_bytes(&self) -> u64 { - self.bytes_provided_by_tendermint - self.occupied_space_in_bytes() + self.max_block_space_in_bytes - self.occupied_space_in_bytes() } /// Checks if this [`TxAllottedSpace`] has any free space remaining. From 6c43416938bbc9d597f769f5a409641cb99a6a47 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:04:25 +0000 Subject: [PATCH 1579/1995] Add state trait --- .../ledger/shell/prepare_proposal/tx_bins.rs | 103 +++++++++++++----- 1 file changed, 73 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 2a2426a5b8..e06df0b364 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -33,6 +33,8 @@ use std::marker::PhantomData; use num_rational::Ratio; +#[doc(inline)] +pub use self::states::State; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// All status responses from trying to allocate block space for a tx. @@ -94,49 +96,46 @@ impl TxAllottedSpace { decrypted_txs: TxBin::init(max), } } +} + +impl states::State for TxAllottedSpace { + // TODO: change to `TxAllottedSpace` + type Next = (); - /// Try to allocate space for a new DKG decrypted transaction. - #[allow(dead_code)] #[inline] - pub fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { self.decrypted_txs.try_dump(tx) } - /// Try to allocate space for a new batch of DKG decrypted transactions. - #[allow(dead_code)] #[inline] - pub fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx, { self.decrypted_txs.try_dump_all(txs) } - /// Transition to the next state in the [`TxAllottedSpace`] state machine. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] #[inline] - pub fn next_state( - self, - ) -> TxAllottedSpace { - let Self { - max_block_space_in_bytes, - mut protocol_txs, - encrypted_txs, - decrypted_txs, - .. - } = self; - // TODO: reserve space for protocol txs - protocol_txs.allotted_space_in_bytes = 0; - TxAllottedSpace { - _state: PhantomData, - max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } + fn next_state(self) -> Self::Next { + // ```ignore + // let Self { + // max_block_space_in_bytes, + // mut protocol_txs, + // encrypted_txs, + // decrypted_txs, + // .. + // } = self; + // // TODO: reserve space for protocol txs + // protocol_txs.allotted_space_in_bytes = 0; + // TxAllottedSpace { + // _state: PhantomData, + // max_block_space_in_bytes, + // protocol_txs, + // encrypted_txs, + // decrypted_txs, + // } + // ``` + todo!() } } @@ -359,6 +358,10 @@ mod states { mod states_impl { //! Implements [`super::states`]. + use super::AllocStatus; + #[allow(unused_imports)] + use super::TxAllottedSpace; + /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. /// @@ -411,6 +414,46 @@ mod states_impl { /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. #[allow(dead_code)] pub enum WithoutEncryptedTxs {} + + impl State for () { + type Next = (); + + fn try_alloc(&mut self, _: &[u8]) -> AllocStatus { + panic!("Can't do anything in the empty state"); + } + + fn try_alloc_batch<'tx, T>(&mut self, _: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + panic!("Can't do anything in the empty state"); + } + + fn next_state(self) -> Self::Next { + panic!("Can't do anything in the empty state"); + } + } + + /// Represents a state in the [`TxAllottedSpace`] state machine. + /// + /// For more info, read the module docs of + /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + pub trait State { + /// The next state in the [`TxAllottedSpace`] state machine. + type Next: State; + + /// Try to allocate space for a new transaction. + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; + + /// Try to allocate space for a new batch of transactions. + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx; + + /// Transition to the next state in the [`TxAllottedSpace`] state + /// machine. + fn next_state(self) -> Self::Next; + } } // ```ignore From 33513bb398e96665db0a88216bef62e157c5056e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:08:22 +0000 Subject: [PATCH 1580/1995] Rename block space allocator --- .../ledger/shell/prepare_proposal/tx_bins.rs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index e06df0b364..bf9f845832 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -58,8 +58,8 @@ pub enum AllocStatus { /// - DKG decrypted transactions. /// - DKG encrypted transactions. #[derive(Default)] -pub struct TxAllottedSpace { - /// The current state of the [`TxAllottedSpace`] state machine. +pub struct BlockSpaceAllocator { + /// The current state of the [`BlockSpaceAllocator`] state machine. _state: PhantomData<*const State>, /// The total space Tendermint has allotted to the /// application for the current block height. @@ -73,7 +73,7 @@ pub struct TxAllottedSpace { } impl From<&RequestPrepareProposal> - for TxAllottedSpace + for BlockSpaceAllocator { #[inline] fn from(req: &RequestPrepareProposal) -> Self { @@ -82,8 +82,8 @@ impl From<&RequestPrepareProposal> } } -impl TxAllottedSpace { - /// Construct a new [`TxAllottedSpace`], with an upper bound +impl BlockSpaceAllocator { + /// Construct a new [`BlockSpaceAllocator`], with an upper bound /// on the max size of all txs in a block defined by Tendermint. #[inline] pub fn init(tendermint_max_block_space_in_bytes: u64) -> Self { @@ -98,8 +98,8 @@ impl TxAllottedSpace { } } -impl states::State for TxAllottedSpace { - // TODO: change to `TxAllottedSpace` +impl states::State for BlockSpaceAllocator { + // TODO: change to `BlockSpaceAllocator` type Next = (); #[inline] @@ -127,7 +127,7 @@ impl states::State for TxAllottedSpace { // } = self; // // TODO: reserve space for protocol txs // protocol_txs.allotted_space_in_bytes = 0; - // TxAllottedSpace { + // BlockSpaceAllocator { // _state: PhantomData, // max_block_space_in_bytes, // protocol_txs, @@ -140,10 +140,10 @@ impl states::State for TxAllottedSpace { } // WIP -impl TxAllottedSpace { +impl BlockSpaceAllocator { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// - /// This method should not be used outside of [`TxAllottedSpace`] + /// This method should not be used outside of [`BlockSpaceAllocator`] /// instance construction or unit testing. #[allow(dead_code)] fn uninitialized_space_in_bytes(&self) -> u64 { @@ -162,13 +162,13 @@ impl TxAllottedSpace { } /// Return the amount, in bytes, of free space in this - /// [`TxAllottedSpace`]. + /// [`BlockSpaceAllocator`]. #[inline] pub fn free_space_in_bytes(&self) -> u64 { self.max_block_space_in_bytes - self.occupied_space_in_bytes() } - /// Checks if this [`TxAllottedSpace`] has any free space remaining. + /// Checks if this [`BlockSpaceAllocator`] has any free space remaining. #[allow(dead_code)] #[inline] pub fn has_free_space(&self) -> bool { @@ -180,7 +180,7 @@ impl TxAllottedSpace { // be shunned to this impl block -- shame! // // WIP -impl TxAllottedSpace { +impl BlockSpaceAllocator { /// Try to allocate space for a new protocol transaction. #[allow(dead_code)] #[inline] @@ -323,7 +323,7 @@ mod thres { // hacky workaround to get module docstrings formatted properly #[rustfmt::skip] mod states { - //! All the states of the [`TxAllottedSpace`] state machine, + //! All the states of the [`BlockSpaceAllocator`] state machine, //! over the extent of a Tendermint consensus round //! block proposal. //! @@ -349,7 +349,7 @@ mod states { //! same two modes of operation defined above. #[allow(unused_imports)] - use super::TxAllottedSpace; + use super::BlockSpaceAllocator; #[doc(inline)] pub use super::states_impl::*; @@ -360,7 +360,7 @@ mod states_impl { use super::AllocStatus; #[allow(unused_imports)] - use super::TxAllottedSpace; + use super::BlockSpaceAllocator; /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. @@ -434,12 +434,12 @@ mod states_impl { } } - /// Represents a state in the [`TxAllottedSpace`] state machine. + /// Represents a state in the [`BlockSpaceAllocator`] state machine. /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. pub trait State { - /// The next state in the [`TxAllottedSpace`] state machine. + /// The next state in the [`BlockSpaceAllocator`] state machine. type Next: State; /// Try to allocate space for a new transaction. @@ -450,7 +450,7 @@ mod states_impl { where T: IntoIterator + 'tx; - /// Transition to the next state in the [`TxAllottedSpace`] state + /// Transition to the next state in the [`BlockSpaceAllocator`] state /// machine. fn next_state(self) -> Self::Next; } @@ -490,14 +490,14 @@ mod states_impl { // // proptest! { // /// Check if we reject a tx when its respective bin -// /// capacity has been reached on a [`TxAllottedSpace`]. +// /// capacity has been reached on a [`BlockSpaceAllocator`]. // #[test] // fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { // proptest_reject_tx_on_bin_cap_reached(max) // } // // /// Check if the sum of all individual bin allotments for a -// /// [`TxAllottedSpace`] corresponds to the total space ceded +// /// [`BlockSpaceAllocator`] corresponds to the total space ceded // /// by Tendermint. // #[test] // fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { @@ -517,7 +517,7 @@ mod states_impl { // tendermint_max_block_space_in_bytes: u64, // ) { // let mut bins = -// TxAllottedSpace::init(tendermint_max_block_space_in_bytes); +// BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // // // fill the entire bin of decrypted txs // bins.decrypted_txs.current_space_in_bytes = @@ -534,7 +534,7 @@ mod states_impl { // fn proptest_bin_capacity_eq_provided_space( // tendermint_max_block_space_in_bytes: u64, // ) { -// let bins = TxAllottedSpace::init(tendermint_max_block_space_in_bytes); +// let bins = BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // assert_eq!(0, bins.uninitialized_space_in_bytes()); // } // @@ -546,7 +546,7 @@ mod states_impl { // encrypted_txs, // decrypted_txs, // } = args; -// let bins = RefCell::new(TxAllottedSpace::init( +// let bins = RefCell::new(BlockSpaceAllocator::init( // tendermint_max_block_space_in_bytes, // )); // @@ -617,7 +617,7 @@ mod states_impl { // } // } // -// /// Return random bin sizes for a [`TxAllottedSpace`]. +// /// Return random bin sizes for a [`BlockSpaceAllocator`]. // fn arb_max_bin_sizes() -> impl Strategy // { // const MAX_BLOCK_SIZE_BYTES: u64 = 1000; From 0c26aece455f70dc81eaa87aa51dbf02e03cf1ae Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:14:25 +0000 Subject: [PATCH 1581/1995] Uncomment next_state() code --- .../ledger/shell/prepare_proposal/tx_bins.rs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index bf9f845832..141becf5ae 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -117,25 +117,23 @@ impl states::State for BlockSpaceAllocator { #[inline] fn next_state(self) -> Self::Next { - // ```ignore - // let Self { - // max_block_space_in_bytes, - // mut protocol_txs, - // encrypted_txs, - // decrypted_txs, - // .. - // } = self; - // // TODO: reserve space for protocol txs - // protocol_txs.allotted_space_in_bytes = 0; - // BlockSpaceAllocator { - // _state: PhantomData, - // max_block_space_in_bytes, - // protocol_txs, - // encrypted_txs, - // decrypted_txs, - // } - // ``` - todo!() + let Self { + max_block_space_in_bytes, + mut protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + // TODO: reserve space for protocol txs + protocol_txs.allotted_space_in_bytes = 0; + let _alloc: BlockSpaceAllocator = + BlockSpaceAllocator { + _state: PhantomData, + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + }; } } From ef58cea22b843bf8b47d393e03d1ce52a4116e4a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:16:03 +0000 Subject: [PATCH 1582/1995] Make states mod public --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 141becf5ae..3eacb0f056 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -33,8 +33,6 @@ use std::marker::PhantomData; use num_rational::Ratio; -#[doc(inline)] -pub use self::states::State; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// All status responses from trying to allocate block space for a tx. @@ -320,7 +318,7 @@ mod thres { // hacky workaround to get module docstrings formatted properly #[rustfmt::skip] -mod states { +pub mod states { //! All the states of the [`BlockSpaceAllocator`] state machine, //! over the extent of a Tendermint consensus round //! block proposal. From b22c188e5d7a7c009864860de1a26d8c2f8f1324 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:43:07 +0000 Subject: [PATCH 1583/1995] Impl new state --- .../ledger/shell/prepare_proposal/tx_bins.rs | 86 +++++++++++++++---- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 3eacb0f056..ce0d4364fb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -55,7 +55,7 @@ pub enum AllocStatus { /// - Protocol transactions. /// - DKG decrypted transactions. /// - DKG encrypted transactions. -#[derive(Default)] +#[derive(Debug, Default)] pub struct BlockSpaceAllocator { /// The current state of the [`BlockSpaceAllocator`] state machine. _state: PhantomData<*const State>, @@ -91,14 +91,17 @@ impl BlockSpaceAllocator { max_block_space_in_bytes: max, protocol_txs: TxBin::default(), encrypted_txs: TxBin::default(), + // decrypted txs can use as much space as needed; in practice, + // we'll only need, at most, the amount of space reserved for + // encrypted txs at the prev block height decrypted_txs: TxBin::init(max), } } } impl states::State for BlockSpaceAllocator { - // TODO: change to `BlockSpaceAllocator` - type Next = (); + type Next = BlockSpaceAllocator; + type Transition = (); #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { @@ -114,24 +117,62 @@ impl states::State for BlockSpaceAllocator { } #[inline] - fn next_state(self) -> Self::Next { + fn next_state(mut self, _: ()) -> Self::Next { + // seal decrypted txs + self.decrypted_txs.allotted_space_in_bytes = + self.decrypted_txs.current_space_in_bytes; + + // reserve space for protocol txs + // + // TODO: need to use some kind of ratio here, to constrain the aomunt of + // protocol txs in a block + let free_space = self.free_space_in_bytes(); + self.protocol_txs.allotted_space_in_bytes = free_space; + + // cast state let Self { max_block_space_in_bytes, - mut protocol_txs, + protocol_txs, encrypted_txs, decrypted_txs, .. } = self; - // TODO: reserve space for protocol txs - protocol_txs.allotted_space_in_bytes = 0; - let _alloc: BlockSpaceAllocator = - BlockSpaceAllocator { - _state: PhantomData, - max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - }; + + BlockSpaceAllocator { + _state: PhantomData, + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} + +impl states::State for BlockSpaceAllocator { + // TODO: change to + // Either>, BlockSpaceAllocator>> + type Next = (); + // TODO: change to enum, for readability + type Transition = bool; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.protocol_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.protocol_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self, _with_encrypted: bool) -> Self::Next { + todo!() } } @@ -219,7 +260,7 @@ impl BlockSpaceAllocator { /// Allotted space for a batch of transactions of the same kind in some /// proposed block, measured in bytes. -#[derive(Copy, Clone, Default)] +#[derive(Debug, Copy, Clone, Default)] struct TxBin { /// The current space utilized by the batch of transactions. current_space_in_bytes: u64, @@ -413,6 +454,7 @@ mod states_impl { impl State for () { type Next = (); + type Transition = (); fn try_alloc(&mut self, _: &[u8]) -> AllocStatus { panic!("Can't do anything in the empty state"); @@ -425,7 +467,7 @@ mod states_impl { panic!("Can't do anything in the empty state"); } - fn next_state(self) -> Self::Next { + fn next_state(self, _: ()) -> Self::Next { panic!("Can't do anything in the empty state"); } } @@ -434,10 +476,15 @@ mod states_impl { /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. + // TODO: change to `State` pub trait State { /// The next state in the [`BlockSpaceAllocator`] state machine. + // TODO: change to `type Next: State` with GATs type Next: State; + /// The transition function for some [`BlockSpaceAllocator`] state. + type Transition; + /// Try to allocate space for a new transaction. fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; @@ -448,7 +495,10 @@ mod states_impl { /// Transition to the next state in the [`BlockSpaceAllocator`] state /// machine. - fn next_state(self) -> Self::Next; + fn next_state( + self, + transition_function: Self::Transition, + ) -> Self::Next; } } From 0126231596c4fb073a1440975dfdff9fb88d0f82 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 14 Nov 2022 17:46:02 +0000 Subject: [PATCH 1584/1995] Remove unused code --- .../ledger/shell/prepare_proposal/tx_bins.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index ce0d4364fb..2320e5fd5c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -218,25 +218,6 @@ impl BlockSpaceAllocator { // // WIP impl BlockSpaceAllocator { - /// Try to allocate space for a new protocol transaction. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_protocol_tx(&mut self, tx: &[u8]) -> AllocStatus { - self.protocol_txs.try_dump(tx) - } - - /// Try to allocate space for a new batch of protocol transactions. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_protocol_tx_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.protocol_txs.try_dump_all(txs) - } - - // --------------------------------------------------- // - /// Try to allocate space for a new DKG encrypted transaction. #[allow(dead_code)] #[inline] From a18fd6b877c55f709277add05ff211b3a0781a8d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 09:10:57 +0000 Subject: [PATCH 1585/1995] Properly initialize space for protocol txs --- .../ledger/shell/prepare_proposal/tx_bins.rs | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 2320e5fd5c..1cd4ed08b7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -123,11 +123,8 @@ impl states::State for BlockSpaceAllocator { self.decrypted_txs.current_space_in_bytes; // reserve space for protocol txs - // - // TODO: need to use some kind of ratio here, to constrain the aomunt of - // protocol txs in a block - let free_space = self.free_space_in_bytes(); - self.protocol_txs.allotted_space_in_bytes = free_space; + let uninit = self.uninitialized_space_in_bytes(); + self.protocol_txs = TxBin::init_over_ratio(uninit, thres::ONE_THIRD); // cast state let Self { @@ -319,23 +316,9 @@ mod thres { use num_rational::Ratio; - /// The threshold over Tendermint's allotted space for protocol txs. - #[allow(dead_code)] - pub const PROTOCOL_TX: Ratio = Ratio::new_raw(1, 3); - - /// The threshold over Tendermint's allotted space for DKG encrypted txs. - #[allow(dead_code)] - pub const ENCRYPTED_TX: Ratio = Ratio::new_raw(1, 3); - - /// The threshold over Tendermint's allotted space for DKG decrypted txs. - /// - /// This value should always be the same as [`ENCRYPTED_TX`]. - /// The reason for which is that during the decision process of - /// block height `H`, we must include the same number of decrypted - /// txs as the number of encrypted txs proposed during block height - /// `H - 1`. - #[allow(dead_code)] - pub const DECRYPTED_TX: Ratio = ENCRYPTED_TX; + /// The threshold over Tendermint's allotted space for all three + /// (major) kinds of Namada transations. + pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); } // hacky workaround to get module docstrings formatted properly From 2ae93be6da9fd20c99f6f621b7676b396a73b178 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 09:19:58 +0000 Subject: [PATCH 1586/1995] Add NextState --- .../ledger/shell/prepare_proposal/tx_bins.rs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 1cd4ed08b7..84daa77ae9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -117,7 +117,7 @@ impl states::State for BlockSpaceAllocator { } #[inline] - fn next_state(mut self, _: ()) -> Self::Next { + fn next_state_over(mut self, _: ()) -> Self::Next { // seal decrypted txs self.decrypted_txs.allotted_space_in_bytes = self.decrypted_txs.current_space_in_bytes; @@ -168,7 +168,7 @@ impl states::State for BlockSpaceAllocator { } #[inline] - fn next_state(self, _with_encrypted: bool) -> Self::Next { + fn next_state_over(self, _with_encrypted: bool) -> Self::Next { todo!() } } @@ -431,7 +431,7 @@ mod states_impl { panic!("Can't do anything in the empty state"); } - fn next_state(self, _: ()) -> Self::Next { + fn next_state_over(self, _: ()) -> Self::Next { panic!("Can't do anything in the empty state"); } } @@ -440,10 +440,8 @@ mod states_impl { /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - // TODO: change to `State` pub trait State { /// The next state in the [`BlockSpaceAllocator`] state machine. - // TODO: change to `type Next: State` with GATs type Next: State; /// The transition function for some [`BlockSpaceAllocator`] state. @@ -459,11 +457,24 @@ mod states_impl { /// Transition to the next state in the [`BlockSpaceAllocator`] state /// machine. - fn next_state( + fn next_state_over( self, transition_function: Self::Transition, ) -> Self::Next; } + + /// Convenience extension of [`State`]. + pub trait NextState: State { + /// Transition to the next state in the [`BlockSpaceAllocator`] state + /// machine, for states whose transition function is the unit value. + #[inline] + fn next_state(self) -> Self::Next + where + Self: Sized, + { + self.next_state_over(()) + } + } } // ```ignore From 51cea1f0d770bebb44aae637f489920e7898ad11 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:01:58 +0000 Subject: [PATCH 1587/1995] Refactor State --- .../ledger/shell/prepare_proposal/tx_bins.rs | 90 ++++++++----------- 1 file changed, 37 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 84daa77ae9..d485a275ab 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -101,7 +101,6 @@ impl BlockSpaceAllocator { impl states::State for BlockSpaceAllocator { type Next = BlockSpaceAllocator; - type Transition = (); #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { @@ -117,7 +116,7 @@ impl states::State for BlockSpaceAllocator { } #[inline] - fn next_state_over(mut self, _: ()) -> Self::Next { + fn next_state(mut self) -> Self::Next { // seal decrypted txs self.decrypted_txs.allotted_space_in_bytes = self.decrypted_txs.current_space_in_bytes; @@ -145,14 +144,38 @@ impl states::State for BlockSpaceAllocator { } } -impl states::State for BlockSpaceAllocator { - // TODO: change to - // Either>, BlockSpaceAllocator>> - type Next = (); - // TODO: change to enum, for readability - type Transition = bool; +impl states::State + for BlockSpaceAllocator +{ + type Next = BlockSpaceAllocator< + states::BuildingEncryptedTxBatch, + >; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.protocol_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.protocol_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} + +impl states::State + for BlockSpaceAllocator +{ + type Next = BlockSpaceAllocator< + states::BuildingEncryptedTxBatch, + >; #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { @@ -168,7 +191,7 @@ impl states::State for BlockSpaceAllocator { } #[inline] - fn next_state_over(self, _with_encrypted: bool) -> Self::Next { + fn next_state(self) -> Self::Next { todo!() } } @@ -416,36 +439,13 @@ mod states_impl { #[allow(dead_code)] pub enum WithoutEncryptedTxs {} - impl State for () { - type Next = (); - type Transition = (); - - fn try_alloc(&mut self, _: &[u8]) -> AllocStatus { - panic!("Can't do anything in the empty state"); - } - - fn try_alloc_batch<'tx, T>(&mut self, _: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - panic!("Can't do anything in the empty state"); - } - - fn next_state_over(self, _: ()) -> Self::Next { - panic!("Can't do anything in the empty state"); - } - } - /// Represents a state in the [`BlockSpaceAllocator`] state machine. /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - pub trait State { + pub trait State { /// The next state in the [`BlockSpaceAllocator`] state machine. - type Next: State; - - /// The transition function for some [`BlockSpaceAllocator`] state. - type Transition; + type Next; /// Try to allocate space for a new transaction. fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; @@ -457,23 +457,7 @@ mod states_impl { /// Transition to the next state in the [`BlockSpaceAllocator`] state /// machine. - fn next_state_over( - self, - transition_function: Self::Transition, - ) -> Self::Next; - } - - /// Convenience extension of [`State`]. - pub trait NextState: State { - /// Transition to the next state in the [`BlockSpaceAllocator`] state - /// machine, for states whose transition function is the unit value. - #[inline] - fn next_state(self) -> Self::Next - where - Self: Sized, - { - self.next_state_over(()) - } + fn next_state(self) -> Self::Next; } } From 8a41254348b7ab78be5fb56454572c54deff7da4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:06:23 +0000 Subject: [PATCH 1588/1995] Add convenience traits --- .../ledger/shell/prepare_proposal/tx_bins.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index d485a275ab..95d7177f10 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -459,6 +459,34 @@ mod states_impl { /// machine. fn next_state(self) -> Self::Next; } + + /// Convenience extension of [`State`], to transition to a new + /// state with encrypted txs in a block. + pub trait StateWithEncryptedTxs: State { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we include encrypted txs in a block. + #[inline] + fn next_state_with_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state() + } + } + + /// Convenience extension of [`State`], to transition to a new + /// state without encrypted txs in a block. + pub trait StateWithoutEncryptedTxs: State { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we do not include encrypted txs in a block. + #[inline] + fn next_state_without_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state() + } + } } // ```ignore From 3aad53085b7574f51b5d899106b15eb75a366098 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:13:29 +0000 Subject: [PATCH 1589/1995] State extension impl --- apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 95d7177f10..6bc42fddfb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -196,7 +196,6 @@ impl states::State } } -// WIP impl BlockSpaceAllocator { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// @@ -474,6 +473,8 @@ mod states_impl { } } + impl StateWithEncryptedTxs for S where S: State {} + /// Convenience extension of [`State`], to transition to a new /// state without encrypted txs in a block. pub trait StateWithoutEncryptedTxs: State { @@ -487,6 +488,8 @@ mod states_impl { self.next_state() } } + + impl StateWithoutEncryptedTxs for S where S: State {} } // ```ignore From 3b417167d9e4c310d09451e1bb0b1040e5d42287 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:19:54 +0000 Subject: [PATCH 1590/1995] WIP: Encrypted tx batches --- .../ledger/shell/prepare_proposal/tx_bins.rs | 82 +++++++++++++------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index 6bc42fddfb..cd0adfa3a6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -196,6 +196,62 @@ impl states::State } } +impl states::State + for BlockSpaceAllocator< + states::BuildingEncryptedTxBatch, + > +{ + type Next = BlockSpaceAllocator< + states::FillingRemainingSpace, + >; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.encrypted_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.encrypted_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} + +impl states::State + for BlockSpaceAllocator< + states::BuildingEncryptedTxBatch, + > +{ + type Next = BlockSpaceAllocator< + states::FillingRemainingSpace, + >; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.encrypted_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.encrypted_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} + impl BlockSpaceAllocator { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// @@ -232,32 +288,6 @@ impl BlockSpaceAllocator { } } -// all allocation boilerplate code shall -// be shunned to this impl block -- shame! -// -// WIP -impl BlockSpaceAllocator { - /// Try to allocate space for a new DKG encrypted transaction. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_encrypted_tx(&mut self, tx: &[u8]) -> AllocStatus { - self.encrypted_txs.try_dump(tx) - } - - /// Try to allocate space for a new batch of DKG encrypted transactions. - #[allow(dead_code)] - #[inline] - pub fn try_alloc_encrypted_tx_batch<'tx, T>( - &mut self, - txs: T, - ) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.encrypted_txs.try_dump_all(txs) - } -} - /// Allotted space for a batch of transactions of the same kind in some /// proposed block, measured in bytes. #[derive(Debug, Copy, Clone, Default)] From aff268e1f65b7ba5f2332f35e69c97256666aeeb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:23:00 +0000 Subject: [PATCH 1591/1995] WIP: Filling remaining space in block --- .../ledger/shell/prepare_proposal/tx_bins.rs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs index cd0adfa3a6..5f96931576 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs @@ -252,6 +252,58 @@ impl states::State } } +impl states::State + for BlockSpaceAllocator< + states::FillingRemainingSpace, + > +{ + type Next = (); + + #[inline] + fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { + todo!() + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + todo!() + } + + #[inline] + fn next_state(self) -> Self::Next { + // NOOP + } +} + +impl states::State + for BlockSpaceAllocator< + states::FillingRemainingSpace, + > +{ + type Next = (); + + #[inline] + fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { + todo!() + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + todo!() + } + + #[inline] + fn next_state(self) -> Self::Next { + // NOOP + } +} + impl BlockSpaceAllocator { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// From 92a386e7b5d41512735ef87a8c240ec6c8e1a9e7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:26:07 +0000 Subject: [PATCH 1592/1995] Choose a wiser mod name for block space allocation --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- .../shell/prepare_proposal/{tx_bins.rs => block_space_alloc.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename apps/src/lib/node/ledger/shell/prepare_proposal/{tx_bins.rs => block_space_alloc.rs} (100%) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0e7ac9828f..5a6542e084 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,6 +1,6 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -mod tx_bins; +mod block_space_alloc; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs similarity index 100% rename from apps/src/lib/node/ledger/shell/prepare_proposal/tx_bins.rs rename to apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs From ea14fe4fd8f95a6a52f6be799769cd2a429c545c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:36:03 +0000 Subject: [PATCH 1593/1995] Temporarily remove tests mod --- .../prepare_proposal/block_space_alloc.rs | 189 ------------------ 1 file changed, 189 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 5f96931576..982c34586a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -573,192 +573,3 @@ mod states_impl { impl StateWithoutEncryptedTxs for S where S: State {} } - -// ```ignore -// #[cfg(test)] -// mod tests { -// use std::cell::RefCell; -// -// use proptest::prelude::*; -// -// use super::*; -// use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; -// -// /// Proptest generated txs. -// #[derive(Debug)] -// struct PropTx { -// tendermint_max_block_space_in_bytes: u64, -// protocol_txs: Vec, -// encrypted_txs: Vec, -// decrypted_txs: Vec, -// } -// -// /// Check if the sum of all individual tx thresholds does -// /// not exceed one. -// /// -// /// This is important, because we do not want to exceed -// /// the maximum block size in Tendermint, and get randomly -// /// rejected blocks. -// #[test] -// fn test_tx_thres_doesnt_exceed_one() { -// let sum = -// thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; -// assert_eq!(sum.to_integer(), 1); -// } -// -// proptest! { -// /// Check if we reject a tx when its respective bin -// /// capacity has been reached on a [`BlockSpaceAllocator`]. -// #[test] -// fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { -// proptest_reject_tx_on_bin_cap_reached(max) -// } -// -// /// Check if the sum of all individual bin allotments for a -// /// [`BlockSpaceAllocator`] corresponds to the total space ceded -// /// by Tendermint. -// #[test] -// fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { -// proptest_bin_capacity_eq_provided_space(max) -// } -// -// /// Test that dumping txs whose total combined size -// /// is less than the bin cap does not fill up the bin. -// #[test] -// fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { -// proptest_tx_dump_doesnt_fill_up_bin(args) -// } -// } -// -// /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. -// fn proptest_reject_tx_on_bin_cap_reached( -// tendermint_max_block_space_in_bytes: u64, -// ) { -// let mut bins = -// BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); -// -// // fill the entire bin of decrypted txs -// bins.decrypted_txs.current_space_in_bytes = -// bins.decrypted_txs.allotted_space_in_bytes; -// -// // make sure we can't dump any new decrypted txs in the bin -// assert_eq!( -// bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), -// AllocStatus::Rejected -// ); -// } -// -// /// Implementation of [`test_bin_capacity_eq_provided_space`]. -// fn proptest_bin_capacity_eq_provided_space( -// tendermint_max_block_space_in_bytes: u64, -// ) { -// let bins = BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); -// assert_eq!(0, bins.uninitialized_space_in_bytes()); -// } -// -// /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. -// fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { -// let PropTx { -// tendermint_max_block_space_in_bytes, -// protocol_txs, -// encrypted_txs, -// decrypted_txs, -// } = args; -// let bins = RefCell::new(BlockSpaceAllocator::init( -// tendermint_max_block_space_in_bytes, -// )); -// -// // produce new txs until we fill up the bins -// // -// // TODO: ideally the proptest strategy would already return -// // txs whose total added size would be bounded -// let protocol_txs = protocol_txs.into_iter().take_while(|tx| { -// let bin = bins.borrow().protocol_txs; -// let new_size = bin.current_space_in_bytes + tx.len() as u64; -// new_size < bin.allotted_space_in_bytes -// }); -// let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { -// let bin = bins.borrow().encrypted_txs; -// let new_size = bin.current_space_in_bytes + tx.len() as u64; -// new_size < bin.allotted_space_in_bytes -// }); -// let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { -// let bin = bins.borrow().decrypted_txs; -// let new_size = bin.current_space_in_bytes + tx.len() as u64; -// new_size < bin.allotted_space_in_bytes -// }); -// -// // make sure we can keep dumping txs, -// // without filling up the bins -// for tx in protocol_txs { -// assert_eq!( -// bins.borrow_mut().try_alloc_protocol_tx(&tx), -// AllocStatus::Accepted -// ); -// } -// for tx in encrypted_txs { -// assert_eq!( -// bins.borrow_mut().try_alloc_encrypted_tx(&tx), -// AllocStatus::Accepted -// ); -// } -// for tx in decrypted_txs { -// assert_eq!( -// bins.borrow_mut().try_alloc_decrypted_tx(&tx), -// AllocStatus::Accepted -// ); -// } -// } -// -// prop_compose! { -// /// Generate arbitrarily sized txs of different kinds. -// fn arb_transactions() -// // create base strategies -// ( -// (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, -// decrypted_tx_max_bin_size) in arb_max_bin_sizes(), -// ) -// // compose strategies -// ( -// tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), -// protocol_txs in arb_tx_list(protocol_tx_max_bin_size), -// encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), -// decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), -// ) -// -> PropTx { -// PropTx { -// tendermint_max_block_space_in_bytes, -// protocol_txs, -// encrypted_txs, -// decrypted_txs, -// } -// } -// } -// -// /// Return random bin sizes for a [`BlockSpaceAllocator`]. -// fn arb_max_bin_sizes() -> impl Strategy -// { -// const MAX_BLOCK_SIZE_BYTES: u64 = 1000; -// (1..=MAX_BLOCK_SIZE_BYTES).prop_map( -// |tendermint_max_block_space_in_bytes| { -// ( -// tendermint_max_block_space_in_bytes, -// (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) -// .to_integer() as usize, -// (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) -// .to_integer() as usize, -// (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) -// .to_integer() as usize, -// ) -// }, -// ) -// } -// -// /// Return a list of txs. -// fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { -// const MAX_TX_NUM: usize = 64; -// let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); -// prop::collection::vec(tx, 0..=MAX_TX_NUM) -// } -// } -// ``` From fe294857d22b065909b5761e4fb88e18b5ae8058 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:38:30 +0000 Subject: [PATCH 1594/1995] Fix rustfmt acting up --- .../prepare_proposal/block_space_alloc.rs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 982c34586a..a3fae26fc0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -425,8 +425,6 @@ mod thres { pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); } -// hacky workaround to get module docstrings formatted properly -#[rustfmt::skip] pub mod states { //! All the states of the [`BlockSpaceAllocator`] state machine, //! over the extent of a Tendermint consensus round @@ -444,25 +442,15 @@ pub mod states { //! this state, we populate a block with DKG encrypted txs. //! This state supports two modes of operation, which you can //! think of as two states diverging from [`BuildingProtocolTxBatch`]: - //! 1. [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs - //! are included in a block proposal. - //! 2. [`WithEncryptedTxs`] - When this mode is active, we are able - //! to include encrypted txs in a block proposal. + //! * [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs + //! are included in a block proposal. + //! * [`WithEncryptedTxs`] - When this mode is active, we are able to + //! include encrypted txs in a block proposal. //! 4. [`FillingRemainingSpace`] - the fourth and final state. //! During this phase, we fill all remaining block space with arbitrary //! transactions that haven't been included yet. This state supports the //! same two modes of operation defined above. - #[allow(unused_imports)] - use super::BlockSpaceAllocator; - - #[doc(inline)] - pub use super::states_impl::*; -} - -mod states_impl { - //! Implements [`super::states`]. - use super::AllocStatus; #[allow(unused_imports)] use super::BlockSpaceAllocator; From be69a87732a5726bb155ab14c1d4449930306d72 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:43:44 +0000 Subject: [PATCH 1595/1995] Move states mod to a new file --- .../prepare_proposal/block_space_alloc.rs | 139 +----------------- .../block_space_alloc/states.rs | 134 +++++++++++++++++ 2 files changed, 136 insertions(+), 137 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index a3fae26fc0..f0ff79d097 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -9,6 +9,8 @@ //! Namada gets a portion of (i.e. threshold over) the total //! allotted space. +pub mod states; + // TODO: what if a tx has a size greater than the threshold for // its bin? how do we handle this? if we keep it in the mempool // forever, it'll be a DoS vec, as we can make nodes run out of @@ -424,140 +426,3 @@ mod thres { /// (major) kinds of Namada transations. pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); } - -pub mod states { - //! All the states of the [`BlockSpaceAllocator`] state machine, - //! over the extent of a Tendermint consensus round - //! block proposal. - //! - //! # States - //! - //! The state machine moves through the following states: - //! - //! 1. [`BuildingDecryptedTxBatch`] - the initial state. In - //! this state, we populate a block with DKG decrypted txs. - //! 2. [`BuildingProtocolTxBatch`] - the second state. In - //! this state, we populate a block with protocol txs. - //! 3. [`BuildingEncryptedTxBatch`] - the third state. In - //! this state, we populate a block with DKG encrypted txs. - //! This state supports two modes of operation, which you can - //! think of as two states diverging from [`BuildingProtocolTxBatch`]: - //! * [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs - //! are included in a block proposal. - //! * [`WithEncryptedTxs`] - When this mode is active, we are able to - //! include encrypted txs in a block proposal. - //! 4. [`FillingRemainingSpace`] - the fourth and final state. - //! During this phase, we fill all remaining block space with arbitrary - //! transactions that haven't been included yet. This state supports the - //! same two modes of operation defined above. - - use super::AllocStatus; - #[allow(unused_imports)] - use super::BlockSpaceAllocator; - - /// The leader of the current Tendermint round is building - /// a new batch of DKG decrypted transactions. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub enum BuildingDecryptedTxBatch {} - - /// The leader of the current Tendermint round is building - /// a new batch of Namada protocol transactions. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub enum BuildingProtocolTxBatch {} - - /// The leader of the current Tendermint round is building - /// a new batch of DKG encrypted transactions. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub struct BuildingEncryptedTxBatch { - /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. - _mode: Mode, - } - - /// The leader of the current Tendermint round is populating - /// all remaining space in a block proposal with arbitrary - /// transactions. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub struct FillingRemainingSpace { - /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. - _mode: Mode, - } - - /// Allow block proposals to include encrypted txs. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub enum WithEncryptedTxs {} - - /// Prohibit block proposals from including encrypted txs. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - #[allow(dead_code)] - pub enum WithoutEncryptedTxs {} - - /// Represents a state in the [`BlockSpaceAllocator`] state machine. - /// - /// For more info, read the module docs of - /// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. - pub trait State { - /// The next state in the [`BlockSpaceAllocator`] state machine. - type Next; - - /// Try to allocate space for a new transaction. - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; - - /// Try to allocate space for a new batch of transactions. - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx; - - /// Transition to the next state in the [`BlockSpaceAllocator`] state - /// machine. - fn next_state(self) -> Self::Next; - } - - /// Convenience extension of [`State`], to transition to a new - /// state with encrypted txs in a block. - pub trait StateWithEncryptedTxs: State { - /// Transition to the next state in the [`BlockSpaceAllocator`] state, - /// ensuring we include encrypted txs in a block. - #[inline] - fn next_state_with_encrypted_txs(self) -> Self::Next - where - Self: Sized, - { - self.next_state() - } - } - - impl StateWithEncryptedTxs for S where S: State {} - - /// Convenience extension of [`State`], to transition to a new - /// state without encrypted txs in a block. - pub trait StateWithoutEncryptedTxs: State { - /// Transition to the next state in the [`BlockSpaceAllocator`] state, - /// ensuring we do not include encrypted txs in a block. - #[inline] - fn next_state_without_encrypted_txs(self) -> Self::Next - where - Self: Sized, - { - self.next_state() - } - } - - impl StateWithoutEncryptedTxs for S where S: State {} -} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs new file mode 100644 index 0000000000..aa7ba56db1 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -0,0 +1,134 @@ +//! All the states of the [`BlockSpaceAllocator`] state machine, +//! over the extent of a Tendermint consensus round +//! block proposal. +//! +//! # States +//! +//! The state machine moves through the following states: +//! +//! 1. [`BuildingDecryptedTxBatch`] - the initial state. In +//! this state, we populate a block with DKG decrypted txs. +//! 2. [`BuildingProtocolTxBatch`] - the second state. In +//! this state, we populate a block with protocol txs. +//! 3. [`BuildingEncryptedTxBatch`] - the third state. In +//! this state, we populate a block with DKG encrypted txs. +//! This state supports two modes of operation, which you can +//! think of as two states diverging from [`BuildingProtocolTxBatch`]: +//! * [`WithoutEncryptedTxs`] - When this mode is active, no encrypted txs are +//! included in a block proposal. +//! * [`WithEncryptedTxs`] - When this mode is active, we are able to include +//! encrypted txs in a block proposal. +//! 4. [`FillingRemainingSpace`] - the fourth and final state. +//! During this phase, we fill all remaining block space with arbitrary +//! transactions that haven't been included yet. This state supports the +//! same two modes of operation defined above. + +use super::AllocStatus; +#[allow(unused_imports)] +use super::BlockSpaceAllocator; + +/// The leader of the current Tendermint round is building +/// a new batch of DKG decrypted transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub enum BuildingDecryptedTxBatch {} + +/// The leader of the current Tendermint round is building +/// a new batch of Namada protocol transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub enum BuildingProtocolTxBatch {} + +/// The leader of the current Tendermint round is building +/// a new batch of DKG encrypted transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub struct BuildingEncryptedTxBatch { + /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. + _mode: Mode, +} + +/// The leader of the current Tendermint round is populating +/// all remaining space in a block proposal with arbitrary +/// transactions. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub struct FillingRemainingSpace { + /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. + _mode: Mode, +} + +/// Allow block proposals to include encrypted txs. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub enum WithEncryptedTxs {} + +/// Prohibit block proposals from including encrypted txs. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +#[allow(dead_code)] +pub enum WithoutEncryptedTxs {} + +/// Represents a state in the [`BlockSpaceAllocator`] state machine. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +pub trait State { + /// The next state in the [`BlockSpaceAllocator`] state machine. + type Next; + + /// Try to allocate space for a new transaction. + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; + + /// Try to allocate space for a new batch of transactions. + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx; + + /// Transition to the next state in the [`BlockSpaceAllocator`] state + /// machine. + fn next_state(self) -> Self::Next; +} + +/// Convenience extension of [`State`], to transition to a new +/// state with encrypted txs in a block. +pub trait StateWithEncryptedTxs: State { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we include encrypted txs in a block. + #[inline] + fn next_state_with_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state() + } +} + +impl StateWithEncryptedTxs for S where S: State {} + +/// Convenience extension of [`State`], to transition to a new +/// state without encrypted txs in a block. +pub trait StateWithoutEncryptedTxs: State { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// ensuring we do not include encrypted txs in a block. + #[inline] + fn next_state_without_encrypted_txs(self) -> Self::Next + where + Self: Sized, + { + self.next_state() + } +} + +impl StateWithoutEncryptedTxs for S where S: State {} From c01a314bcc9c111ea5534d042a903dc05f8c51a3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:49:53 +0000 Subject: [PATCH 1596/1995] Fix docstrings referencing outdated mod --- .../prepare_proposal/block_space_alloc/states.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index aa7ba56db1..1c5ffb4024 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -31,7 +31,7 @@ use super::BlockSpaceAllocator; /// a new batch of DKG decrypted transactions. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub enum BuildingDecryptedTxBatch {} @@ -39,7 +39,7 @@ pub enum BuildingDecryptedTxBatch {} /// a new batch of Namada protocol transactions. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub enum BuildingProtocolTxBatch {} @@ -47,7 +47,7 @@ pub enum BuildingProtocolTxBatch {} /// a new batch of DKG encrypted transactions. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub struct BuildingEncryptedTxBatch { /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. @@ -59,7 +59,7 @@ pub struct BuildingEncryptedTxBatch { /// transactions. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub struct FillingRemainingSpace { /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. @@ -69,21 +69,21 @@ pub struct FillingRemainingSpace { /// Allow block proposals to include encrypted txs. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub enum WithEncryptedTxs {} /// Prohibit block proposals from including encrypted txs. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. #[allow(dead_code)] pub enum WithoutEncryptedTxs {} /// Represents a state in the [`BlockSpaceAllocator`] state machine. /// /// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::tx_bins::states`]. +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub trait State { /// The next state in the [`BlockSpaceAllocator`] state machine. type Next; From 018f4de8c0e52619266b70c3779dc918a216d2c5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 10:51:13 +0000 Subject: [PATCH 1597/1995] Improve docstr --- .../ledger/shell/prepare_proposal/block_space_alloc/states.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 1c5ffb4024..0766b35507 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -4,7 +4,7 @@ //! //! # States //! -//! The state machine moves through the following states: +//! The state machine moves through the following state tree: //! //! 1. [`BuildingDecryptedTxBatch`] - the initial state. In //! this state, we populate a block with DKG decrypted txs. From 52e40bdd5836a705d0e85d7c1b3a09575d73d21e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 11:09:38 +0000 Subject: [PATCH 1598/1995] Move each state to a new module --- .../prepare_proposal/block_space_alloc.rs | 205 ------------------ .../block_space_alloc/states.rs | 5 + .../block_space_alloc/states/decrypted_txs.rs | 49 +++++ .../block_space_alloc/states/encrypted_txs.rs | 51 +++++ .../block_space_alloc/states/protocol_txs.rs | 52 +++++ .../block_space_alloc/states/remaining_txs.rs | 48 ++++ 6 files changed, 205 insertions(+), 205 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs create mode 100644 apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index f0ff79d097..de061290e7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -101,211 +101,6 @@ impl BlockSpaceAllocator { } } -impl states::State for BlockSpaceAllocator { - type Next = BlockSpaceAllocator; - - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.decrypted_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.decrypted_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(mut self) -> Self::Next { - // seal decrypted txs - self.decrypted_txs.allotted_space_in_bytes = - self.decrypted_txs.current_space_in_bytes; - - // reserve space for protocol txs - let uninit = self.uninitialized_space_in_bytes(); - self.protocol_txs = TxBin::init_over_ratio(uninit, thres::ONE_THIRD); - - // cast state - let Self { - max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - .. - } = self; - - BlockSpaceAllocator { - _state: PhantomData, - max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } - } -} - -impl states::State - for BlockSpaceAllocator -{ - type Next = BlockSpaceAllocator< - states::BuildingEncryptedTxBatch, - >; - - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.protocol_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.protocol_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(self) -> Self::Next { - todo!() - } -} - -impl states::State - for BlockSpaceAllocator -{ - type Next = BlockSpaceAllocator< - states::BuildingEncryptedTxBatch, - >; - - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.protocol_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.protocol_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(self) -> Self::Next { - todo!() - } -} - -impl states::State - for BlockSpaceAllocator< - states::BuildingEncryptedTxBatch, - > -{ - type Next = BlockSpaceAllocator< - states::FillingRemainingSpace, - >; - - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.encrypted_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.encrypted_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(self) -> Self::Next { - todo!() - } -} - -impl states::State - for BlockSpaceAllocator< - states::BuildingEncryptedTxBatch, - > -{ - type Next = BlockSpaceAllocator< - states::FillingRemainingSpace, - >; - - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.encrypted_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.encrypted_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(self) -> Self::Next { - todo!() - } -} - -impl states::State - for BlockSpaceAllocator< - states::FillingRemainingSpace, - > -{ - type Next = (); - - #[inline] - fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { - todo!() - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - todo!() - } - - #[inline] - fn next_state(self) -> Self::Next { - // NOOP - } -} - -impl states::State - for BlockSpaceAllocator< - states::FillingRemainingSpace, - > -{ - type Next = (); - - #[inline] - fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { - todo!() - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - todo!() - } - - #[inline] - fn next_state(self) -> Self::Next { - // NOOP - } -} - impl BlockSpaceAllocator { /// Return uninitialized space in tx bins, resulting from ratio conversions. /// diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 0766b35507..66c89fcc6f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -23,6 +23,11 @@ //! transactions that haven't been included yet. This state supports the //! same two modes of operation defined above. +mod decrypted_txs; +mod encrypted_txs; +mod protocol_txs; +mod remaining_txs; + use super::AllocStatus; #[allow(unused_imports)] use super::BlockSpaceAllocator; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs new file mode 100644 index 0000000000..ff28b8ad96 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -0,0 +1,49 @@ +use std::marker::PhantomData; + +use super::super::{thres, AllocStatus, BlockSpaceAllocator, TxBin}; +use super::{BuildingDecryptedTxBatch, BuildingProtocolTxBatch, State}; + +impl State for BlockSpaceAllocator { + type Next = BlockSpaceAllocator; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.decrypted_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.decrypted_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(mut self) -> Self::Next { + // seal decrypted txs + self.decrypted_txs.allotted_space_in_bytes = + self.decrypted_txs.current_space_in_bytes; + + // reserve space for protocol txs + let uninit = self.uninitialized_space_in_bytes(); + self.protocol_txs = TxBin::init_over_ratio(uninit, thres::ONE_THIRD); + + // cast state + let Self { + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } +} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs new file mode 100644 index 0000000000..df6018324a --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -0,0 +1,51 @@ +use super::super::{AllocStatus, BlockSpaceAllocator}; +use super::{ + BuildingEncryptedTxBatch, FillingRemainingSpace, State, WithEncryptedTxs, + WithoutEncryptedTxs, +}; + +impl State for BlockSpaceAllocator> { + type Next = BlockSpaceAllocator>; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.encrypted_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.encrypted_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} + +impl State + for BlockSpaceAllocator> +{ + type Next = BlockSpaceAllocator>; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.encrypted_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.encrypted_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs new file mode 100644 index 0000000000..943d6c2633 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -0,0 +1,52 @@ +use super::super::{AllocStatus, BlockSpaceAllocator}; +use super::{ + BuildingEncryptedTxBatch, BuildingProtocolTxBatch, State, WithEncryptedTxs, + WithoutEncryptedTxs, +}; + +impl State for BlockSpaceAllocator { + type Next = BlockSpaceAllocator>; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.protocol_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.protocol_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} + +impl State + for BlockSpaceAllocator +{ + type Next = + BlockSpaceAllocator>; + + #[inline] + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.protocol_txs.try_dump(tx) + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + self.protocol_txs.try_dump_all(txs) + } + + #[inline] + fn next_state(self) -> Self::Next { + todo!() + } +} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs new file mode 100644 index 0000000000..bda6da24cc --- /dev/null +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -0,0 +1,48 @@ +use super::super::{AllocStatus, BlockSpaceAllocator}; +use super::{ + FillingRemainingSpace, State, WithEncryptedTxs, WithoutEncryptedTxs, +}; + +impl State for BlockSpaceAllocator> { + type Next = (); + + #[inline] + fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { + todo!() + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + todo!() + } + + #[inline] + fn next_state(self) -> Self::Next { + // NOOP + } +} + +impl State for BlockSpaceAllocator> { + type Next = (); + + #[inline] + fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { + todo!() + } + + #[inline] + fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + where + T: IntoIterator + 'tx, + { + todo!() + } + + #[inline] + fn next_state(self) -> Self::Next { + // NOOP + } +} From 115681e982084683965917eb2afeaf03062859ca Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 15 Nov 2022 10:04:17 +0000 Subject: [PATCH 1599/1995] Ensure fake Ethereum events endpoint doesn't shut down immediately We do this by using the AbortableSpawner --- .../test_tools/events_endpoint.rs | 32 +++++++++---- apps/src/lib/node/ledger/mod.rs | 47 +++++++++++++++++-- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs index 864865b2e0..0bf9c3a7dd 100644 --- a/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs +++ b/apps/src/lib/node/ledger/ethereum_node/test_tools/events_endpoint.rs @@ -1,7 +1,7 @@ use borsh::BorshDeserialize; use namada::types::ethereum_events::EthereumEvent; -use tokio::macros::support::poll_fn; use tokio::sync::mpsc::Sender as BoundedSender; +use tokio::sync::oneshot::{Receiver, Sender}; use warp::reply::WithStatus; use warp::Filter; @@ -13,34 +13,46 @@ const DEFAULT_LISTEN_ADDR: ([u8; 4], u16) = ([0, 0, 0, 0], 3030); const EVENTS_POST_ENDPOINT: &str = "eth_events"; /// Starts a [`warp::Server`] that listens for Borsh-serialized Ethereum events -/// and then forwards them to `sender`. It shuts down if `abort_sender` is -/// closed. -pub fn serve( +/// and then forwards them to `sender`. It shuts down if a signal is sent on the +/// `abort_recv` channel. +pub async fn serve( sender: BoundedSender, - mut abort_sender: tokio::sync::oneshot::Sender<()>, -) -> tokio::task::JoinHandle<()> { + abort_recv: Receiver>, +) { tracing::info!(?DEFAULT_LISTEN_ADDR, "Ethereum event endpoint is starting"); let eth_events = warp::post() .and(warp::path(EVENTS_POST_ENDPOINT)) .and(warp::body::bytes()) .then(move |bytes: bytes::Bytes| send(bytes, sender.clone())); - let (_, server) = warp::serve(eth_events).bind_with_graceful_shutdown( + let (_, future) = warp::serve(eth_events).bind_with_graceful_shutdown( DEFAULT_LISTEN_ADDR, async move { tracing::info!( ?DEFAULT_LISTEN_ADDR, "Starting to listen for Borsh-serialized Ethereum events" ); - poll_fn(|cx| abort_sender.poll_closed(cx)).await; + match abort_recv.await { + Ok(abort_resp_send) => { + if abort_resp_send.send(()).is_err() { + tracing::warn!( + "Received signal to abort but failed to respond, \ + will abort now" + ) + } + } + Err(_) => tracing::warn!( + "Channel for receiving signal to abort was closed \ + abruptly, will abort now" + ), + }; tracing::info!( ?DEFAULT_LISTEN_ADDR, "Stopping listening for Borsh-serialized Ethereum events" ); }, ); - - tokio::task::spawn(server) + future.await } /// Callback to send out events from the oracle diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index fe75aa399c..ee953cff75 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -234,7 +234,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Start oracle if necessary let (eth_receiver, oracle) = - match maybe_start_ethereum_oracle(&config, abort_sender).await { + match maybe_start_ethereum_oracle(&mut spawner, &config, abort_sender) + .await + { EthereumOracleTask::NotEnabled { handle, eth_receiver, @@ -646,6 +648,7 @@ enum EthereumOracleTask { /// Potentially starts an Ethereum event oracle. async fn maybe_start_ethereum_oracle( + spawner: &mut AbortableSpawner, config: &config::Ledger, abort_sender: oneshot::Sender<()>, ) -> EthereumOracleTask { @@ -681,10 +684,44 @@ async fn maybe_start_ethereum_oracle( } } ethereum_bridge::ledger::Mode::EventsEndpoint => { - let handle = ethereum_node::test_tools::events_endpoint::serve( - eth_sender, - abort_sender, - ); + let (oracle_abort_send, oracle_abort_recv) = + tokio::sync::oneshot::channel::>( + ); + let handle = spawner + .spawn_abortable( + "Ethereum Events Endpoint", + move |aborter| async move { + ethereum_node::test_tools::events_endpoint::serve( + eth_sender, + oracle_abort_recv, + ) + .await; + tracing::info!( + "Ethereum events endpoint is no longer running." + ); + + drop(aborter); + }, + ) + .with_cleanup(async move { + let (oracle_abort_resp_send, oracle_abort_resp_recv) = + tokio::sync::oneshot::channel::<()>(); + + if let Ok(()) = + oracle_abort_send.send(oracle_abort_resp_send) + { + match oracle_abort_resp_recv.await { + Ok(()) => {} + Err(err) => { + tracing::error!( + "Failed to receive an abort response from \ + the Ethereum events endpoint task: {}", + err + ); + } + } + } + }); EthereumOracleTask::EventsEndpoint { handle, eth_receiver, From 23689fd7ae5c38bee28b3d99cae153cc93504118 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 15 Nov 2022 10:36:50 +0000 Subject: [PATCH 1600/1995] Refactor disable_eth_fullnode -> set_ethereum_bridge_mode --- tests/src/e2e/ledger_tests.rs | 100 +++++++++++++++++++++++++++++----- tests/src/e2e/setup.rs | 12 +++- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b6b29df79c..0a66122fd3 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -25,7 +25,7 @@ use serde_json::json; use setup::constants::*; use super::helpers::{get_height, wait_for_block_height}; -use super::setup::{disable_eth_fullnode, get_all_wasms_hashes}; +use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode}; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -39,7 +39,12 @@ use crate::{run, run_as}; fn run_ledger() -> Result<()> { let test = setup::single_node_net()?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); let cmd_combinations = vec![vec!["ledger"], vec!["ledger", "run"]]; @@ -72,8 +77,18 @@ fn test_node_connectivity() -> Result<()> { let test = setup::network(|genesis| setup::add_validators(1, genesis), None)?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(1)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(1), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run 2 genesis validator ledger nodes and 1 non-validator node let args = ["ledger"]; @@ -175,7 +190,12 @@ fn test_node_connectivity() -> Result<()> { fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { let test = setup::single_node_net()?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -214,7 +234,12 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { fn run_ledger_load_state_and_reset() -> Result<()> { let test = setup::single_node_net()?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -283,7 +308,12 @@ fn run_ledger_load_state_and_reset() -> Result<()> { fn ledger_txs_and_queries() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -457,7 +487,12 @@ fn ledger_txs_and_queries() -> Result<()> { fn invalid_transactions() -> Result<()> { let test = setup::single_node_net()?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -609,7 +644,12 @@ fn pos_bonds() -> Result<()> { None, )?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -810,7 +850,12 @@ fn pos_init_validator() -> Result<()> { None, )?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -979,7 +1024,12 @@ fn ledger_many_txs_in_a_block() -> Result<()> { Some("10s"), )?); - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -1089,7 +1139,12 @@ fn proposal_submission() -> Result<()> { None, )?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); let anomac_help = vec!["--help"]; @@ -1451,7 +1506,12 @@ fn proposal_submission() -> Result<()> { fn proposal_offline() -> Result<()> { let test = setup::network(|genesis| genesis, None)?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -2002,8 +2062,18 @@ fn double_signing_gets_slashed() -> Result<()> { let test = setup::network(|genesis| setup::add_validators(1, genesis), None)?; - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(0)); - disable_eth_fullnode(&test, &test.net.chain_id, &Who::Validator(1)); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(1), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run 2 genesis validator ledger nodes let args = ["ledger"]; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index ae28e5b8ef..bea3c6c118 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -76,10 +76,16 @@ pub fn update_actor_config( .unwrap(); } -/// Disable the Ethereum fullnode of `who`. -pub fn disable_eth_fullnode(test: &Test, chain_id: &ChainId, who: &Who) { +/// Configures the Ethereum bridge mode of `who`. This should be done before +/// `who` starts running. +pub fn set_ethereum_bridge_mode( + test: &Test, + chain_id: &ChainId, + who: &Who, + mode: ethereum_bridge::ledger::Mode, +) { update_actor_config(test, chain_id, who, |config| { - config.ledger.ethereum_bridge.mode = ethereum_bridge::ledger::Mode::Off; + config.ledger.ethereum_bridge.mode = mode; }); } From ec17d3f3a918a8712257a0001b2951fc7bc9a06a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 15 Nov 2022 10:37:00 +0000 Subject: [PATCH 1601/1995] Add run_ledger_with_ethereum_events_endpoint --- tests/src/e2e/eth_bridge_tests.rs | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index e8efb336c8..57d9fa4793 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,3 +1,7 @@ +use color_eyre::eyre::Result; +use namada_apps::config::ethereum_bridge; + +use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::get_actor_rpc; use crate::e2e::setup; use crate::e2e::setup::constants::{ @@ -92,3 +96,32 @@ fn everything() { anomac_tx.assert_success(); } } + +/// Tests that we can start the ledger with an endpoint for submitting Ethereum +/// events. This mode can be used in further end-to-end tests. +#[test] +fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { + let test = setup::single_node_net()?; + + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::EventsEndpoint, + ); + + // Start the ledger as a validator + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, vec!["ledger"], Some(40))?; + ledger.exp_string( + "Starting to listen for Borsh-serialized Ethereum events", + )?; + ledger.exp_string("Anoma ledger node started")?; + + ledger.send_control('c')?; + ledger.exp_string( + "Stopping listening for Borsh-serialized Ethereum events", + )?; + + Ok(()) +} From 1759d06ea3322e569318cd1cf1b6d54d468e9df7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 12:54:11 +0000 Subject: [PATCH 1602/1995] Reserve space for encrypted txs --- .../prepare_proposal/block_space_alloc.rs | 7 ++++- .../block_space_alloc/states/decrypted_txs.rs | 4 +-- .../block_space_alloc/states/protocol_txs.rs | 29 +++++++++++++++++-- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index de061290e7..ea4b749481 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -151,7 +151,6 @@ impl TxBin { /// Construct a new [`TxBin`], with an upper bound on the max number /// of storable txs defined by a ratio over `max_bytes`. #[inline] - #[allow(dead_code)] fn init_over_ratio(max_bytes: u64, frac: Ratio) -> Self { let allotted_space_in_bytes = (frac * max_bytes).to_integer(); Self { @@ -169,6 +168,12 @@ impl TxBin { } } + /// Shrink the allotted space of this [`TxBin`] to whatever + /// space is currently being utilized. + fn shrink(&mut self) { + self.allotted_space_in_bytes = self.current_space_in_bytes; + } + /// Try to dump a new transaction into this [`TxBin`]. /// /// Signal the caller if the tx is larger than its max diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index ff28b8ad96..4ad8e15904 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -21,9 +21,7 @@ impl State for BlockSpaceAllocator { #[inline] fn next_state(mut self) -> Self::Next { - // seal decrypted txs - self.decrypted_txs.allotted_space_in_bytes = - self.decrypted_txs.current_space_in_bytes; + self.decrypted_txs.shrink(); // reserve space for protocol txs let uninit = self.uninitialized_space_in_bytes(); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 943d6c2633..60287d571f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -1,4 +1,6 @@ -use super::super::{AllocStatus, BlockSpaceAllocator}; +use std::marker::PhantomData; + +use super::super::{AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ BuildingEncryptedTxBatch, BuildingProtocolTxBatch, State, WithEncryptedTxs, WithoutEncryptedTxs, @@ -21,8 +23,29 @@ impl State for BlockSpaceAllocator { } #[inline] - fn next_state(self) -> Self::Next { - todo!() + fn next_state(mut self) -> Self::Next { + self.protocol_txs.shrink(); + + // reserve space for encrypted txs + let free_space = self.uninitialized_space_in_bytes(); + self.protocol_txs = TxBin::init(free_space); + + // cast state + let Self { + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } } } From 301f81f6bdf3d5152f50c4bd382ea618d1af8f0b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:08:01 +0000 Subject: [PATCH 1603/1995] Add state transition to next to last state --- .../block_space_alloc/states/protocol_txs.rs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 60287d571f..e796298d5a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -69,7 +69,24 @@ impl State } #[inline] - fn next_state(self) -> Self::Next { - todo!() + fn next_state(mut self) -> Self::Next { + self.protocol_txs.shrink(); + + // cast state + let Self { + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = self; + + BlockSpaceAllocator { + _state: PhantomData, + max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } } } From 0913786f5e5ed65eaa33fe3816db90d0796bdaa9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:14:17 +0000 Subject: [PATCH 1604/1995] Remove unused methods --- .../prepare_proposal/block_space_alloc.rs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index ea4b749481..6e2c58cf62 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -106,35 +106,12 @@ impl BlockSpaceAllocator { /// /// This method should not be used outside of [`BlockSpaceAllocator`] /// instance construction or unit testing. - #[allow(dead_code)] fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes + self.decrypted_txs.allotted_space_in_bytes; self.max_block_space_in_bytes - total_bin_space } - - /// The total space, in bytes, occupied by each transaction. - #[inline] - pub fn occupied_space_in_bytes(&self) -> u64 { - self.protocol_txs.current_space_in_bytes - + self.encrypted_txs.current_space_in_bytes - + self.decrypted_txs.current_space_in_bytes - } - - /// Return the amount, in bytes, of free space in this - /// [`BlockSpaceAllocator`]. - #[inline] - pub fn free_space_in_bytes(&self) -> u64 { - self.max_block_space_in_bytes - self.occupied_space_in_bytes() - } - - /// Checks if this [`BlockSpaceAllocator`] has any free space remaining. - #[allow(dead_code)] - #[inline] - pub fn has_free_space(&self) -> bool { - self.free_space_in_bytes() > 0 - } } /// Allotted space for a batch of transactions of the same kind in some From 8dd1f6d0db643059f8c822fb1f9da3c38b7e934e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:15:47 +0000 Subject: [PATCH 1605/1995] Tune inlined methods --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 6e2c58cf62..c58b60bfca 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -106,6 +106,7 @@ impl BlockSpaceAllocator { /// /// This method should not be used outside of [`BlockSpaceAllocator`] /// instance construction or unit testing. + #[inline] fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes @@ -147,6 +148,7 @@ impl TxBin { /// Shrink the allotted space of this [`TxBin`] to whatever /// space is currently being utilized. + #[inline] fn shrink(&mut self) { self.allotted_space_in_bytes = self.current_space_in_bytes; } @@ -155,7 +157,6 @@ impl TxBin { /// /// Signal the caller if the tx is larger than its max /// allotted bin space. - #[inline] fn try_dump(&mut self, tx: &[u8]) -> AllocStatus { let tx_len = tx.len() as u64; if tx_len > self.allotted_space_in_bytes { @@ -174,7 +175,6 @@ impl TxBin { /// /// If an allocation fails, rollback the state of the [`TxBin`], /// and return the respective status of the failure. - #[inline] fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx, From a49233701d3d0351cdb1643f833857cf5b9993cf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:25:08 +0000 Subject: [PATCH 1606/1995] Improve mod docstrings --- .../prepare_proposal/block_space_alloc.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index c58b60bfca..aafbb97389 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -5,9 +5,22 @@ //! on the size of a block, rejecting blocks whose size exceeds //! the limit stated in [`RequestPrepareProposal`]. //! -//! In the current implementation, each kind of transaction in -//! Namada gets a portion of (i.e. threshold over) the total -//! allotted space. +//! # How space is allocated +//! +//! In the current implementation, we allocate space for transactions +//! in the following order of preference: +//! +//! - First, we allocate space for DKG decrypted txs. +//! - Next, we allocate space for protocol txs. Protocol txs get 1/3 of the +//! block space allotted to them. +//! - Finally, we allocate space for encrypted txs. +//! - If any space remains, we try to fit other smaller txs in the block. +//! +//! Since decrypted txs will utilize at most as much space as +//! encrypted txs will utilize, and we allocate 1/3 of space +//! that has already been taken up by decrypted txs to protocol +//! txs, we roughly divide the block space in 3 for each kind +//! of major tx type. pub mod states; From 3ed460e46a57a64b8a3ec53843fea889ca3007f6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:25:49 +0000 Subject: [PATCH 1607/1995] Small docstr improvement --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index aafbb97389..a17b740ede 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -13,7 +13,7 @@ //! - First, we allocate space for DKG decrypted txs. //! - Next, we allocate space for protocol txs. Protocol txs get 1/3 of the //! block space allotted to them. -//! - Finally, we allocate space for encrypted txs. +//! - Finally, we allocate space for DKG encrypted txs. //! - If any space remains, we try to fit other smaller txs in the block. //! //! Since decrypted txs will utilize at most as much space as From f929d6587e4ad3c583b9f2d40b4c566bc4e0886d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 13:29:54 +0000 Subject: [PATCH 1608/1995] Add TODO item --- .../prepare_proposal/block_space_alloc/states/protocol_txs.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index e796298d5a..7b139b8ca1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -57,6 +57,10 @@ impl State #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + // TODO: prioritize certain kinds of protocol txs; + // this can be done at the `CheckTx` level, + // we don't need the `TxBin`s to be aware + // of different prioriy hints for protocol txs self.protocol_txs.try_dump(tx) } From 877e9415de0df4ae8a8682c6b774ceacd155ba55 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:00:41 +0000 Subject: [PATCH 1609/1995] Create bin for all remaining block space --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- .../block_space_alloc/states/decrypted_txs.rs | 4 ++-- .../block_space_alloc/states/protocol_txs.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index a17b740ede..442e364c8c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -76,7 +76,7 @@ pub struct BlockSpaceAllocator { _state: PhantomData<*const State>, /// The total space Tendermint has allotted to the /// application for the current block height. - max_block_space_in_bytes: u64, + block: TxBin, /// The current space utilized by protocol transactions. protocol_txs: TxBin, /// The current space utilized by DKG encrypted transactions. @@ -103,7 +103,7 @@ impl BlockSpaceAllocator { let max = tendermint_max_block_space_in_bytes; Self { _state: PhantomData, - max_block_space_in_bytes: max, + block: TxBin::init(max), protocol_txs: TxBin::default(), encrypted_txs: TxBin::default(), // decrypted txs can use as much space as needed; in practice, @@ -124,7 +124,7 @@ impl BlockSpaceAllocator { let total_bin_space = self.protocol_txs.allotted_space_in_bytes + self.encrypted_txs.allotted_space_in_bytes + self.decrypted_txs.allotted_space_in_bytes; - self.max_block_space_in_bytes - total_bin_space + self.block.allotted_space_in_bytes - total_bin_space } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 4ad8e15904..b6a189a757 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -29,7 +29,7 @@ impl State for BlockSpaceAllocator { // cast state let Self { - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, @@ -38,7 +38,7 @@ impl State for BlockSpaceAllocator { BlockSpaceAllocator { _state: PhantomData, - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 7b139b8ca1..2830d6be02 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -32,7 +32,7 @@ impl State for BlockSpaceAllocator { // cast state let Self { - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, @@ -41,7 +41,7 @@ impl State for BlockSpaceAllocator { BlockSpaceAllocator { _state: PhantomData, - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, @@ -78,7 +78,7 @@ impl State // cast state let Self { - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, @@ -87,7 +87,7 @@ impl State BlockSpaceAllocator { _state: PhantomData, - max_block_space_in_bytes, + block, protocol_txs, encrypted_txs, decrypted_txs, From 93559868b5fc0995818a10f8b2a1d5310b294a2a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:03:36 +0000 Subject: [PATCH 1610/1995] Claim block space from tx bins --- .../shell/prepare_proposal/block_space_alloc.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 442e364c8c..7e16700190 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -126,6 +126,20 @@ impl BlockSpaceAllocator { + self.decrypted_txs.allotted_space_in_bytes; self.block.allotted_space_in_bytes - total_bin_space } + + /// Claim all the space used by the [`TxBin`] instances + /// as block space. + fn claim_block_space(&mut self) { + let used_space = self.protocol_txs.current_space_in_bytes + + self.encrypted_txs.current_space_in_bytes + + self.decrypted_txs.current_space_in_bytes; + + self.block.current_space_in_bytes = used_space; + + self.decrypted_txs.current_space_in_bytes = 0; + self.protocol_txs.current_space_in_bytes = 0; + self.encrypted_txs.current_space_in_bytes = 0; + } } /// Allotted space for a batch of transactions of the same kind in some From a8ad3f1370bcd6b799243c5f7506b4137438bd4f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:03:58 +0000 Subject: [PATCH 1611/1995] Inline --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 7e16700190..300122951c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -129,6 +129,7 @@ impl BlockSpaceAllocator { /// Claim all the space used by the [`TxBin`] instances /// as block space. + #[inline] fn claim_block_space(&mut self) { let used_space = self.protocol_txs.current_space_in_bytes + self.encrypted_txs.current_space_in_bytes From 7451c1df75cdb6b8b2f93aaa96ab04eec2864f5f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:10:44 +0000 Subject: [PATCH 1612/1995] Transition to tx bins' final state --- .../block_space_alloc/states/encrypted_txs.rs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index df6018324a..0067f7fc86 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ BuildingEncryptedTxBatch, FillingRemainingSpace, State, WithEncryptedTxs, @@ -22,7 +24,7 @@ impl State for BlockSpaceAllocator> { #[inline] fn next_state(self) -> Self::Next { - todo!() + next_state(self) } } @@ -46,6 +48,33 @@ impl State #[inline] fn next_state(self) -> Self::Next { - todo!() + next_state(self) + } +} + +#[inline] +fn next_state( + mut alloc: BlockSpaceAllocator>, +) -> BlockSpaceAllocator> { + alloc.encrypted_txs.shrink(); + + // reserve space for any remaining txs + alloc.claim_block_space(); + + // cast state + let BlockSpaceAllocator { + block, + protocol_txs, + encrypted_txs, + decrypted_txs, + .. + } = alloc; + + BlockSpaceAllocator { + _state: PhantomData, + block, + protocol_txs, + encrypted_txs, + decrypted_txs, } } From 66514ac5989f693e9ba2a6e3f0596268bd0af112 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:13:23 +0000 Subject: [PATCH 1613/1995] Alloc space for remaining txs --- .../block_space_alloc/states/remaining_txs.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index bda6da24cc..cadfc5e6e2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -7,16 +7,16 @@ impl State for BlockSpaceAllocator> { type Next = (); #[inline] - fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { - todo!() + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.block.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx, { - todo!() + self.block.try_dump_all(txs) } #[inline] @@ -29,16 +29,16 @@ impl State for BlockSpaceAllocator> { type Next = (); #[inline] - fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { - todo!() + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + self.block.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx, { - todo!() + self.block.try_dump_all(txs) } #[inline] From 8a21a35fbdd477a40dbfc5071e07d87556b180b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:20:42 +0000 Subject: [PATCH 1614/1995] New docstr changes --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 300122951c..26ef296e18 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -5,6 +5,12 @@ //! on the size of a block, rejecting blocks whose size exceeds //! the limit stated in [`RequestPrepareProposal`]. //! +//! The code in this module doesn't perform any deserializing to +//! verify if we are, in fact, allocating space for the correct +//! kind of tx for the current [`BlockSpaceAllocator`] state. It +//! is up to the user to dispatch the correct kind of tx into the +//! current state of the allocator. +//! //! # How space is allocated //! //! In the current implementation, we allocate space for transactions From 71d9748a9a7161f17bcd50986447d6a817ae8ec5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 14:47:03 +0000 Subject: [PATCH 1615/1995] Remove #[allow(dead_code)] --- .../shell/prepare_proposal/block_space_alloc/states.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 66c89fcc6f..ccb8e2c0f6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -37,7 +37,6 @@ use super::BlockSpaceAllocator; /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub enum BuildingDecryptedTxBatch {} /// The leader of the current Tendermint round is building @@ -45,7 +44,6 @@ pub enum BuildingDecryptedTxBatch {} /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub enum BuildingProtocolTxBatch {} /// The leader of the current Tendermint round is building @@ -53,7 +51,6 @@ pub enum BuildingProtocolTxBatch {} /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub struct BuildingEncryptedTxBatch { /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. _mode: Mode, @@ -65,7 +62,6 @@ pub struct BuildingEncryptedTxBatch { /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub struct FillingRemainingSpace { /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. _mode: Mode, @@ -75,14 +71,12 @@ pub struct FillingRemainingSpace { /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub enum WithEncryptedTxs {} /// Prohibit block proposals from including encrypted txs. /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -#[allow(dead_code)] pub enum WithoutEncryptedTxs {} /// Represents a state in the [`BlockSpaceAllocator`] state machine. From e84462f67b0132d577743476cb17833ceb9e1bb2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:03:51 +0000 Subject: [PATCH 1616/1995] Split state transition from space allocation in tx bins --- .../block_space_alloc/states.rs | 66 +++++++++++++++---- .../block_space_alloc/states/decrypted_txs.rs | 12 ++-- .../block_space_alloc/states/encrypted_txs.rs | 32 +++++---- .../block_space_alloc/states/protocol_txs.rs | 41 +++++------- .../block_space_alloc/states/remaining_txs.rs | 17 +---- 5 files changed, 100 insertions(+), 68 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index ccb8e2c0f6..74a1edc427 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -83,10 +83,7 @@ pub enum WithoutEncryptedTxs {} /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub trait State { - /// The next state in the [`BlockSpaceAllocator`] state machine. - type Next; - +pub trait State { /// Try to allocate space for a new transaction. fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; @@ -94,15 +91,31 @@ pub trait State { fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus where T: IntoIterator + 'tx; +} + +/// Represents a state transition in the [`BlockSpaceAllocator`] state machine. +/// +/// This trait should not be used directly. Instead, consider using one of +/// [`NextState`], [`NextStateWithEncryptedTxs`] or +/// [`NextStateWithoutEncryptedTxs`]. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateImpl { + /// The next state in the [`BlockSpaceAllocator`] state machine. + type Next; /// Transition to the next state in the [`BlockSpaceAllocator`] state /// machine. - fn next_state(self) -> Self::Next; + fn next_state_impl(self) -> Self::Next; } -/// Convenience extension of [`State`], to transition to a new +/// Convenience extension of [`NextStateImpl`], to transition to a new /// state with encrypted txs in a block. -pub trait StateWithEncryptedTxs: State { +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateWithEncryptedTxs: NextStateImpl { /// Transition to the next state in the [`BlockSpaceAllocator`] state, /// ensuring we include encrypted txs in a block. #[inline] @@ -110,15 +123,20 @@ pub trait StateWithEncryptedTxs: State { where Self: Sized, { - self.next_state() + self.next_state_impl() } } -impl StateWithEncryptedTxs for S where S: State {} +impl NextStateWithEncryptedTxs for S where S: NextStateImpl {} -/// Convenience extension of [`State`], to transition to a new +/// Convenience extension of [`NextStateImpl`], to transition to a new /// state without encrypted txs in a block. -pub trait StateWithoutEncryptedTxs: State { +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextStateWithoutEncryptedTxs: + NextStateImpl +{ /// Transition to the next state in the [`BlockSpaceAllocator`] state, /// ensuring we do not include encrypted txs in a block. #[inline] @@ -126,8 +144,30 @@ pub trait StateWithoutEncryptedTxs: State { where Self: Sized, { - self.next_state() + self.next_state_impl() + } +} + +impl NextStateWithoutEncryptedTxs for S where + S: NextStateImpl +{ +} + +/// Convenience extension of [`NextStateImpl`], to transition to a new +/// state with a null transition function. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait NextState: NextStateImpl { + /// Transition to the next state in the [`BlockSpaceAllocator`] state, + /// using a null transiiton function. + #[inline] + fn next_state(self) -> Self::Next + where + Self: Sized, + { + self.next_state_impl() } } -impl StateWithoutEncryptedTxs for S where S: State {} +impl NextState for S where S: NextStateImpl {} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index b6a189a757..498bf7411b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -1,11 +1,11 @@ use std::marker::PhantomData; use super::super::{thres, AllocStatus, BlockSpaceAllocator, TxBin}; -use super::{BuildingDecryptedTxBatch, BuildingProtocolTxBatch, State}; +use super::{ + BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, State, +}; impl State for BlockSpaceAllocator { - type Next = BlockSpaceAllocator; - #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { self.decrypted_txs.try_dump(tx) @@ -18,9 +18,13 @@ impl State for BlockSpaceAllocator { { self.decrypted_txs.try_dump_all(txs) } +} + +impl NextStateImpl for BlockSpaceAllocator { + type Next = BlockSpaceAllocator; #[inline] - fn next_state(mut self) -> Self::Next { + fn next_state_impl(mut self) -> Self::Next { self.decrypted_txs.shrink(); // reserve space for protocol txs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 0067f7fc86..2dde3e830f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -2,13 +2,11 @@ use std::marker::PhantomData; use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ - BuildingEncryptedTxBatch, FillingRemainingSpace, State, WithEncryptedTxs, - WithoutEncryptedTxs, + BuildingEncryptedTxBatch, FillingRemainingSpace, NextStateImpl, State, + WithEncryptedTxs, WithoutEncryptedTxs, }; impl State for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator>; - #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { self.encrypted_txs.try_dump(tx) @@ -21,9 +19,15 @@ impl State for BlockSpaceAllocator> { { self.encrypted_txs.try_dump_all(txs) } +} + +impl NextStateImpl + for BlockSpaceAllocator> +{ + type Next = BlockSpaceAllocator>; #[inline] - fn next_state(self) -> Self::Next { + fn next_state_impl(self) -> Self::Next { next_state(self) } } @@ -31,23 +35,27 @@ impl State for BlockSpaceAllocator> { impl State for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator>; - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - self.encrypted_txs.try_dump(tx) + fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { + AllocStatus::Rejected } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus where T: IntoIterator + 'tx, { - self.encrypted_txs.try_dump_all(txs) + AllocStatus::Rejected } +} + +impl NextStateImpl + for BlockSpaceAllocator> +{ + type Next = BlockSpaceAllocator>; #[inline] - fn next_state(self) -> Self::Next { + fn next_state_impl(self) -> Self::Next { next_state(self) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 2830d6be02..cb0ffc31ea 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -2,15 +2,17 @@ use std::marker::PhantomData; use super::super::{AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ - BuildingEncryptedTxBatch, BuildingProtocolTxBatch, State, WithEncryptedTxs, - WithoutEncryptedTxs, + BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, State, + WithEncryptedTxs, WithoutEncryptedTxs, }; -impl State for BlockSpaceAllocator { - type Next = BlockSpaceAllocator>; - +impl State for BlockSpaceAllocator { #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + // TODO: prioritize certain kinds of protocol txs; + // this can be done at the `CheckTx` level, + // we don't need the `TxBin`s to be aware + // of different prioriy hints for protocol txs self.protocol_txs.try_dump(tx) } @@ -21,9 +23,15 @@ impl State for BlockSpaceAllocator { { self.protocol_txs.try_dump_all(txs) } +} + +impl NextStateImpl + for BlockSpaceAllocator +{ + type Next = BlockSpaceAllocator>; #[inline] - fn next_state(mut self) -> Self::Next { + fn next_state_impl(mut self) -> Self::Next { self.protocol_txs.shrink(); // reserve space for encrypted txs @@ -49,31 +57,14 @@ impl State for BlockSpaceAllocator { } } -impl State +impl NextStateImpl for BlockSpaceAllocator { type Next = BlockSpaceAllocator>; #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - // TODO: prioritize certain kinds of protocol txs; - // this can be done at the `CheckTx` level, - // we don't need the `TxBin`s to be aware - // of different prioriy hints for protocol txs - self.protocol_txs.try_dump(tx) - } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus - where - T: IntoIterator + 'tx, - { - self.protocol_txs.try_dump_all(txs) - } - - #[inline] - fn next_state(mut self) -> Self::Next { + fn next_state_impl(mut self) -> Self::Next { self.protocol_txs.shrink(); // cast state diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index cadfc5e6e2..21c49b767f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -4,8 +4,6 @@ use super::{ }; impl State for BlockSpaceAllocator> { - type Next = (); - #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { self.block.try_dump(tx) @@ -18,16 +16,12 @@ impl State for BlockSpaceAllocator> { { self.block.try_dump_all(txs) } - - #[inline] - fn next_state(self) -> Self::Next { - // NOOP - } } +// TODO: limit txs that can go in the bins at this level? so we don't misuse +// the abstraction. it's not like we can't push encrypted txs into the bins, +// right now... impl State for BlockSpaceAllocator> { - type Next = (); - #[inline] fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { self.block.try_dump(tx) @@ -40,9 +34,4 @@ impl State for BlockSpaceAllocator> { { self.block.try_dump_all(txs) } - - #[inline] - fn next_state(self) -> Self::Next { - // NOOP - } } From 0fd75a52f5c652eab0b3775e0a32c9c7beb4a4e2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 15:14:47 +0000 Subject: [PATCH 1617/1995] Fix encrypted txs space allocation --- .../prepare_proposal/block_space_alloc/states/protocol_txs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index cb0ffc31ea..17f6d13b7d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -36,7 +36,7 @@ impl NextStateImpl // reserve space for encrypted txs let free_space = self.uninitialized_space_in_bytes(); - self.protocol_txs = TxBin::init(free_space); + self.encrypted_txs = TxBin::init(free_space); // cast state let Self { From a6c386df41ea094795dc105ad7a25b33ba53ce20 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:01:36 +0000 Subject: [PATCH 1618/1995] Return reason for dropping txs in a proposal --- .../prepare_proposal/block_space_alloc.rs | 20 +++++++++++++------ .../block_space_alloc/states/encrypted_txs.rs | 14 ++++++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 26ef296e18..044af12bfd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -62,10 +62,10 @@ pub enum AllocStatus { /// The transaction is able to be included in the current block. Accepted, /// The transaction can only be included in an upcoming block. - Rejected, + Rejected { tx_len: u64, space_left: u64 }, /// The transaction would overflow the allotted bin space, /// therefore it needs to be handled separately. - OverflowsBin, + OverflowsBin { tx_len: u64, bin_size: u64 }, } /// Allotted space for a batch of transactions in some proposed block, @@ -171,6 +171,12 @@ impl TxBin { } } + /// Return the amount of space left in this [`TxBin`]. + #[inline] + fn space_left_in_bytes(&self) -> u64 { + self.allotted_space_in_bytes - self.current_space_in_bytes + } + /// Construct a new [`TxBin`], with a capacity of `max_bytes`. #[inline] fn init(max_bytes: u64) -> Self { @@ -194,14 +200,16 @@ impl TxBin { fn try_dump(&mut self, tx: &[u8]) -> AllocStatus { let tx_len = tx.len() as u64; if tx_len > self.allotted_space_in_bytes { - return AllocStatus::OverflowsBin; + let bin_size = self.allotted_space_in_bytes; + return AllocStatus::OverflowsBin { tx_len, bin_size }; } let occupied = self.current_space_in_bytes + tx_len; if occupied <= self.allotted_space_in_bytes { self.current_space_in_bytes = occupied; AllocStatus::Accepted } else { - AllocStatus::Rejected + let space_left = self.space_left_in_bytes(); + AllocStatus::Rejected { tx_len, space_left } } } @@ -217,8 +225,8 @@ impl TxBin { for tx in txs { match self.try_dump(tx) { AllocStatus::Accepted => space_diff += tx.len() as u64, - status - @ (AllocStatus::Rejected | AllocStatus::OverflowsBin) => { + status @ (AllocStatus::Rejected { .. } + | AllocStatus::OverflowsBin { .. }) => { self.current_space_in_bytes -= space_diff; return status; } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 2dde3e830f..4b73e722a0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -36,8 +36,11 @@ impl State for BlockSpaceAllocator> { #[inline] - fn try_alloc(&mut self, _tx: &[u8]) -> AllocStatus { - AllocStatus::Rejected + fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + AllocStatus::Rejected { + tx_len: tx.len() as u64, + space_left: 0, + } } #[inline] @@ -45,7 +48,12 @@ impl State where T: IntoIterator + 'tx, { - AllocStatus::Rejected + AllocStatus::Rejected { + // arbitrary `tx_len` value; doesn't really matter what we + // choose here, as long as it's greater than zero + tx_len: u64::MAX, + space_left: 0, + } } } From 0512868e31e00dcf240c44903062d5afb72cc46d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 17 Nov 2022 10:17:30 +0000 Subject: [PATCH 1619/1995] Run Ethereum bridge CI for stacked PRs --- .github/workflows/build-and-test-bridge.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index dd5d87265e..c3d3808a0e 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -8,6 +8,7 @@ on: pull_request_target: branches: - eth-bridge-integration + - '**/ethbridge/**' types: [opened, synchronize, reopened] workflow_dispatch: From c7f1701e3bcc417b556ab8cbb2ab40302ace3ed5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 12:58:50 +0000 Subject: [PATCH 1620/1995] Rename field: current_space -> occupied_space --- .../prepare_proposal/block_space_alloc.rs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 044af12bfd..493a9e2e53 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -137,15 +137,15 @@ impl BlockSpaceAllocator { /// as block space. #[inline] fn claim_block_space(&mut self) { - let used_space = self.protocol_txs.current_space_in_bytes - + self.encrypted_txs.current_space_in_bytes - + self.decrypted_txs.current_space_in_bytes; + let used_space = self.protocol_txs.occupied_space_in_bytes + + self.encrypted_txs.occupied_space_in_bytes + + self.decrypted_txs.occupied_space_in_bytes; - self.block.current_space_in_bytes = used_space; + self.block.occupied_space_in_bytes = used_space; - self.decrypted_txs.current_space_in_bytes = 0; - self.protocol_txs.current_space_in_bytes = 0; - self.encrypted_txs.current_space_in_bytes = 0; + self.decrypted_txs.occupied_space_in_bytes = 0; + self.protocol_txs.occupied_space_in_bytes = 0; + self.encrypted_txs.occupied_space_in_bytes = 0; } } @@ -154,7 +154,7 @@ impl BlockSpaceAllocator { #[derive(Debug, Copy, Clone, Default)] struct TxBin { /// The current space utilized by the batch of transactions. - current_space_in_bytes: u64, + occupied_space_in_bytes: u64, /// The maximum space the batch of transactions may occupy. allotted_space_in_bytes: u64, } @@ -167,14 +167,14 @@ impl TxBin { let allotted_space_in_bytes = (frac * max_bytes).to_integer(); Self { allotted_space_in_bytes, - current_space_in_bytes: 0, + occupied_space_in_bytes: 0, } } /// Return the amount of space left in this [`TxBin`]. #[inline] fn space_left_in_bytes(&self) -> u64 { - self.allotted_space_in_bytes - self.current_space_in_bytes + self.allotted_space_in_bytes - self.occupied_space_in_bytes } /// Construct a new [`TxBin`], with a capacity of `max_bytes`. @@ -182,7 +182,7 @@ impl TxBin { fn init(max_bytes: u64) -> Self { Self { allotted_space_in_bytes: max_bytes, - current_space_in_bytes: 0, + occupied_space_in_bytes: 0, } } @@ -190,7 +190,7 @@ impl TxBin { /// space is currently being utilized. #[inline] fn shrink(&mut self) { - self.allotted_space_in_bytes = self.current_space_in_bytes; + self.allotted_space_in_bytes = self.occupied_space_in_bytes; } /// Try to dump a new transaction into this [`TxBin`]. @@ -203,9 +203,9 @@ impl TxBin { let bin_size = self.allotted_space_in_bytes; return AllocStatus::OverflowsBin { tx_len, bin_size }; } - let occupied = self.current_space_in_bytes + tx_len; + let occupied = self.occupied_space_in_bytes + tx_len; if occupied <= self.allotted_space_in_bytes { - self.current_space_in_bytes = occupied; + self.occupied_space_in_bytes = occupied; AllocStatus::Accepted } else { let space_left = self.space_left_in_bytes(); @@ -227,7 +227,7 @@ impl TxBin { AllocStatus::Accepted => space_diff += tx.len() as u64, status @ (AllocStatus::Rejected { .. } | AllocStatus::OverflowsBin { .. }) => { - self.current_space_in_bytes -= space_diff; + self.occupied_space_in_bytes -= space_diff; return status; } } From fc3cb2e7adfa44e006aa2d64026537a48f0794f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:01:19 +0000 Subject: [PATCH 1621/1995] Set all bin space to zero when claiming block space --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 493a9e2e53..39db009fc8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -143,9 +143,9 @@ impl BlockSpaceAllocator { self.block.occupied_space_in_bytes = used_space; - self.decrypted_txs.occupied_space_in_bytes = 0; - self.protocol_txs.occupied_space_in_bytes = 0; - self.encrypted_txs.occupied_space_in_bytes = 0; + self.decrypted_txs = TxBin::default(); + self.protocol_txs = TxBin::default(); + self.encrypted_txs = TxBin::default(); } } From 005416d1aaf4bf7775b290a87d42c56ecb986118 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:03:17 +0000 Subject: [PATCH 1622/1995] Improve binding name of remaining free space --- .../block_space_alloc/states/decrypted_txs.rs | 5 +++-- .../block_space_alloc/states/protocol_txs.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 498bf7411b..229c55a108 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -28,8 +28,9 @@ impl NextStateImpl for BlockSpaceAllocator { self.decrypted_txs.shrink(); // reserve space for protocol txs - let uninit = self.uninitialized_space_in_bytes(); - self.protocol_txs = TxBin::init_over_ratio(uninit, thres::ONE_THIRD); + let remaining_free_space = self.uninitialized_space_in_bytes(); + self.protocol_txs = + TxBin::init_over_ratio(remaining_free_space, thres::ONE_THIRD); // cast state let Self { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 17f6d13b7d..4374152c44 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -35,8 +35,8 @@ impl NextStateImpl self.protocol_txs.shrink(); // reserve space for encrypted txs - let free_space = self.uninitialized_space_in_bytes(); - self.encrypted_txs = TxBin::init(free_space); + let remaining_free_space = self.uninitialized_space_in_bytes(); + self.encrypted_txs = TxBin::init(remaining_free_space); // cast state let Self { From e779c0a987972d4fa06b5113d2674679626075a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:09:00 +0000 Subject: [PATCH 1623/1995] Improve docstr --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 39db009fc8..ab8f1acf41 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -121,10 +121,12 @@ impl BlockSpaceAllocator { } impl BlockSpaceAllocator { - /// Return uninitialized space in tx bins, resulting from ratio conversions. + /// Return the amount of space left to initialize in all + /// [`TxBin`] instances. /// - /// This method should not be used outside of [`BlockSpaceAllocator`] - /// instance construction or unit testing. + /// This is calculated based on the different between the + /// allotted Tendermint block space and the sum of the allotted + /// space to each [`TxBin`] instance in a [`BlockSpaceAllocator`]. #[inline] fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes From 1dff452994c2f8b36361d112c3a3373f6b289419 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:09:35 +0000 Subject: [PATCH 1624/1995] Fix docstr typo --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index ab8f1acf41..63bedf468a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -244,6 +244,6 @@ mod thres { use num_rational::Ratio; /// The threshold over Tendermint's allotted space for all three - /// (major) kinds of Namada transations. + /// (major) kinds of Namada transactions. pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); } From 3905441499b57dab4b9fe26fb074e1b438e3c038 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:28:11 +0000 Subject: [PATCH 1625/1995] Fix docstr --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 63bedf468a..865708ce08 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -124,7 +124,7 @@ impl BlockSpaceAllocator { /// Return the amount of space left to initialize in all /// [`TxBin`] instances. /// - /// This is calculated based on the different between the + /// This is calculated based on the difference between the /// allotted Tendermint block space and the sum of the allotted /// space to each [`TxBin`] instance in a [`BlockSpaceAllocator`]. #[inline] From e23ddf54c73834017061ac6ccc641aa3d5b553cc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:30:31 +0000 Subject: [PATCH 1626/1995] More uninitialized_space_in_bytes() docstr changes --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 865708ce08..7504bb0855 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -124,9 +124,9 @@ impl BlockSpaceAllocator { /// Return the amount of space left to initialize in all /// [`TxBin`] instances. /// - /// This is calculated based on the difference between the - /// allotted Tendermint block space and the sum of the allotted - /// space to each [`TxBin`] instance in a [`BlockSpaceAllocator`]. + /// This is calculated based on the difference between the Tendermint + /// block space for a given round and the sum of the allotted space + /// to each [`TxBin`] instance in a [`BlockSpaceAllocator`]. #[inline] fn uninitialized_space_in_bytes(&self) -> u64 { let total_bin_space = self.protocol_txs.allotted_space_in_bytes From 1497ce0a4ab6e4695467a9fe0ca045510e636a36 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:41:08 +0000 Subject: [PATCH 1627/1995] Include offending tx in AllocStatus --- .../prepare_proposal/block_space_alloc.rs | 14 +++++------ .../block_space_alloc/states.rs | 4 ++-- .../block_space_alloc/states/decrypted_txs.rs | 4 ++-- .../block_space_alloc/states/encrypted_txs.rs | 24 ++++++++----------- .../block_space_alloc/states/protocol_txs.rs | 4 ++-- .../block_space_alloc/states/remaining_txs.rs | 8 +++---- 6 files changed, 27 insertions(+), 31 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 7504bb0855..3818603648 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -58,14 +58,14 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// All status responses from trying to allocate block space for a tx. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum AllocStatus { +pub enum AllocStatus<'tx> { /// The transaction is able to be included in the current block. Accepted, /// The transaction can only be included in an upcoming block. - Rejected { tx_len: u64, space_left: u64 }, + Rejected { tx: &'tx [u8], space_left: u64 }, /// The transaction would overflow the allotted bin space, /// therefore it needs to be handled separately. - OverflowsBin { tx_len: u64, bin_size: u64 }, + OverflowsBin { tx: &'tx [u8], bin_size: u64 }, } /// Allotted space for a batch of transactions in some proposed block, @@ -199,11 +199,11 @@ impl TxBin { /// /// Signal the caller if the tx is larger than its max /// allotted bin space. - fn try_dump(&mut self, tx: &[u8]) -> AllocStatus { + fn try_dump<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { let tx_len = tx.len() as u64; if tx_len > self.allotted_space_in_bytes { let bin_size = self.allotted_space_in_bytes; - return AllocStatus::OverflowsBin { tx_len, bin_size }; + return AllocStatus::OverflowsBin { tx, bin_size }; } let occupied = self.occupied_space_in_bytes + tx_len; if occupied <= self.allotted_space_in_bytes { @@ -211,7 +211,7 @@ impl TxBin { AllocStatus::Accepted } else { let space_left = self.space_left_in_bytes(); - AllocStatus::Rejected { tx_len, space_left } + AllocStatus::Rejected { tx, space_left } } } @@ -219,7 +219,7 @@ impl TxBin { /// /// If an allocation fails, rollback the state of the [`TxBin`], /// and return the respective status of the failure. - fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 74a1edc427..ed1cd618aa 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -85,10 +85,10 @@ pub enum WithoutEncryptedTxs {} /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub trait State { /// Try to allocate space for a new transaction. - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus; + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx>; /// Try to allocate space for a new batch of transactions. - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx; } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 229c55a108..e5e981d484 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -7,12 +7,12 @@ use super::{ impl State for BlockSpaceAllocator { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.decrypted_txs.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 4b73e722a0..a2a6d72a55 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -8,12 +8,12 @@ use super::{ impl State for BlockSpaceAllocator> { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.encrypted_txs.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { @@ -36,24 +36,20 @@ impl State for BlockSpaceAllocator> { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { - AllocStatus::Rejected { - tx_len: tx.len() as u64, - space_left: 0, - } + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + AllocStatus::Rejected { tx, space_left: 0 } } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, _txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { - AllocStatus::Rejected { - // arbitrary `tx_len` value; doesn't really matter what we - // choose here, as long as it's greater than zero - tx_len: u64::MAX, - space_left: 0, - } + let tx = txs + .into_iter() + .next() + .expect("We should have had at least one tx in the batch"); + AllocStatus::Rejected { tx, space_left: 0 } } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 4374152c44..3e422b412e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -8,7 +8,7 @@ use super::{ impl State for BlockSpaceAllocator { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { // TODO: prioritize certain kinds of protocol txs; // this can be done at the `CheckTx` level, // we don't need the `TxBin`s to be aware @@ -17,7 +17,7 @@ impl State for BlockSpaceAllocator { } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index 21c49b767f..67965c4954 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -5,12 +5,12 @@ use super::{ impl State for BlockSpaceAllocator> { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.block.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { @@ -23,12 +23,12 @@ impl State for BlockSpaceAllocator> { // right now... impl State for BlockSpaceAllocator> { #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> AllocStatus { + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.block.try_dump(tx) } #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus + fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { From c52d46cecf3e1ce230b289e12485bbf13bcd57e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:51:04 +0000 Subject: [PATCH 1628/1995] Refactor State into TryAlloc and TryAllocBatch --- .../block_space_alloc/states.rs | 11 ++++++-- .../block_space_alloc/states/decrypted_txs.rs | 12 ++------ .../block_space_alloc/states/encrypted_txs.rs | 28 ++++--------------- .../block_space_alloc/states/protocol_txs.rs | 19 +++++-------- .../block_space_alloc/states/remaining_txs.rs | 24 ++++------------ 5 files changed, 28 insertions(+), 66 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index ed1cd618aa..2bf3202d20 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -79,14 +79,21 @@ pub enum WithEncryptedTxs {} /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub enum WithoutEncryptedTxs {} -/// Represents a state in the [`BlockSpaceAllocator`] state machine. +/// Try to allocate a new transaction on a [`BlockSpaceAllocator`] state. /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub trait State { +pub trait TryAlloc { /// Try to allocate space for a new transaction. fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx>; +} +/// Try to allocate a new batch of transactions on a +/// [`BlockSpaceAllocator`] state. +/// +/// For more info, read the module docs of +/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. +pub trait TryAllocBatch { /// Try to allocate space for a new batch of transactions. fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index e5e981d484..e2ea17dea5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -2,22 +2,14 @@ use std::marker::PhantomData; use super::super::{thres, AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ - BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, State, + BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, }; -impl State for BlockSpaceAllocator { +impl TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.decrypted_txs.try_dump(tx) } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - self.decrypted_txs.try_dump_all(txs) - } } impl NextStateImpl for BlockSpaceAllocator { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index a2a6d72a55..41cc3130b8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -2,23 +2,17 @@ use std::marker::PhantomData; use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ - BuildingEncryptedTxBatch, FillingRemainingSpace, NextStateImpl, State, + BuildingEncryptedTxBatch, FillingRemainingSpace, NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, }; -impl State for BlockSpaceAllocator> { +impl TryAlloc + for BlockSpaceAllocator> +{ #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.encrypted_txs.try_dump(tx) } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - self.encrypted_txs.try_dump_all(txs) - } } impl NextStateImpl @@ -32,25 +26,13 @@ impl NextStateImpl } } -impl State +impl TryAlloc for BlockSpaceAllocator> { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { AllocStatus::Rejected { tx, space_left: 0 } } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - let tx = txs - .into_iter() - .next() - .expect("We should have had at least one tx in the batch"); - AllocStatus::Rejected { tx, space_left: 0 } - } } impl NextStateImpl diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index 3e422b412e..b4ce10e956 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -2,25 +2,20 @@ use std::marker::PhantomData; use super::super::{AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ - BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, State, - WithEncryptedTxs, WithoutEncryptedTxs, + BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, + TryAllocBatch, WithEncryptedTxs, WithoutEncryptedTxs, }; -impl State for BlockSpaceAllocator { - #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { - // TODO: prioritize certain kinds of protocol txs; - // this can be done at the `CheckTx` level, - // we don't need the `TxBin`s to be aware - // of different prioriy hints for protocol txs - self.protocol_txs.try_dump(tx) - } - +impl TryAllocBatch for BlockSpaceAllocator { #[inline] fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> where T: IntoIterator + 'tx, { + // TODO: prioritize certain kinds of protocol txs; + // this can be done at the `CheckTx` level, + // we don't need the `TxBin`s to be aware + // of different prioriy hints for protocol txs self.protocol_txs.try_dump_all(txs) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index 67965c4954..d9210edf79 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -1,37 +1,23 @@ use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ - FillingRemainingSpace, State, WithEncryptedTxs, WithoutEncryptedTxs, + FillingRemainingSpace, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, }; -impl State for BlockSpaceAllocator> { +impl TryAlloc for BlockSpaceAllocator> { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.block.try_dump(tx) } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - self.block.try_dump_all(txs) - } } // TODO: limit txs that can go in the bins at this level? so we don't misuse // the abstraction. it's not like we can't push encrypted txs into the bins, // right now... -impl State for BlockSpaceAllocator> { +impl TryAlloc + for BlockSpaceAllocator> +{ #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.block.try_dump(tx) } - - #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - self.block.try_dump_all(txs) - } } From 383062e92e37d85d4e4c49abfc4d9c1ceb0299fb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 15:01:41 +0000 Subject: [PATCH 1629/1995] Add back (now broken) tx bin unit tests --- .../prepare_proposal/block_space_alloc.rs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 3818603648..aead94afba 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -247,3 +247,191 @@ mod thres { /// (major) kinds of Namada transactions. pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use proptest::prelude::*; + + use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space_in_bytes: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } + + /// Check if the sum of all individual tx thresholds does + /// not exceed one. + /// + /// This is important, because we do not want to exceed + /// the maximum block size in Tendermint, and get randomly + /// rejected blocks. + #[test] + fn test_tx_thres_doesnt_exceed_one() { + let sum = + thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; + assert_eq!(sum.to_integer(), 1); + } + + proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`BlockSpaceAllocator`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + + /// Check if the sum of all individual bin allotments for a + /// [`BlockSpaceAllocator`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not fill up the bin. + #[test] + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) + } + } + + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; + + // make sure we can't dump any new decrypted txs in the bin + assert_eq!( + bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + AllocStatus::Rejected + ); + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space_in_bytes: u64, + ) { + let bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + assert_eq!(0, bins.uninitialized_space_in_bytes()); + } + + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + let bins = RefCell::new(BlockSpaceAllocator::init( + tendermint_max_block_space_in_bytes, + )); + + // produce new txs until we fill up the bins + // + // TODO: ideally the proptest strategy would already return + // txs whose total added size would be bounded + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + + // make sure we can keep dumping txs, + // without filling up the bins + for tx in protocol_txs { + assert_eq!( + bins.borrow_mut().try_alloc_protocol_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in encrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_encrypted_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in decrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_decrypted_tx(&tx), + AllocStatus::Accepted + ); + } + } + + prop_compose! { + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions() + // create base strategies + ( + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), + ) + // compose strategies + ( + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), + ) + -> PropTx { + PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } + } + + /// Return random bin sizes for a [`BlockSpaceAllocator`]. + fn arb_max_bin_sizes() -> impl Strategy + { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + ) + }, + ) + } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } +} From 304b13184c9a66650cdbf0d90afe428c5456e9e1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 09:24:52 +0000 Subject: [PATCH 1630/1995] WIP: Fixing unit tests for tx bins --- .../prepare_proposal/block_space_alloc.rs | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index aead94afba..44df566500 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -254,6 +254,7 @@ mod tests { use proptest::prelude::*; + use super::states::{NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -266,19 +267,6 @@ mod tests { decrypted_txs: Vec, } - /// Check if the sum of all individual tx thresholds does - /// not exceed one. - /// - /// This is important, because we do not want to exceed - /// the maximum block size in Tendermint, and get randomly - /// rejected blocks. - #[test] - fn test_tx_thres_doesnt_exceed_one() { - let sum = - thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert_eq!(sum.to_integer(), 1); - } - proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. @@ -316,7 +304,7 @@ mod tests { // make sure we can't dump any new decrypted txs in the bin assert_eq!( - bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + bins.try_alloc(b"arbitrary tx bytes"), AllocStatus::Rejected ); } @@ -338,49 +326,44 @@ mod tests { encrypted_txs, decrypted_txs, } = args; + + // produce new txs until the moment we would have + // filled up the bins. + // + // iterate over the produced txs to make sure we can keep + // dumping new txs without filling up the bins + let bins = RefCell::new(BlockSpaceAllocator::init( tendermint_max_block_space_in_bytes, )); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in decrypted_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } - // produce new txs until we fill up the bins - // - // TODO: ideally the proptest strategy would already return - // txs whose total added size would be bounded + let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); + for tx in protocol_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } + + let bins = + RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - - // make sure we can keep dumping txs, - // without filling up the bins - for tx in protocol_txs { - assert_eq!( - bins.borrow_mut().try_alloc_protocol_tx(&tx), - AllocStatus::Accepted - ); - } for tx in encrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_encrypted_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in decrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_decrypted_tx(&tx), - AllocStatus::Accepted - ); + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); } } @@ -417,11 +400,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From b97e285bf0b7af6b2ed87d0aa72e6a116a1532d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:10:01 +0000 Subject: [PATCH 1631/1995] Fix tx bins unit tests --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 44df566500..8b45ba18d8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -254,7 +254,7 @@ mod tests { use proptest::prelude::*; - use super::states::{NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; From a934de40f3c9cc8218c73ae409e4cd9b89932739 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:04:12 +0000 Subject: [PATCH 1632/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 8b45ba18d8..378e770035 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -252,6 +252,7 @@ mod thres { mod tests { use std::cell::RefCell; + use assert_matches::assert_matches; use proptest::prelude::*; use super::states::{NextState, NextStateWithEncryptedTxs, State}; @@ -303,9 +304,9 @@ mod tests { bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin - assert_eq!( + assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected + AllocStatus::Rejected { .. } ); } @@ -342,7 +343,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = RefCell::new(bins.into_inner().next_state()); @@ -352,7 +356,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = @@ -363,7 +370,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } } From a259bc306d3798422140594dac4d9e2c71bbaf88 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 17 Nov 2022 17:32:43 +0000 Subject: [PATCH 1633/1995] Sort by voting power then by address, when ABI encoding voting powers map --- shared/src/proto/types.rs | 6 +- .../vote_extensions/validator_set_update.rs | 97 +++++++++++++++++-- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 5097d6a0be..0957b5fc41 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -52,8 +52,10 @@ pub trait SignedSerialize { /// A byte vector containing the serialized data. type Output: AsRef<[u8]>; - /// Encodes `data` as a byte vector, - /// with some arbitrary serialization method. + /// Encodes `data` as a byte vector, with some arbitrary serialization + /// method. The returned output *must* be deterministic based on + /// `data`, so that two callers signing the same `data` will be + /// signing the same `Self::Output`. fn serialize(data: &T) -> Self::Output; } diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 74e634ab68..c0c6bcc471 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -1,5 +1,6 @@ //! Contains types necessary for processing validator set updates //! in vote extensions. +use std::cmp::Ordering; use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -157,12 +158,16 @@ pub struct EthAddrBook { /// Provides a mapping between [`EthAddress`] and [`VotingPower`] instances. pub type VotingPowersMap = HashMap; -/// This trait contains additional methods for a [`HashMap`], related +/// This trait contains additional methods for a [`VotingPowersMap`], related /// with validator set update vote extensions logic. pub trait VotingPowersMapExt { /// Returns the list of Ethereum validator hot and cold addresses and their - /// respective voting power (in this order), with an Ethereum ABI - /// compatible encoding. + /// respective voting powers (in this order), with an Ethereum ABI + /// compatible encoding. Implementations of this method must be + /// deterministic based on `self`. In addition, the returned `Vec`s must be + /// sorted in descending order by voting power, as this is more efficient to + /// deal with on the Ethereum side when working out if there is enough + /// voting power for a given validator set update. fn get_abi_encoded(&self) -> (Vec, Vec, Vec); /// Returns the keccak hashes of this [`VotingPowersMap`], @@ -192,17 +197,29 @@ pub trait VotingPowersMapExt { } } +/// Compare two items of [`VotingPowersMap`]. This comparison operation must +/// match the equivalent comparison operation in Ethereum bridge code. +fn compare_voting_powers_map_items( + first: &(&EthAddrBook, &VotingPower), + second: &(&EthAddrBook, &VotingPower), +) -> Ordering { + let (first_power, second_power) = (first.1, second.1); + let (first_addr, second_addr) = (first.0, second.0); + match second_power.cmp(first_power) { + Ordering::Less => Ordering::Less, + Ordering::Equal => first_addr.cmp(second_addr), + Ordering::Greater => Ordering::Greater, + } +} + impl VotingPowersMapExt for VotingPowersMap { fn get_abi_encoded(&self) -> (Vec, Vec, Vec) { - // get addresses and voting powers all into one vec + // get addresses and voting powers all into one vec so that they can be + // sorted appropriately let mut unsorted: Vec<_> = self.iter().collect(); - - // sort it by voting power, in descending order - unsorted.sort_by(|&(_, ref power_1), &(_, ref power_2)| { - power_2.cmp(power_1) - }); - + unsorted.sort_by(compare_voting_powers_map_items); let sorted = unsorted; + let total_voting_power: u64 = sorted .iter() .map(|&(_, &voting_power)| u64::from(voting_power)) @@ -370,4 +387,64 @@ mod tests { assert_eq!(&hex::encode(got), EXPECTED); } + + /// Checks that comparing two [`VotingPowersMap`] items which have the same + /// voting powers but different [`EthAddrBook`]s does not result in them + /// being regarded as equal. + #[test] + fn test_compare_voting_powers_map_items_identical_voting_powers() { + let same_voting_power = 200.into(); + + let validator_a = EthAddrBook { + hot_key_addr: EthAddress([0; 20]), + cold_key_addr: EthAddress([0; 20]), + }; + let validator_b = EthAddrBook { + hot_key_addr: EthAddress([1; 20]), + cold_key_addr: EthAddress([1; 20]), + }; + + assert_eq!( + compare_voting_powers_map_items( + &(&validator_a, &same_voting_power), + &(&validator_b, &same_voting_power), + ), + Ordering::Less + ); + } + + /// Checks that [`VotingPowersMapExt::get_abi_encoded`] gives a + /// deterministic result in the case where there are multiple validators + /// with the same voting power. + /// + /// NB: this test may pass even if the implementation is not + /// deterministic unless the test is run with the `--release` profile, as it + /// is implicitly relying on how iterating over a [`HashMap`] seems to + /// return items in the order in which they were inserted, at least for this + /// very small 2-item example. + #[test] + fn test_voting_powers_map_get_abi_encoded_deterministic_with_identical_voting_powers() + { + let validator_a = EthAddrBook { + hot_key_addr: EthAddress([0; 20]), + cold_key_addr: EthAddress([0; 20]), + }; + let validator_b = EthAddrBook { + hot_key_addr: EthAddress([1; 20]), + cold_key_addr: EthAddress([1; 20]), + }; + let same_voting_power = 200.into(); + + let mut voting_powers_1 = VotingPowersMap::default(); + voting_powers_1.insert(validator_a.clone(), same_voting_power); + voting_powers_1.insert(validator_b.clone(), same_voting_power); + + let mut voting_powers_2 = VotingPowersMap::default(); + voting_powers_2.insert(validator_b, same_voting_power); + voting_powers_2.insert(validator_a, same_voting_power); + + let x = voting_powers_1.get_abi_encoded(); + let y = voting_powers_2.get_abi_encoded(); + assert_eq!(x, y); + } } From 72dee5cb7d24f29f6e396175e5f371292adf9b04 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:55:16 +0000 Subject: [PATCH 1634/1995] Rebase fixes --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 10 +++++----- .../block_space_alloc/states/protocol_txs.rs | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 378e770035..ab4ceb85b2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -255,7 +255,7 @@ mod tests { use assert_matches::assert_matches; use proptest::prelude::*; - use super::states::{NextState, NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -300,7 +300,7 @@ mod tests { BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.occupied_space_in_bytes = bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin @@ -339,7 +339,7 @@ mod tests { )); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { @@ -352,7 +352,7 @@ mod tests { let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { @@ -366,7 +366,7 @@ mod tests { RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index b4ce10e956..e563a6fe3b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,6 +6,14 @@ use super::{ TryAllocBatch, WithEncryptedTxs, WithoutEncryptedTxs, }; +#[cfg(test)] +impl super::TryAlloc for BlockSpaceAllocator { + #[inline] + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + self.protocol_txs.try_dump(tx) + } +} + impl TryAllocBatch for BlockSpaceAllocator { #[inline] fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> From c78376450e4b30dc595e5889aa2ff120ae558509 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 14:22:50 +0000 Subject: [PATCH 1635/1995] Implement LazyProposedTxSet --- .../prepare_proposal/block_space_alloc.rs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index ab4ceb85b2..5e1d5a04e1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -50,12 +50,67 @@ pub mod states; // the total gas of all chosen txs cannot exceed the configured max // gas per block, otherwise a proposal will be rejected! +use std::collections::BTreeMap; use std::marker::PhantomData; use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +/// The storage unit for the bits in a [`LazyProposedTxSet`]. +#[allow(dead_code)] +type LazyProposedTxSetStorage = u128; + +/// The width, in bytes, of the storage unit for a [`LazyProposedTxSet`]. +#[allow(dead_code)] +const LAZY_PROPOSED_TX_SET_STORAGE_WIDTH: usize = + std::mem::size_of::(); + +/// A set of transaction indices that have been included in some block proposal. +#[derive(Default, Debug, Clone)] +#[allow(dead_code)] +pub struct LazyProposedTxSet { + /// Map of transactions indices, in a `RequestPrepareProposal`, to + /// bit vectors, containing the actual boolean values to be asserted. + /// + /// If the bit `B` is set, at the bit vector with index `S`, then the + /// transaction `LAZY_PROPOSED_TX_SET_STORAGE_WIDTH * S + B` is + /// included in the proposal. + bit_sets: BTreeMap, +} + +impl LazyProposedTxSet { + /// Add a new transaction index to this [`LazyProposedTxSet`]. + #[allow(dead_code)] + pub fn include_tx_index(&mut self, index: usize) { + // theset let exprs will get optimized into a single op, + // since they're ordered in sequence, which is nice + let map_index = index / LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; + let bit_set_index = index % LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; + + let set = self.bit_sets.entry(map_index).or_insert(0); + *set |= 1 << bit_set_index; + } + + /// Return an iterator over the transaction indices in + /// this [`LazyProposedTxSet`], in ascending order. + #[allow(dead_code)] + #[inline] + pub fn iter(&self) -> impl Iterator + '_ { + self.bit_sets.iter().flat_map(|(&map_index, &set)| { + (0..LAZY_PROPOSED_TX_SET_STORAGE_WIDTH) + .into_iter() + .flat_map(move |bit_set_index| { + let is_bit_set = (set & (1 << bit_set_index)) != 0; + is_bit_set.then(|| { + map_index as usize * LAZY_PROPOSED_TX_SET_STORAGE_WIDTH + + bit_set_index as usize + }) + }) + }) + } +} + /// All status responses from trying to allocate block space for a tx. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum AllocStatus<'tx> { @@ -268,6 +323,32 @@ mod tests { decrypted_txs: Vec, } + /// Test [`LazyProposedTxSet`] index insert ops. + #[test] + fn test_lazy_proposed_tx_set_insert() { + let mut set = LazyProposedTxSet::default(); + let mut indices = vec![1, 4, 6, 3, 1, 100, 123, 12, 3]; + + // insert some elements into the set + for i in indices.iter().copied() { + set.include_tx_index(i); + } + + // check if the set contains the same elements + // we inserted, in ascending order + indices.sort_unstable(); + indices.dedup(); + + let set_indices: Vec<_> = set.iter().collect(); + assert_eq!(indices, set_indices); + + // check that the no. of storage elements used is lower + // than the max no. of bitsets we would otherwise need + let storage_elements_max = + indices[indices.len() - 1] / LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; + assert!(set.bit_sets.len() <= storage_elements_max); + } + proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. From 745ee4fe5f74a2181158c48c80ce823754b7d183 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 18 Nov 2022 14:04:51 +0000 Subject: [PATCH 1636/1995] Add test_compare_voting_powers_map_items_different_voting_powers --- .../vote_extensions/validator_set_update.rs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index c0c6bcc471..0ff7c78a99 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -413,6 +413,31 @@ mod tests { ); } + /// Checks that comparing two [`VotingPowersMap`] items with different + /// voting powers results in the item with the lesser voting power being + /// regarded as "greater". + #[test] + fn test_compare_voting_powers_map_items_different_voting_powers() { + let validator_a = EthAddrBook { + hot_key_addr: EthAddress([0; 20]), + cold_key_addr: EthAddress([0; 20]), + }; + let validator_a_voting_power = 200.into(); + let validator_b = EthAddrBook { + hot_key_addr: EthAddress([1; 20]), + cold_key_addr: EthAddress([1; 20]), + }; + let validator_b_voting_power = 100.into(); + + assert_eq!( + compare_voting_powers_map_items( + &(&validator_a, &validator_a_voting_power), + &(&validator_b, &validator_b_voting_power), + ), + Ordering::Less + ); + } + /// Checks that [`VotingPowersMapExt::get_abi_encoded`] gives a /// deterministic result in the case where there are multiple validators /// with the same voting power. From 761593970824cc5bc20ec61636b2beebd5e9e113 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 21 Nov 2022 09:42:20 +0000 Subject: [PATCH 1637/1995] Update shared/src/types/vote_extensions/validator_set_update.rs Co-authored-by: Tiago Carvalho --- shared/src/types/vote_extensions/validator_set_update.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 0ff7c78a99..e9235b8e6f 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -206,9 +206,8 @@ fn compare_voting_powers_map_items( let (first_power, second_power) = (first.1, second.1); let (first_addr, second_addr) = (first.0, second.0); match second_power.cmp(first_power) { - Ordering::Less => Ordering::Less, Ordering::Equal => first_addr.cmp(second_addr), - Ordering::Greater => Ordering::Greater, + ordering => ordering, } } From ce92a709fabbda79c398120eb33ff518e767f305 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 21 Nov 2022 10:00:21 +0000 Subject: [PATCH 1638/1995] Split SignedSerialize::serialize docstring into paragraphs --- shared/src/proto/types.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 0957b5fc41..4888488c8e 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -53,7 +53,9 @@ pub trait SignedSerialize { type Output: AsRef<[u8]>; /// Encodes `data` as a byte vector, with some arbitrary serialization - /// method. The returned output *must* be deterministic based on + /// method. + /// + /// The returned output *must* be deterministic based on /// `data`, so that two callers signing the same `data` will be /// signing the same `Self::Output`. fn serialize(data: &T) -> Self::Output; From d1808565219ecb776d7e014351367506099fd575 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:14:17 +0000 Subject: [PATCH 1639/1995] Move index set to shared --- .../prepare_proposal/block_space_alloc.rs | 81 --------------- shared/src/types/index_set.rs | 99 +++++++++++++++++++ shared/src/types/mod.rs | 1 + 3 files changed, 100 insertions(+), 81 deletions(-) create mode 100644 shared/src/types/index_set.rs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 5e1d5a04e1..ab4ceb85b2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -50,67 +50,12 @@ pub mod states; // the total gas of all chosen txs cannot exceed the configured max // gas per block, otherwise a proposal will be rejected! -use std::collections::BTreeMap; use std::marker::PhantomData; use num_rational::Ratio; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; -/// The storage unit for the bits in a [`LazyProposedTxSet`]. -#[allow(dead_code)] -type LazyProposedTxSetStorage = u128; - -/// The width, in bytes, of the storage unit for a [`LazyProposedTxSet`]. -#[allow(dead_code)] -const LAZY_PROPOSED_TX_SET_STORAGE_WIDTH: usize = - std::mem::size_of::(); - -/// A set of transaction indices that have been included in some block proposal. -#[derive(Default, Debug, Clone)] -#[allow(dead_code)] -pub struct LazyProposedTxSet { - /// Map of transactions indices, in a `RequestPrepareProposal`, to - /// bit vectors, containing the actual boolean values to be asserted. - /// - /// If the bit `B` is set, at the bit vector with index `S`, then the - /// transaction `LAZY_PROPOSED_TX_SET_STORAGE_WIDTH * S + B` is - /// included in the proposal. - bit_sets: BTreeMap, -} - -impl LazyProposedTxSet { - /// Add a new transaction index to this [`LazyProposedTxSet`]. - #[allow(dead_code)] - pub fn include_tx_index(&mut self, index: usize) { - // theset let exprs will get optimized into a single op, - // since they're ordered in sequence, which is nice - let map_index = index / LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; - let bit_set_index = index % LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; - - let set = self.bit_sets.entry(map_index).or_insert(0); - *set |= 1 << bit_set_index; - } - - /// Return an iterator over the transaction indices in - /// this [`LazyProposedTxSet`], in ascending order. - #[allow(dead_code)] - #[inline] - pub fn iter(&self) -> impl Iterator + '_ { - self.bit_sets.iter().flat_map(|(&map_index, &set)| { - (0..LAZY_PROPOSED_TX_SET_STORAGE_WIDTH) - .into_iter() - .flat_map(move |bit_set_index| { - let is_bit_set = (set & (1 << bit_set_index)) != 0; - is_bit_set.then(|| { - map_index as usize * LAZY_PROPOSED_TX_SET_STORAGE_WIDTH - + bit_set_index as usize - }) - }) - }) - } -} - /// All status responses from trying to allocate block space for a tx. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum AllocStatus<'tx> { @@ -323,32 +268,6 @@ mod tests { decrypted_txs: Vec, } - /// Test [`LazyProposedTxSet`] index insert ops. - #[test] - fn test_lazy_proposed_tx_set_insert() { - let mut set = LazyProposedTxSet::default(); - let mut indices = vec![1, 4, 6, 3, 1, 100, 123, 12, 3]; - - // insert some elements into the set - for i in indices.iter().copied() { - set.include_tx_index(i); - } - - // check if the set contains the same elements - // we inserted, in ascending order - indices.sort_unstable(); - indices.dedup(); - - let set_indices: Vec<_> = set.iter().collect(); - assert_eq!(indices, set_indices); - - // check that the no. of storage elements used is lower - // than the max no. of bitsets we would otherwise need - let storage_elements_max = - indices[indices.len() - 1] / LAZY_PROPOSED_TX_SET_STORAGE_WIDTH; - assert!(set.bit_sets.len() <= storage_elements_max); - } - proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. diff --git a/shared/src/types/index_set.rs b/shared/src/types/index_set.rs new file mode 100644 index 0000000000..e061489f61 --- /dev/null +++ b/shared/src/types/index_set.rs @@ -0,0 +1,99 @@ +//! Set data structure optimized to store [`usize`] values. + +use std::collections::BTreeMap; + +/// The storage unit for the bits in an [`IndexSet`]. +#[allow(dead_code)] +type IndexSetStorage = u128; + +/// The width, in bytes, of the storage unit for an [`IndexSet`]. +#[allow(dead_code)] +const INDEX_SET_STORAGE_WIDTH: usize = std::mem::size_of::(); + +/// Set data structure optimized to store [`usize`] values. +#[derive(Default, Debug, Clone)] +#[allow(dead_code)] +pub struct IndexSet { + /// Map of indices to bit vectors, containing the actual boolean + /// values to be asserted. + /// + /// If the bit `B` is set, at the bit vector with index `S`, then + /// the index `INDEX_SET_STORAGE_WIDTH * S + B` is in the set. + bit_sets: BTreeMap, +} + +impl IndexSet { + /// Add a new index to this [`IndexSet`]. + #[allow(dead_code)] + pub fn insert(&mut self, index: usize) { + // theset let exprs will get optimized into a single op, + // since they're ordered in sequence, which is nice + let map_index = index / INDEX_SET_STORAGE_WIDTH; + let bit_set_index = index % INDEX_SET_STORAGE_WIDTH; + + let set = self.bit_sets.entry(map_index).or_insert(0); + *set |= 1 << bit_set_index; + } + + /// Return an iterator over the transaction indices in + /// this [`IndexSet`], in ascending order. + #[allow(dead_code)] + #[inline] + pub fn iter(&self) -> impl Iterator + '_ { + self.bit_sets.iter().flat_map(|(&map_index, &set)| { + (0..INDEX_SET_STORAGE_WIDTH).into_iter().flat_map( + move |bit_set_index| { + let is_bit_set = (set & (1 << bit_set_index)) != 0; + is_bit_set.then(|| { + map_index as usize * INDEX_SET_STORAGE_WIDTH + + bit_set_index as usize + }) + }, + ) + }) + } + + /// Merge two [`IndexSet`] instances. + /// + /// Corresponds to a mutating union set operation, + /// between `self` and `other`. + #[allow(dead_code)] + #[inline] + pub fn merge(&mut self, other: IndexSet) { + for (&map_index, &other_set) in other.bit_sets.iter() { + let set = self.bit_sets.entry(map_index).or_insert(0); + *set |= other_set; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test [`IndexSet`] index insert ops. + #[test] + fn test_index_set_insert() { + let mut set = IndexSet::default(); + let mut indices = vec![1, 4, 6, 3, 1, 100, 123, 12, 3]; + + // insert some elements into the set + for i in indices.iter().copied() { + set.insert(i); + } + + // check if the set contains the same elements + // we inserted, in ascending order + indices.sort_unstable(); + indices.dedup(); + + let set_indices: Vec<_> = set.iter().collect(); + assert_eq!(indices, set_indices); + + // check that the no. of storage elements used is lower + // than the max no. of bitsets we would otherwise need + let storage_elements_max = + indices[indices.len() - 1] / INDEX_SET_STORAGE_WIDTH; + assert!(set.bit_sets.len() <= storage_elements_max); + } +} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 0a5ac9a6b6..3b6eacd4bd 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -9,6 +9,7 @@ pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; +pub mod index_set; pub mod internal; pub mod keccak; pub mod key; From cb2b6f2d57137223cd1d3c68d5b68ea03504d9d9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:17:38 +0000 Subject: [PATCH 1640/1995] Pass IndexSet as a reference --- shared/src/types/index_set.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/index_set.rs b/shared/src/types/index_set.rs index e061489f61..d669649a74 100644 --- a/shared/src/types/index_set.rs +++ b/shared/src/types/index_set.rs @@ -59,7 +59,7 @@ impl IndexSet { /// between `self` and `other`. #[allow(dead_code)] #[inline] - pub fn merge(&mut self, other: IndexSet) { + pub fn merge(&mut self, other: &IndexSet) { for (&map_index, &other_set) in other.bit_sets.iter() { let set = self.bit_sets.entry(map_index).or_insert(0); *set |= other_set; From 10e1306e133fa8b8182dd784e22380298c79162b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:24:33 +0000 Subject: [PATCH 1641/1995] Revert "Revert tx bins PrepareProposal changes" This reverts commit 6fc2d4689ac7953005a6de565a8a24d3388ff6f0. --- .../lib/node/ledger/shell/prepare_proposal.rs | 105 +++++++++++++----- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5a6542e084..4ef19f13b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,6 +12,7 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; +use self::tx_bins::{AllocStatus, TxAllottedSpace}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -39,7 +40,7 @@ where /// the proposal is rejected (unless we can simply overwrite /// them in the next block). // TODO: change second paragraph of the docstr, to reflect new - // alloted space per block design + // allotted space per block design pub fn prepare_proposal( &mut self, req: RequestPrepareProposal, @@ -51,21 +52,38 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // TODO: add some info logging? + // start counting allotted space for txs + let mut bins = TxAllottedSpace::from(&req); + + // NOTE: AD-HOC SOLUTION + // ====================== + // TODO: choose txs in this order: + // - decrypted txs (ALL OF THEM) + // - protocol txs (we should give priority to valset upds) + // - encrypted txs (it's fine if this bin is empty) + // + // at the beginning of an epoch, do not pick any + // encrypted txs :) inspired by solana + // + // `tracing::warn!()` log we are not accepting encrypted + // txs for a given block height + // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] - let txs = self.build_vote_extensions_txs(req.local_last_commit); + let txs = self + .build_vote_extensions_txs(&mut bins, req.local_last_commit); #[cfg(not(feature = "abcipp"))] - let mut txs = self.build_vote_extensions_txs(&req.txs); + let mut txs = self.build_vote_extensions_txs(&mut bins, &req.txs); #[cfg(feature = "abcipp")] let mut txs: Vec = txs.into_iter().map(record::add).collect(); // add mempool txs - let mut mempool_txs = self.build_mempool_txs(req.txs); + let mut mempool_txs = self.build_mempool_txs(&mut bins, req.txs); txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(); + let decrypted_txs = self.build_decrypted_txs(&mut bins); #[cfg(feature = "abcipp")] let decrypted_txs: Vec = decrypted_txs.into_iter().map(record::add).collect(); @@ -100,6 +118,7 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, + bins: &mut TxAllottedSpace, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -153,7 +172,7 @@ where .get_protocol_key() .expect("Validators should always have a protocol key"); - iter_protocol_txs(VoteExtensionDigest { + let txs: Vec<_> = iter_protocol_txs(VoteExtensionDigest { ethereum_events, validator_set_update, }) @@ -161,37 +180,51 @@ where // TODO(feature = "abcipp"): remove this later, when we get rid of // `abciplus` .chain(protocol_txs.into_iter()) - .collect() + .collect(); + + match bins.try_alloc_protocol_tx_batch(txs.iter().map(Vec::as_slice)) { + AllocStatus::Accepted => txs, + AllocStatus::Rejected => { + // no space left for tx batch, so we + // do not include any protocol tx in + // this block + // + // TODO: maybe we should find a way to include + // validator set updates all the time. for instance, + // we could have recursive bins -> bin space within + // a bin is partitioned into yet more bins. so, we + // could have, say, 2/3 of the bin space available + // for eth events, and 1/3 available for valset + // upds + vec![] + } + AllocStatus::OverflowsBin => { + // TODO: handle tx whose size is greater + // than bin size + vec![] + } + } } /// Builds a batch of mempool transactions #[cfg(feature = "abcipp")] - fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + txs.len() / 2; - 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() + fn build_mempool_txs( + &mut self, + _bins: &mut TxAllottedSpace, + txs: Vec>, + ) -> Vec { + // TODO(feature = "abcipp"): implement building batch of mempool txs + todo!() } /// Builds a batch of mempool transactions #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + txs.len() / 2; + fn build_mempool_txs( + &mut self, + bins: &mut TxAllottedSpace, + txs: Vec>, + ) -> Vec { txs.into_iter() - .take(number_of_new_txs) .filter_map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) @@ -201,6 +234,10 @@ where None } }) + // TODO: handle bin overflows + .take_while(|tx_bytes| { + bins.try_alloc_encrypted_tx(&*tx_bytes) == AllocStatus::Accepted + }) .collect() } @@ -212,9 +249,13 @@ where // sources: // - https://specs.anoma.net/main/releases/v2.html // - https://github.com/anoma/ferveo - fn build_decrypted_txs(&mut self) -> Vec { + fn build_decrypted_txs( + &mut self, + bins: &mut TxAllottedSpace, + ) -> Vec { // TODO: This should not be hardcoded - let privkey = ::G2Affine::prime_subgroup_generator(); + let privkey = + ::G2Affine::prime_subgroup_generator(); self.storage .tx_queue @@ -226,6 +267,10 @@ where }) .to_bytes() }) + // TODO: handle bin overflows + .take_while(|tx_bytes| { + bins.try_alloc_decrypted_tx(&*tx_bytes) == AllocStatus::Accepted + }) .collect() } } From 21f810a9b5aee9bf40c17ebb4f6960d87738c174 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:47:48 +0000 Subject: [PATCH 1642/1995] WIP: Add block space allocator to PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4ef19f13b3..9bebe1bec9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -12,7 +12,11 @@ use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; -use self::tx_bins::{AllocStatus, TxAllottedSpace}; +use self::block_space_alloc::states::{ + BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, + BuildingProtocolTxBatch, State, +}; +use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -53,7 +57,7 @@ where // TODO: add some info logging? // start counting allotted space for txs - let mut bins = TxAllottedSpace::from(&req); + let mut alloc = BlockSpaceAllocator::from(&req); // NOTE: AD-HOC SOLUTION // ====================== @@ -68,28 +72,29 @@ where // `tracing::warn!()` log we are not accepting encrypted // txs for a given block height + // decrypt the wrapper txs included in the previous block + let decrypted_txs = self.build_decrypted_txs(&mut alloc); + #[cfg(feature = "abcipp")] + let decrypted_txs: Vec = + decrypted_txs.into_iter().map(record::add).collect(); + let mut txs = decrypted_txs; + // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] - let txs = self - .build_vote_extensions_txs(&mut bins, req.local_last_commit); + let protocol_txs = self + .build_vote_extensions_txs(&mut alloc, req.local_last_commit); #[cfg(not(feature = "abcipp"))] - let mut txs = self.build_vote_extensions_txs(&mut bins, &req.txs); + let mut protocol_txs = + self.build_vote_extensions_txs(&mut alloc, &req.txs); #[cfg(feature = "abcipp")] - let mut txs: Vec = - txs.into_iter().map(record::add).collect(); + let mut protocol_txs: Vec = + protocol_txs.into_iter().map(record::add).collect(); + txs.append(&mut protocol_txs); // add mempool txs - let mut mempool_txs = self.build_mempool_txs(&mut bins, req.txs); + let mut mempool_txs = self.build_mempool_txs(&mut alloc, req.txs); txs.append(&mut mempool_txs); - // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(&mut bins); - #[cfg(feature = "abcipp")] - let decrypted_txs: Vec = - decrypted_txs.into_iter().map(record::add).collect(); - let mut decrypted_txs = decrypted_txs; - txs.append(&mut decrypted_txs); - txs } else { vec![] @@ -118,7 +123,7 @@ where /// events and, optionally, a validator set update fn build_vote_extensions_txs( &mut self, - bins: &mut TxAllottedSpace, + alloc: &mut BlockSpaceAllocator, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -182,7 +187,7 @@ where .chain(protocol_txs.into_iter()) .collect(); - match bins.try_alloc_protocol_tx_batch(txs.iter().map(Vec::as_slice)) { + match alloc.try_alloc_batch(txs.iter().map(Vec::as_slice)) { AllocStatus::Accepted => txs, AllocStatus::Rejected => { // no space left for tx batch, so we @@ -208,9 +213,9 @@ where /// Builds a batch of mempool transactions #[cfg(feature = "abcipp")] - fn build_mempool_txs( + fn build_mempool_txs( &mut self, - _bins: &mut TxAllottedSpace, + _alloc: &mut BlockSpaceAllocator>, txs: Vec>, ) -> Vec { // TODO(feature = "abcipp"): implement building batch of mempool txs @@ -219,9 +224,9 @@ where /// Builds a batch of mempool transactions #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs( + fn build_mempool_txs( &mut self, - bins: &mut TxAllottedSpace, + alloc: &mut BlockSpaceAllocator>, txs: Vec>, ) -> Vec { txs.into_iter() @@ -236,7 +241,7 @@ where }) // TODO: handle bin overflows .take_while(|tx_bytes| { - bins.try_alloc_encrypted_tx(&*tx_bytes) == AllocStatus::Accepted + alloc.try_alloc(&*tx_bytes) == AllocStatus::Accepted }) .collect() } @@ -251,7 +256,7 @@ where // - https://github.com/anoma/ferveo fn build_decrypted_txs( &mut self, - bins: &mut TxAllottedSpace, + alloc: &mut BlockSpaceAllocator, ) -> Vec { // TODO: This should not be hardcoded let privkey = @@ -268,8 +273,9 @@ where .to_bytes() }) // TODO: handle bin overflows + // TODO: all txs should be accepted .take_while(|tx_bytes| { - bins.try_alloc_decrypted_tx(&*tx_bytes) == AllocStatus::Accepted + alloc.try_alloc(&*tx_bytes) == AllocStatus::Accepted }) .collect() } From ffeb9f12476c7b8468ab82ebbd285c191b02f96d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 11:17:21 +0000 Subject: [PATCH 1643/1995] Transition to new states in block space allocator --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9bebe1bec9..1884f52eb4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,7 +14,7 @@ use namada::types::vote_extensions::VoteExtensionDigest; use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, - BuildingProtocolTxBatch, State, + BuildingProtocolTxBatch, NextState, NextStateWithEncryptedTxs, State, }; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; use super::super::*; @@ -80,6 +80,7 @@ where let mut txs = decrypted_txs; // add ethereum events and validator set updates as protocol txs + let mut alloc = alloc.next_state(); #[cfg(feature = "abcipp")] let protocol_txs = self .build_vote_extensions_txs(&mut alloc, req.local_last_commit); @@ -92,6 +93,8 @@ where txs.append(&mut protocol_txs); // add mempool txs + // TODO: check if we can add encrypted txs or not + let mut alloc = alloc.next_state_with_encrypted_txs(); let mut mempool_txs = self.build_mempool_txs(&mut alloc, req.txs); txs.append(&mut mempool_txs); @@ -217,7 +220,10 @@ where &mut self, _alloc: &mut BlockSpaceAllocator>, txs: Vec>, - ) -> Vec { + ) -> Vec + where + BlockSpaceAllocator>: State, + { // TODO(feature = "abcipp"): implement building batch of mempool txs todo!() } @@ -228,7 +234,10 @@ where &mut self, alloc: &mut BlockSpaceAllocator>, txs: Vec>, - ) -> Vec { + ) -> Vec + where + BlockSpaceAllocator>: State, + { txs.into_iter() .filter_map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = From 30227a77c30e677ac5fcefbaa6f5ead369adf159 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 14:06:55 +0000 Subject: [PATCH 1644/1995] WIP: Fix PrepareProposal unit tests --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1884f52eb4..7dfc800191 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -428,7 +428,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), txs: vec![non_wrapper_tx.to_bytes()], - max_tx_bytes: 0, + max_tx_bytes: i64::MAX, ..Default::default() }; #[cfg(feature = "abcipp")] @@ -993,7 +993,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), txs: vec![wrapper.clone()], - max_tx_bytes: 0, + max_tx_bytes: i64::MAX, ..Default::default() }; #[cfg(feature = "abcipp")] @@ -1031,7 +1031,7 @@ mod test_prepare_proposal { let mut req = RequestPrepareProposal { txs: vec![], - max_tx_bytes: 0, + max_tx_bytes: i64::MAX, ..Default::default() }; // create a request with two new wrappers from mempool and From 6c70564e87a765418ca5da22036e4e35df5a7180 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 15:45:57 +0000 Subject: [PATCH 1645/1995] Fix most PrepareProposal unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7dfc800191..741bfbe92e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -209,6 +209,7 @@ where AllocStatus::OverflowsBin => { // TODO: handle tx whose size is greater // than bin size + // TODO: tracing warn vec![] } } @@ -249,8 +250,13 @@ where } }) // TODO: handle bin overflows - .take_while(|tx_bytes| { - alloc.try_alloc(&*tx_bytes) == AllocStatus::Accepted + .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { + AllocStatus::Accepted => true, + AllocStatus::Rejected => false, + AllocStatus::OverflowsBin => { + // TODO: tracing warn + false + } }) .collect() } @@ -283,8 +289,13 @@ where }) // TODO: handle bin overflows // TODO: all txs should be accepted - .take_while(|tx_bytes| { - alloc.try_alloc(&*tx_bytes) == AllocStatus::Accepted + .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { + AllocStatus::Accepted => true, + AllocStatus::Rejected => false, + AllocStatus::OverflowsBin => { + // TODO: tracing warn + false + } }) .collect() } @@ -711,6 +722,7 @@ mod test_prepare_proposal { let mut rsp = shell.prepare_proposal(RequestPrepareProposal { local_last_commit: Some(ExtendedCommitInfo { + max_tx_bytes: i64::MAX, votes: vec![vote], ..Default::default() }), @@ -791,6 +803,7 @@ mod test_prepare_proposal { .sign(&protocol_key) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { + max_tx_bytes: i64::MAX, txs: vec![tx], ..Default::default() }); @@ -912,6 +925,7 @@ mod test_prepare_proposal { }; // this should panic shell.prepare_proposal(RequestPrepareProposal { + max_tx_bytes: i64::MAX, local_last_commit: Some(ExtendedCommitInfo { votes: vec![vote], ..Default::default() @@ -925,6 +939,7 @@ mod test_prepare_proposal { .sign(&protocol_key) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { + max_tx_bytes: i64::MAX, txs: vec![vote], ..Default::default() }); From bf8768074fd51dee86257099f6f143d55cbcd193 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:14:41 +0000 Subject: [PATCH 1646/1995] Log tx accepted/rejected status during block proposals --- .../lib/node/ledger/shell/prepare_proposal.rs | 70 +++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 741bfbe92e..624e9ce2dd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -192,7 +192,7 @@ where match alloc.try_alloc_batch(txs.iter().map(Vec::as_slice)) { AllocStatus::Accepted => txs, - AllocStatus::Rejected => { + AllocStatus::Rejected { tx_len, space_left } => { // no space left for tx batch, so we // do not include any protocol tx in // this block @@ -204,12 +204,25 @@ where // could have, say, 2/3 of the bin space available // for eth events, and 1/3 available for valset // upds + tracing::debug!( + tx_len, + space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping protocol tx from the current proposal", + ); vec![] } - AllocStatus::OverflowsBin => { + AllocStatus::OverflowsBin { tx_len, bin_size } => { // TODO: handle tx whose size is greater // than bin size - // TODO: tracing warn + tracing::warn!( + tx_len, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large protocol tx from the current proposal", + ); vec![] } } @@ -249,12 +262,28 @@ where None } }) - // TODO: handle bin overflows .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, - AllocStatus::Rejected => false, - AllocStatus::OverflowsBin => { - // TODO: tracing warn + AllocStatus::Rejected { tx_len, space_left } => { + tracing::debug!( + tx_len, + space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping encrypted tx from the current proposal", + ); + false + } + AllocStatus::OverflowsBin { tx_len, bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + tx_len, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large encrypted tx from the current proposal", + ); false } }) @@ -287,13 +316,30 @@ where }) .to_bytes() }) - // TODO: handle bin overflows - // TODO: all txs should be accepted + // TODO: make sure all txs are accepted; .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, - AllocStatus::Rejected => false, - AllocStatus::OverflowsBin => { - // TODO: tracing warn + AllocStatus::Rejected { tx_len, space_left } => { + // TODO: handle rejected txs + tracing::warn!( + tx_len, + space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping decrypted tx from the current proposal", + ); + false + } + AllocStatus::OverflowsBin { tx_len, bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + tx_len, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large decrypted tx from the current proposal", + ); false } }) From 466a9a4e4f80a8a594afa8f3a7a6f5709f21a736 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:40:36 +0000 Subject: [PATCH 1647/1995] Small fixes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 624e9ce2dd..160b90617c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -98,6 +98,10 @@ where let mut mempool_txs = self.build_mempool_txs(&mut alloc, req.txs); txs.append(&mut mempool_txs); + // TODO: fill up remaining space + // TODO: check if we can add encrypted txs or not + let _alloc = alloc.next_state(); + txs } else { vec![] @@ -284,7 +288,7 @@ where ?self.storage.get_current_decision_height(), "Dropping large encrypted tx from the current proposal", ); - false + true } }) .collect() @@ -340,7 +344,7 @@ where ?self.storage.get_current_decision_height(), "Dropping large decrypted tx from the current proposal", ); - false + true } }) .collect() From 417e7f0db08779aa6e611dc5e17a884c5a278e3d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 09:13:41 +0000 Subject: [PATCH 1648/1995] Use correct max Tendermint block size from ABCI specs --- .../lib/node/ledger/shell/prepare_proposal.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 160b90617c..01936f9f37 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -434,6 +434,9 @@ mod test_prepare_proposal { use crate::node::ledger::shims::abcipp_shim_types::shim::request::FinalizeBlock; use crate::wallet; + // https://github.com/tendermint/tendermint/blob/v0.37.x/spec/abci/abci%2B%2B_app_requirements.md#blockparamsmaxbytes + const MAX_TM_BLK_SIZE: i64 = 100 << 20; + #[cfg(feature = "abcipp")] fn get_local_last_commit(shell: &TestShell) -> Option { let evts = { @@ -489,7 +492,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), txs: vec![non_wrapper_tx.to_bytes()], - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, ..Default::default() }; #[cfg(feature = "abcipp")] @@ -772,7 +775,7 @@ mod test_prepare_proposal { let mut rsp = shell.prepare_proposal(RequestPrepareProposal { local_last_commit: Some(ExtendedCommitInfo { - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, votes: vec![vote], ..Default::default() }), @@ -853,7 +856,7 @@ mod test_prepare_proposal { .sign(&protocol_key) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, txs: vec![tx], ..Default::default() }); @@ -975,7 +978,7 @@ mod test_prepare_proposal { }; // this should panic shell.prepare_proposal(RequestPrepareProposal { - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, local_last_commit: Some(ExtendedCommitInfo { votes: vec![vote], ..Default::default() @@ -989,7 +992,7 @@ mod test_prepare_proposal { .sign(&protocol_key) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, txs: vec![vote], ..Default::default() }); @@ -1058,7 +1061,7 @@ mod test_prepare_proposal { #[cfg(feature = "abcipp")] local_last_commit: get_local_last_commit(&shell), txs: vec![wrapper.clone()], - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, ..Default::default() }; #[cfg(feature = "abcipp")] @@ -1096,7 +1099,7 @@ mod test_prepare_proposal { let mut req = RequestPrepareProposal { txs: vec![], - max_tx_bytes: i64::MAX, + max_tx_bytes: MAX_TM_BLK_SIZE, ..Default::default() }; // create a request with two new wrappers from mempool and From e3493ec04a77cc123f6bdb8a4f6783dc598b5008 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 09:28:36 +0000 Subject: [PATCH 1649/1995] Fix test_decrypted_txs_in_correct_order() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 01936f9f37..af3dec5ef8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1127,13 +1127,13 @@ mod test_prepare_proposal { 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")) + let expected_txs: Vec = expected_decrypted + .into_iter() + .chain(expected_wrapper.into_iter()) + // we extract the inner data from the txs for testing + // equality since otherwise changes in timestamps would + // fail the test + .map(|tx| tx.data.expect("Test failed")) .collect(); #[cfg(feature = "abcipp")] { From 15a0589d6a63802c679472af551bba650c8a1a60 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 09:39:35 +0000 Subject: [PATCH 1650/1995] Fix up frontrunning protection comment on PrepareProposal --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index af3dec5ef8..668272c185 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -295,9 +295,9 @@ where } /// Builds a batch of DKG decrypted transactions - // TODO: we won't have frontrunning protection until V2 of the Anoma - // protocol; Namada runs V1, therefore this method is - // essentially a NOOP, and ought to be removed + // NOTE: we won't have frontrunning protection until V2 of the + // Anoma protocol; Namada runs V1, therefore this method is + // essentially a NOOP // // sources: // - https://specs.anoma.net/main/releases/v2.html From 2028fed3e205000c5503864a05c56d2a774e0b71 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 09:48:14 +0000 Subject: [PATCH 1651/1995] Check if we are at the 2nd height offset within an epoch --- shared/src/ledger/storage/mod.rs | 11 ++++++++--- shared/src/ledger/storage_api/queries.rs | 5 +++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 91b10e5d14..dba0a3d1b2 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1063,13 +1063,18 @@ where } #[cfg(not(feature = "abcipp"))] + #[inline] fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { - // when checking vote extensions in Prepare - // and ProcessProposal, we simply return true if matches!(can_send, SendValsetUpd::AtPrevHeight) { - return true; + // when checking vote extensions in Prepare + // and ProcessProposal, we simply return true + true + } else { + self.is_deciding_2nd_height_offset() } + } + fn is_deciding_2nd_height_offset(&self) -> bool { let current_decision_height = self.get_current_decision_height(); // NOTE: the first stored height in `fst_block_heights_of_each_epoch` diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index 98a725f984..e53a285906 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -126,10 +126,11 @@ pub trait QueriesExt { /// Determines if it is possible to send a validator set update vote /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. - /// - /// This is done by checking if we are at the second block of a new epoch. fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; + /// Check if we are at the second block height offset within an [`Epoch`]. + fn is_deciding_2nd_height_offset(&self) -> bool; + /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch(&self, height: BlockHeight) -> Option; From 201861ab172f0d09ac3745be1fc6631ae3c94e1f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 15:31:13 +0000 Subject: [PATCH 1652/1995] Mark protocol txs for inclusion in a block --- .../lib/node/ledger/shell/prepare_proposal.rs | 19 +++++++++++----- .../lib/node/ledger/shell/vote_extensions.rs | 22 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 668272c185..a4c9128e3d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -16,6 +16,7 @@ use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextState, NextStateWithEncryptedTxs, State, }; +pub use self::block_space_alloc::LazyProposedTxSet; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -58,6 +59,7 @@ where // start counting allotted space for txs let mut alloc = BlockSpaceAllocator::from(&req); + let mut tx_indices = LazyProposedTxSet::default(); // NOTE: AD-HOC SOLUTION // ====================== @@ -82,11 +84,17 @@ where // add ethereum events and validator set updates as protocol txs let mut alloc = alloc.next_state(); #[cfg(feature = "abcipp")] - let protocol_txs = self - .build_vote_extensions_txs(&mut alloc, req.local_last_commit); + let protocol_txs = self.build_vote_extensions_txs( + &mut alloc, + &mut tx_indices, + req.local_last_commit, + ); #[cfg(not(feature = "abcipp"))] - let mut protocol_txs = - self.build_vote_extensions_txs(&mut alloc, &req.txs); + let mut protocol_txs = self.build_vote_extensions_txs( + &mut alloc, + &mut tx_indices, + &req.txs, + ); #[cfg(feature = "abcipp")] let mut protocol_txs: Vec = protocol_txs.into_iter().map(record::add).collect(); @@ -131,6 +139,7 @@ where fn build_vote_extensions_txs( &mut self, alloc: &mut BlockSpaceAllocator, + tx_indices: &mut LazyProposedTxSet, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -157,7 +166,7 @@ where ); #[cfg(not(feature = "abcipp"))] let (protocol_txs, eth_events, valset_upds) = - split_vote_extensions(txs); + split_vote_extensions(tx_indices, txs); // TODO: remove this later, when we get rid of `abciplus` #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 21c394200b..b7e831627e 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -12,6 +12,7 @@ use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, }; +use super::prepare_proposal::LazyProposedTxSet; use super::*; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedVoteInfo; @@ -298,12 +299,13 @@ pub fn deserialize_vote_extensions( /// ones we could deserialize to [`VoteExtension`] /// instances. #[cfg(not(feature = "abcipp"))] -pub fn deserialize_vote_extensions( - txs: &[TxBytes], -) -> impl Iterator + '_ { +pub fn deserialize_vote_extensions<'prep_proposal>( + tx_indices: &'prep_proposal mut LazyProposedTxSet, + txs: &'prep_proposal [TxBytes], +) -> impl Iterator + 'prep_proposal { use namada::types::transaction::protocol::ProtocolTx; - txs.iter().filter_map(|tx_bytes| { + txs.iter().enumerate().filter_map(|(index, tx_bytes)| { let tx = match Tx::try_from(tx_bytes.as_slice()) { Ok(tx) => tx, Err(err) => { @@ -318,7 +320,14 @@ pub fn deserialize_vote_extensions( TxType::Protocol(ProtocolTx { tx: ProtocolTxType::VoteExtension(ext), .. - }) => Some((tx_bytes.clone(), ext)), + }) => { + // mark every protocol tx for inclusion; we shouldn't include + // them in a block without the corresponding digests, so even + // if those get rejected due to space constraints, the + // behavior should be correct + tx_indices.include_tx_index(index); + Some((tx_bytes.clone(), ext)) + } _ => None, } }) @@ -370,6 +379,7 @@ pub fn split_vote_extensions( /// them from Tendermint's mempool. #[cfg(not(feature = "abcipp"))] pub fn split_vote_extensions( + tx_indices: &mut LazyProposedTxSet, mempool_txs: &[TxBytes], ) -> ( Vec, @@ -380,7 +390,7 @@ pub fn split_vote_extensions( let mut eth_evs = vec![]; let mut valset_upds = vec![]; - for (tx, ext) in deserialize_vote_extensions(mempool_txs) { + for (tx, ext) in deserialize_vote_extensions(tx_indices, mempool_txs) { if let Some(validator_set_update) = ext.validator_set_update { valset_upds.push(validator_set_update); } From 7ef88646a402f415a2d1cc149013bd9c06ac5850 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 15:45:12 +0000 Subject: [PATCH 1653/1995] Mark encrypted txs for inclusion --- .../lib/node/ledger/shell/prepare_proposal.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a4c9128e3d..c2968b4b81 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -103,7 +103,8 @@ where // add mempool txs // TODO: check if we can add encrypted txs or not let mut alloc = alloc.next_state_with_encrypted_txs(); - let mut mempool_txs = self.build_mempool_txs(&mut alloc, req.txs); + let mut mempool_txs = + self.build_mempool_txs(&mut alloc, &mut tx_indices, req.txs); txs.append(&mut mempool_txs); // TODO: fill up remaining space @@ -246,6 +247,7 @@ where fn build_mempool_txs( &mut self, _alloc: &mut BlockSpaceAllocator>, + _tx_indices: &mut LazyProposedTxSet, txs: Vec>, ) -> Vec where @@ -260,23 +262,28 @@ where fn build_mempool_txs( &mut self, alloc: &mut BlockSpaceAllocator>, + tx_indices: &mut LazyProposedTxSet, txs: Vec>, ) -> Vec where BlockSpaceAllocator>: State, { txs.into_iter() - .filter_map(|tx_bytes| { + .enumerate() + .filter_map(|(index, tx_bytes)| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) { - Some(tx_bytes) + Some((index, tx_bytes)) } else { None } }) - .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => true, + .take_while(|(index, tx_bytes)| match alloc.try_alloc(&*tx_bytes) { + AllocStatus::Accepted => { + tx_indices.include_tx_index(*index); + true + } AllocStatus::Rejected { tx_len, space_left } => { tracing::debug!( tx_len, @@ -300,6 +307,7 @@ where true } }) + .map(|(_, tx_bytes)| tx_bytes) .collect() } From 30acbade87fa05d96f0a8b3c886c4ea11b66f095 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 16:04:54 +0000 Subject: [PATCH 1654/1995] Replace Vec> with Vec for readability --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c2968b4b81..7963365aba 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -248,7 +248,7 @@ where &mut self, _alloc: &mut BlockSpaceAllocator>, _tx_indices: &mut LazyProposedTxSet, - txs: Vec>, + txs: Vec, ) -> Vec where BlockSpaceAllocator>: State, @@ -263,7 +263,7 @@ where &mut self, alloc: &mut BlockSpaceAllocator>, tx_indices: &mut LazyProposedTxSet, - txs: Vec>, + txs: Vec, ) -> Vec where BlockSpaceAllocator>: State, @@ -1154,7 +1154,7 @@ mod test_prepare_proposal { .collect(); #[cfg(feature = "abcipp")] { - let received: Vec> = shell + let received: Vec = shell .prepare_proposal(req) .tx_records .iter() @@ -1183,7 +1183,7 @@ mod test_prepare_proposal { } #[cfg(not(feature = "abcipp"))] { - let received: Vec> = shell + let received: Vec = shell .prepare_proposal(req) .txs .into_iter() From 4296831f4587a48c708c3e2f3b35806cca7a2064 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 16:08:04 +0000 Subject: [PATCH 1655/1995] Pass request txs by ref to build_mempool_txs() --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7963365aba..4762610c9a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -104,7 +104,7 @@ where // TODO: check if we can add encrypted txs or not let mut alloc = alloc.next_state_with_encrypted_txs(); let mut mempool_txs = - self.build_mempool_txs(&mut alloc, &mut tx_indices, req.txs); + self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); // TODO: fill up remaining space @@ -248,7 +248,7 @@ where &mut self, _alloc: &mut BlockSpaceAllocator>, _tx_indices: &mut LazyProposedTxSet, - txs: Vec, + txs: &[TxBytes], ) -> Vec where BlockSpaceAllocator>: State, @@ -263,18 +263,18 @@ where &mut self, alloc: &mut BlockSpaceAllocator>, tx_indices: &mut LazyProposedTxSet, - txs: Vec, + txs: &[TxBytes], ) -> Vec where BlockSpaceAllocator>: State, { - txs.into_iter() + txs.iter() .enumerate() .filter_map(|(index, tx_bytes)| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) { - Some((index, tx_bytes)) + Some((index, tx_bytes.clone())) } else { None } From aff423c5a375ee8b62c08578ad42f99e4b806e09 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 16:21:04 +0000 Subject: [PATCH 1656/1995] WIP: Filling remaining block space --- .../lib/node/ledger/shell/prepare_proposal.rs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4762610c9a..3057ef905e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -14,7 +14,8 @@ use namada::types::vote_extensions::VoteExtensionDigest; use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, - BuildingProtocolTxBatch, NextState, NextStateWithEncryptedTxs, State, + BuildingProtocolTxBatch, FillingRemainingSpace, NextState, + NextStateWithEncryptedTxs, State, }; pub use self::block_space_alloc::LazyProposedTxSet; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; @@ -107,9 +108,11 @@ where self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); - // TODO: fill up remaining space // TODO: check if we can add encrypted txs or not - let _alloc = alloc.next_state(); + let mut alloc = alloc.next_state(); + let mut remaining_txs = + self.build_remaining_batch(&mut alloc, &tx_indices, req.txs); + txs.append(&mut remaining_txs); txs } else { @@ -136,7 +139,7 @@ where } /// Builds a batch of vote extension transactions, comprised of Ethereum - /// events and, optionally, a validator set update + /// events and, optionally, a validator set update. fn build_vote_extensions_txs( &mut self, alloc: &mut BlockSpaceAllocator, @@ -242,7 +245,7 @@ where } } - /// Builds a batch of mempool transactions + /// Builds a batch of mempool transactions. #[cfg(feature = "abcipp")] fn build_mempool_txs( &mut self, @@ -257,7 +260,7 @@ where todo!() } - /// Builds a batch of mempool transactions + /// Builds a batch of mempool transactions. #[cfg(not(feature = "abcipp"))] fn build_mempool_txs( &mut self, @@ -311,7 +314,7 @@ where .collect() } - /// Builds a batch of DKG decrypted transactions + /// Builds a batch of DKG decrypted transactions. // NOTE: we won't have frontrunning protection until V2 of the // Anoma protocol; Namada runs V1, therefore this method is // essentially a NOOP @@ -366,6 +369,21 @@ where }) .collect() } + + /// Builds a batch of transactions that can fit in the + /// remaining space of the [`BlockSpaceAllocator`]. + fn build_remaining_batch( + &mut self, + _alloc: &mut BlockSpaceAllocator>, + _tx_indices: &LazyProposedTxSet, + _txs: Vec, + ) -> Vec + where + BlockSpaceAllocator>: State, + { + // TODO + vec![] + } } /// Returns a suitable message to be displayed when Tendermint From c65655bd2f7352a7fe587c8b0ac64d648dafb318 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 17 Nov 2022 16:22:51 +0000 Subject: [PATCH 1657/1995] Add a comment above remaining txs batch construction --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3057ef905e..f2bb243428 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -108,6 +108,9 @@ where self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); + // fill up the remaining block space with + // arbitrary transactions that can fit in + // the free space left // TODO: check if we can add encrypted txs or not let mut alloc = alloc.next_state(); let mut remaining_txs = From b366edfc57cdd7195d366392058ac573304f5c9e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 09:15:53 +0000 Subject: [PATCH 1658/1995] Return a list of the remaining mempool txs --- .../lib/node/ledger/shell/prepare_proposal.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f2bb243428..3b58ea12dd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -389,6 +389,28 @@ where } } +/// Return a list of the transactions that haven't +/// been marked for inclusion in the block, yet. +#[allow(dead_code)] +fn get_remaining_txs( + tx_indices: &LazyProposedTxSet, + txs: Vec, +) -> impl Iterator + '_ { + let mut skip_list = tx_indices.iter(); + let mut skip = skip_list.next(); + + txs.into_iter().enumerate().filter_map(move |(index, tx)| { + // this works bc/ tx indices are ordered + // in ascending order + if Some(index) == skip { + skip = skip_list.next(); + Some(tx) + } else { + None + } + }) +} + /// Returns a suitable message to be displayed when Tendermint /// somehow decides on a block containing vote extensions /// reflecting `<= 2/3` of the total stake. From d9b3b06880cb7f19c475b15a3cf3453141907fa1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 09:20:24 +0000 Subject: [PATCH 1659/1995] Build a batch of the remaining txs --- .../lib/node/ledger/shell/prepare_proposal.rs | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3b58ea12dd..d2ab2ae0d3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -377,15 +377,40 @@ where /// remaining space of the [`BlockSpaceAllocator`]. fn build_remaining_batch( &mut self, - _alloc: &mut BlockSpaceAllocator>, - _tx_indices: &LazyProposedTxSet, - _txs: Vec, + alloc: &mut BlockSpaceAllocator>, + tx_indices: &LazyProposedTxSet, + txs: Vec, ) -> Vec where BlockSpaceAllocator>: State, { - // TODO - vec![] + get_remaining_txs(tx_indices, txs) + .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { + AllocStatus::Accepted => true, + AllocStatus::Rejected { tx_len, space_left } => { + tracing::debug!( + tx_len, + space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping tx from the current proposal", + ); + false + } + AllocStatus::OverflowsBin { tx_len, bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + tx_len, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large tx from the current proposal", + ); + true + } + }) + .collect() } } From 2186530ccb33c66af889441c90567cc27b207209 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 09:25:00 +0000 Subject: [PATCH 1660/1995] Remove dead code marker --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d2ab2ae0d3..13b7498706 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -416,7 +416,6 @@ where /// Return a list of the transactions that haven't /// been marked for inclusion in the block, yet. -#[allow(dead_code)] fn get_remaining_txs( tx_indices: &LazyProposedTxSet, txs: Vec, From 02d11187f5e73290ca230cc408f4ec9205b1c8bf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 14:04:32 +0000 Subject: [PATCH 1661/1995] PrepareProposal rebase fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 13b7498706..f40b3d5ca1 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -15,7 +15,7 @@ use namada::types::vote_extensions::VoteExtensionDigest; use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, BuildingProtocolTxBatch, FillingRemainingSpace, NextState, - NextStateWithEncryptedTxs, State, + NextStateWithEncryptedTxs, TryAlloc, TryAllocBatch, }; pub use self::block_space_alloc::LazyProposedTxSet; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; @@ -212,7 +212,7 @@ where match alloc.try_alloc_batch(txs.iter().map(Vec::as_slice)) { AllocStatus::Accepted => txs, - AllocStatus::Rejected { tx_len, space_left } => { + AllocStatus::Rejected { tx, space_left } => { // no space left for tx batch, so we // do not include any protocol tx in // this block @@ -225,7 +225,7 @@ where // for eth events, and 1/3 available for valset // upds tracing::debug!( - tx_len, + ?tx, space_left, proposal_height = ?self.storage.get_current_decision_height(), @@ -233,11 +233,11 @@ where ); vec![] } - AllocStatus::OverflowsBin { tx_len, bin_size } => { + AllocStatus::OverflowsBin { tx, bin_size } => { // TODO: handle tx whose size is greater // than bin size tracing::warn!( - tx_len, + ?tx, bin_size, proposal_height = ?self.storage.get_current_decision_height(), @@ -257,7 +257,7 @@ where txs: &[TxBytes], ) -> Vec where - BlockSpaceAllocator>: State, + BlockSpaceAllocator>: TryAlloc, { // TODO(feature = "abcipp"): implement building batch of mempool txs todo!() @@ -272,7 +272,7 @@ where txs: &[TxBytes], ) -> Vec where - BlockSpaceAllocator>: State, + BlockSpaceAllocator>: TryAlloc, { txs.iter() .enumerate() @@ -290,9 +290,9 @@ where tx_indices.include_tx_index(*index); true } - AllocStatus::Rejected { tx_len, space_left } => { + AllocStatus::Rejected { tx, space_left } => { tracing::debug!( - tx_len, + ?tx, space_left, proposal_height = ?self.storage.get_current_decision_height(), @@ -300,11 +300,11 @@ where ); false } - AllocStatus::OverflowsBin { tx_len, bin_size } => { + AllocStatus::OverflowsBin { tx, bin_size } => { // TODO: handle tx whose size is greater // than bin size tracing::warn!( - tx_len, + ?tx, bin_size, proposal_height = ?self.storage.get_current_decision_height(), @@ -346,10 +346,10 @@ where // TODO: make sure all txs are accepted; .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, - AllocStatus::Rejected { tx_len, space_left } => { + AllocStatus::Rejected { tx, space_left } => { // TODO: handle rejected txs tracing::warn!( - tx_len, + ?tx, space_left, proposal_height = ?self.storage.get_current_decision_height(), @@ -357,11 +357,11 @@ where ); false } - AllocStatus::OverflowsBin { tx_len, bin_size } => { + AllocStatus::OverflowsBin { tx, bin_size } => { // TODO: handle tx whose size is greater // than bin size tracing::warn!( - tx_len, + ?tx, bin_size, proposal_height = ?self.storage.get_current_decision_height(), @@ -382,14 +382,14 @@ where txs: Vec, ) -> Vec where - BlockSpaceAllocator>: State, + BlockSpaceAllocator>: TryAlloc, { get_remaining_txs(tx_indices, txs) .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, - AllocStatus::Rejected { tx_len, space_left } => { + AllocStatus::Rejected { tx, space_left } => { tracing::debug!( - tx_len, + ?tx, space_left, proposal_height = ?self.storage.get_current_decision_height(), @@ -397,11 +397,11 @@ where ); false } - AllocStatus::OverflowsBin { tx_len, bin_size } => { + AllocStatus::OverflowsBin { tx, bin_size } => { // TODO: handle tx whose size is greater // than bin size tracing::warn!( - tx_len, + ?tx, bin_size, proposal_height = ?self.storage.get_current_decision_height(), From 971d5a65e1f24705cd2d99d6c87e4b5811645560 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 14:25:47 +0000 Subject: [PATCH 1662/1995] Implement is_deciding_offset_within_epoch() --- shared/src/ledger/storage/mod.rs | 17 +++++++++-------- shared/src/ledger/storage_api/queries.rs | 5 +++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index dba0a3d1b2..46c491d292 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1070,11 +1070,13 @@ where // and ProcessProposal, we simply return true true } else { - self.is_deciding_2nd_height_offset() + // offset of 1 => are we at the 2nd + // block within the epoch? + self.is_deciding_offset_within_epoch(1) } } - fn is_deciding_2nd_height_offset(&self) -> bool { + fn is_deciding_offset_within_epoch(&self, height_off: u64) -> bool { let current_decision_height = self.get_current_decision_height(); // NOTE: the first stored height in `fst_block_heights_of_each_epoch` @@ -1082,10 +1084,9 @@ where // handle that case // // we can remove this check once that's fixed - match current_decision_height { - BlockHeight(1) => return false, - BlockHeight(2) => return true, - _ => (), + if self.get_current_epoch().0 == Epoch(0) { + let height_offset_within_epoch = BlockHeight(1 + height_off); + return current_decision_height == height_offset_within_epoch; } let fst_heights_of_each_epoch = @@ -1094,8 +1095,8 @@ where fst_heights_of_each_epoch .last() .map(|&h| { - let second_height_of_epoch = h + 1; - current_decision_height == second_height_of_epoch + let height_offset_within_epoch = h + height_off; + current_decision_height == height_offset_within_epoch }) .unwrap_or(false) } diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index e53a285906..540980c418 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -128,8 +128,9 @@ pub trait QueriesExt { /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; - /// Check if we are at the second block height offset within an [`Epoch`]. - fn is_deciding_2nd_height_offset(&self) -> bool; + /// Check if we are at a given [`BlockHeight`] offset, `height_off`, within + /// the current [`Epoch`]. + fn is_deciding_offset_within_epoch(&self, height_off: u64) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch(&self, height: BlockHeight) -> Option; From 20a22f0fbbcc196aeb69eb3f14bf012441e5a523 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 15:09:38 +0000 Subject: [PATCH 1663/1995] Add test_get_remaining_txs() unit test --- .../lib/node/ledger/shell/prepare_proposal.rs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f40b3d5ca1..842dab7813 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -521,6 +521,35 @@ mod test_prepare_proposal { // https://github.com/tendermint/tendermint/blob/v0.37.x/spec/abci/abci%2B%2B_app_requirements.md#blockparamsmaxbytes const MAX_TM_BLK_SIZE: i64 = 100 << 20; + /// Test if [`get_remaining_txs`] is working as expected. + #[test] + fn test_get_remaining_txs() { + let excluded_indices = [0, 1, 3, 5, 7]; + let all_txs: Vec<_> = (0..10).map(|tx_bytes| vec![tx_bytes]).collect(); + let expected_txs: Vec<_> = all_txs + .iter() + .filter(|tx_bytes| { + excluded_indices + .iter() + .copied() + .find(|&other| tx_bytes[0] == other) + .is_some() + }) + .cloned() + .collect(); + + let set = { + let mut s = LazyProposedTxSet::default(); + for idx in excluded_indices.iter().copied() { + s.include_tx_index(idx as usize); + } + s + }; + + let got_txs: Vec<_> = get_remaining_txs(&set, all_txs).collect(); + assert_eq!(expected_txs, got_txs); + } + #[cfg(feature = "abcipp")] fn get_local_last_commit(shell: &TestShell) -> Option { let evts = { From addcc6e30424bb0139254ac4b7f3563321dcf093 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 15:41:44 +0000 Subject: [PATCH 1664/1995] Add Either states for convenience --- .../block_space_alloc/states.rs | 20 +++++++++++-- .../block_space_alloc/states/encrypted_txs.rs | 29 +++++++++++++++++-- .../block_space_alloc/states/remaining_txs.rs | 15 +++++++++- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 2bf3202d20..9b20383dce 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -28,9 +28,23 @@ mod encrypted_txs; mod protocol_txs; mod remaining_txs; -use super::AllocStatus; -#[allow(unused_imports)] -use super::BlockSpaceAllocator; +use itertools::Either; + +use super::{AllocStatus, BlockSpaceAllocator}; + +/// Convenience wrapper for a [`BlockSpaceAllocator`] state that allocates +/// encrypted transactions. +pub type EncryptedTxBatchAllocator = Either< + BlockSpaceAllocator>, + BlockSpaceAllocator>, +>; + +/// Convenience wrapper for a [`BlockSpaceAllocator`] state that fills up the +/// remaining block space. +pub type RemainingBatchAllocator = Either< + BlockSpaceAllocator>, + BlockSpaceAllocator>, +>; /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 41cc3130b8..4318529935 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -1,9 +1,12 @@ use std::marker::PhantomData; +use itertools::Either::*; + use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ - BuildingEncryptedTxBatch, FillingRemainingSpace, NextStateImpl, TryAlloc, - WithEncryptedTxs, WithoutEncryptedTxs, + BuildingEncryptedTxBatch, EncryptedTxBatchAllocator, FillingRemainingSpace, + NextStateImpl, RemainingBatchAllocator, TryAlloc, WithEncryptedTxs, + WithoutEncryptedTxs, }; impl TryAlloc @@ -72,3 +75,25 @@ fn next_state( decrypted_txs, } } + +impl TryAlloc for EncryptedTxBatchAllocator { + #[inline] + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + match self { + Left(state) => state.try_alloc(tx), + Right(state) => state.try_alloc(tx), + } + } +} + +impl NextStateImpl for EncryptedTxBatchAllocator { + type Next = RemainingBatchAllocator; + + #[inline] + fn next_state_impl(self) -> Self::Next { + match self { + Left(state) => Left(state.next_state_impl()), + Right(state) => Right(state.next_state_impl()), + } + } +} diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index d9210edf79..4a2a17d684 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -1,6 +1,9 @@ +use itertools::Either::*; + use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ - FillingRemainingSpace, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, + FillingRemainingSpace, RemainingBatchAllocator, TryAlloc, WithEncryptedTxs, + WithoutEncryptedTxs, }; impl TryAlloc for BlockSpaceAllocator> { @@ -21,3 +24,13 @@ impl TryAlloc self.block.try_dump(tx) } } + +impl TryAlloc for RemainingBatchAllocator { + #[inline] + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + match self { + Left(state) => state.try_alloc(tx), + Right(state) => state.try_alloc(tx), + } + } +} From c583f3addc80c2757653a49df5f0a2087e831d13 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 15:42:38 +0000 Subject: [PATCH 1665/1995] Appease the clippy gods --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 842dab7813..dd8e941df4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -532,8 +532,7 @@ mod test_prepare_proposal { excluded_indices .iter() .copied() - .find(|&other| tx_bytes[0] == other) - .is_some() + .any(|other| tx_bytes[0] == other) }) .cloned() .collect(); From 885f64aacdaff230b3ba04364ca235b09ed5e2a1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 16:17:00 +0000 Subject: [PATCH 1666/1995] Add some compiler hints to optimize codegen --- shared/src/hints.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ shared/src/lib.rs | 1 + 2 files changed, 45 insertions(+) create mode 100644 shared/src/hints.rs diff --git a/shared/src/hints.rs b/shared/src/hints.rs new file mode 100644 index 0000000000..78d49eeab5 --- /dev/null +++ b/shared/src/hints.rs @@ -0,0 +1,44 @@ +//! Compiler hints, to improve the performance of certain operations. + +/// A function that is seldom called. +#[inline] +#[cold] +pub fn cold() {} + +/// A likely path to be taken in an if-expression. +/// +/// # Example +/// +/// ```ignore +/// if likely(frequent_condition()) { +/// // most common path to take +/// } else { +/// // ... +/// } +/// ``` +#[inline] +pub fn likely(b: bool) -> bool { + if !b { + cold() + } + b +} + +/// An unlikely path to be taken in an if-expression. +/// +/// # Example +/// +/// ```ignore +/// if unlikely(rare_condition()) { +/// // ... +/// } else { +/// // most common path to take +/// } +/// ``` +#[inline] +pub fn unlikely(b: bool) -> bool { + if b { + cold() + } + b +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 6faaf2ff36..dfe0fca8cf 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -16,6 +16,7 @@ pub use { }; pub mod bytes; +pub mod hints; pub mod ledger; pub mod proto; pub mod types; From 4643bf7430d397c5c745a1cbb85dea8b84522e50 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 16:17:46 +0000 Subject: [PATCH 1667/1995] Transition to a state with(out) encrypted txs --- .../lib/node/ledger/shell/prepare_proposal.rs | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index dd8e941df4..da881f9399 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,6 +2,8 @@ mod block_space_alloc; +use itertools::Either::*; +use namada::hints; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; @@ -13,9 +15,10 @@ use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; use namada::types::vote_extensions::VoteExtensionDigest; use self::block_space_alloc::states::{ - BuildingDecryptedTxBatch, BuildingEncryptedTxBatch, - BuildingProtocolTxBatch, FillingRemainingSpace, NextState, - NextStateWithEncryptedTxs, TryAlloc, TryAllocBatch, + BuildingDecryptedTxBatch, BuildingProtocolTxBatch, + EncryptedTxBatchAllocator, NextState, NextStateWithEncryptedTxs, + NextStateWithoutEncryptedTxs, RemainingBatchAllocator, TryAlloc, + TryAllocBatch, }; pub use self::block_space_alloc::LazyProposedTxSet; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; @@ -101,9 +104,29 @@ where protocol_txs.into_iter().map(record::add).collect(); txs.append(&mut protocol_txs); + // transition to the correct state; we may + // or may not need to add encrypted txs to + // the block, depending on the current + // block height + let is_2nd_height_off = + self.storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = + self.storage.is_deciding_offset_within_epoch(2); + + let mut alloc = if hints::unlikely( + is_2nd_height_off || is_3rd_height_off, + ) { + tracing::warn!( + proposal_height = + ?self.storage.get_current_decision_height(), + "No mempool txs are being included in the current proposal" + ); + Right(alloc.next_state_without_encrypted_txs()) + } else { + Left(alloc.next_state_with_encrypted_txs()) + }; + // add mempool txs - // TODO: check if we can add encrypted txs or not - let mut alloc = alloc.next_state_with_encrypted_txs(); let mut mempool_txs = self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); @@ -111,7 +134,6 @@ where // fill up the remaining block space with // arbitrary transactions that can fit in // the free space left - // TODO: check if we can add encrypted txs or not let mut alloc = alloc.next_state(); let mut remaining_txs = self.build_remaining_batch(&mut alloc, &tx_indices, req.txs); @@ -250,30 +272,24 @@ where /// Builds a batch of mempool transactions. #[cfg(feature = "abcipp")] - fn build_mempool_txs( + fn build_mempool_txs( &mut self, - _alloc: &mut BlockSpaceAllocator>, + _alloc: &mut EncryptedTxBatchAllocator, _tx_indices: &mut LazyProposedTxSet, txs: &[TxBytes], - ) -> Vec - where - BlockSpaceAllocator>: TryAlloc, - { + ) -> Vec { // TODO(feature = "abcipp"): implement building batch of mempool txs todo!() } /// Builds a batch of mempool transactions. #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs( + fn build_mempool_txs( &mut self, - alloc: &mut BlockSpaceAllocator>, + alloc: &mut EncryptedTxBatchAllocator, tx_indices: &mut LazyProposedTxSet, txs: &[TxBytes], - ) -> Vec - where - BlockSpaceAllocator>: TryAlloc, - { + ) -> Vec { txs.iter() .enumerate() .filter_map(|(index, tx_bytes)| { @@ -375,15 +391,12 @@ where /// Builds a batch of transactions that can fit in the /// remaining space of the [`BlockSpaceAllocator`]. - fn build_remaining_batch( + fn build_remaining_batch( &mut self, - alloc: &mut BlockSpaceAllocator>, + alloc: &mut RemainingBatchAllocator, tx_indices: &LazyProposedTxSet, txs: Vec, - ) -> Vec - where - BlockSpaceAllocator>: TryAlloc, - { + ) -> Vec { get_remaining_txs(tx_indices, txs) .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, From 3313e13fdd8b773887888ad758b21805a503903a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 16:21:26 +0000 Subject: [PATCH 1668/1995] Remove TODO --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index da881f9399..3294b81057 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -59,25 +59,10 @@ where // proposal is accepted self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { - // TODO: add some info logging? - // start counting allotted space for txs let mut alloc = BlockSpaceAllocator::from(&req); let mut tx_indices = LazyProposedTxSet::default(); - // NOTE: AD-HOC SOLUTION - // ====================== - // TODO: choose txs in this order: - // - decrypted txs (ALL OF THEM) - // - protocol txs (we should give priority to valset upds) - // - encrypted txs (it's fine if this bin is empty) - // - // at the beginning of an epoch, do not pick any - // encrypted txs :) inspired by solana - // - // `tracing::warn!()` log we are not accepting encrypted - // txs for a given block height - // decrypt the wrapper txs included in the previous block let decrypted_txs = self.build_decrypted_txs(&mut alloc); #[cfg(feature = "abcipp")] From 7be501d9c89623c61a1830377e03ec35686e581f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 16:27:55 +0000 Subject: [PATCH 1669/1995] Some comment changes in PrepareProposal --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3294b81057..0f123b62ea 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -70,7 +70,7 @@ where decrypted_txs.into_iter().map(record::add).collect(); let mut txs = decrypted_txs; - // add ethereum events and validator set updates as protocol txs + // add vote extension protocol txs let mut alloc = alloc.next_state(); #[cfg(feature = "abcipp")] let protocol_txs = self.build_vote_extensions_txs( @@ -89,10 +89,7 @@ where protocol_txs.into_iter().map(record::add).collect(); txs.append(&mut protocol_txs); - // transition to the correct state; we may - // or may not need to add encrypted txs to - // the block, depending on the current - // block height + // add mempool txs let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); let is_3rd_height_off = @@ -111,7 +108,6 @@ where Left(alloc.next_state_with_encrypted_txs()) }; - // add mempool txs let mut mempool_txs = self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); From b88cb31eaf0d017c3de7f5e6c962207982ea41b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 16:41:20 +0000 Subject: [PATCH 1670/1995] Clean up PrepareProposal code --- .../lib/node/ledger/shell/prepare_proposal.rs | 99 ++++++++++--------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0f123b62ea..ace4600c43 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -60,27 +60,26 @@ where self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs - let mut alloc = BlockSpaceAllocator::from(&req); + let alloc = BlockSpaceAllocator::from(&req); let mut tx_indices = LazyProposedTxSet::default(); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(&mut alloc); + let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); #[cfg(feature = "abcipp")] let decrypted_txs: Vec = decrypted_txs.into_iter().map(record::add).collect(); let mut txs = decrypted_txs; // add vote extension protocol txs - let mut alloc = alloc.next_state(); #[cfg(feature = "abcipp")] - let protocol_txs = self.build_vote_extensions_txs( - &mut alloc, + let (protocol_txs, alloc) = self.build_vote_extensions_txs( + alloc, &mut tx_indices, req.local_last_commit, ); #[cfg(not(feature = "abcipp"))] - let mut protocol_txs = self.build_vote_extensions_txs( - &mut alloc, + let (mut protocol_txs, alloc) = self.build_vote_extensions_txs( + alloc, &mut tx_indices, &req.txs, ); @@ -90,34 +89,15 @@ where txs.append(&mut protocol_txs); // add mempool txs - let is_2nd_height_off = - self.storage.is_deciding_offset_within_epoch(1); - let is_3rd_height_off = - self.storage.is_deciding_offset_within_epoch(2); - - let mut alloc = if hints::unlikely( - is_2nd_height_off || is_3rd_height_off, - ) { - tracing::warn!( - proposal_height = - ?self.storage.get_current_decision_height(), - "No mempool txs are being included in the current proposal" - ); - Right(alloc.next_state_without_encrypted_txs()) - } else { - Left(alloc.next_state_with_encrypted_txs()) - }; - - let mut mempool_txs = - self.build_mempool_txs(&mut alloc, &mut tx_indices, &req.txs); + let (mut mempool_txs, alloc) = + self.build_mempool_txs(alloc, &mut tx_indices, &req.txs); txs.append(&mut mempool_txs); // fill up the remaining block space with // arbitrary transactions that can fit in // the free space left - let mut alloc = alloc.next_state(); let mut remaining_txs = - self.build_remaining_batch(&mut alloc, &tx_indices, req.txs); + self.build_remaining_batch(alloc, &tx_indices, req.txs); txs.append(&mut remaining_txs); txs @@ -148,16 +128,16 @@ where /// events and, optionally, a validator set update. fn build_vote_extensions_txs( &mut self, - alloc: &mut BlockSpaceAllocator, + mut alloc: BlockSpaceAllocator, tx_indices: &mut LazyProposedTxSet, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, #[cfg(not(feature = "abcipp"))] txs: &[TxBytes], - ) -> Vec { + ) -> (Vec, EncryptedTxBatchAllocator) { // genesis block should not contain vote extensions if self.storage.last_height == BlockHeight(0) { - return vec![]; + return (vec![], self.get_encrypted_txs_allocator(alloc)); } #[cfg(feature = "abcipp")] @@ -213,7 +193,7 @@ where .chain(protocol_txs.into_iter()) .collect(); - match alloc.try_alloc_batch(txs.iter().map(Vec::as_slice)) { + let txs = match alloc.try_alloc_batch(txs.iter().map(Vec::as_slice)) { AllocStatus::Accepted => txs, AllocStatus::Rejected { tx, space_left } => { // no space left for tx batch, so we @@ -248,6 +228,29 @@ where ); vec![] } + }; + + (txs, self.get_encrypted_txs_allocator(alloc)) + } + + /// Transition to an [`EncryptedTxBatchAllocator`]. + #[inline] + fn get_encrypted_txs_allocator( + &self, + alloc: BlockSpaceAllocator, + ) -> EncryptedTxBatchAllocator { + let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + + if hints::unlikely(is_2nd_height_off || is_3rd_height_off) { + tracing::warn!( + proposal_height = + ?self.storage.get_current_decision_height(), + "No mempool txs are being included in the current proposal" + ); + Right(alloc.next_state_without_encrypted_txs()) + } else { + Left(alloc.next_state_with_encrypted_txs()) } } @@ -255,10 +258,10 @@ where #[cfg(feature = "abcipp")] fn build_mempool_txs( &mut self, - _alloc: &mut EncryptedTxBatchAllocator, + _alloc: EncryptedTxBatchAllocator, _tx_indices: &mut LazyProposedTxSet, txs: &[TxBytes], - ) -> Vec { + ) -> (Vec, RemainingBatchAllocator) { // TODO(feature = "abcipp"): implement building batch of mempool txs todo!() } @@ -267,11 +270,12 @@ where #[cfg(not(feature = "abcipp"))] fn build_mempool_txs( &mut self, - alloc: &mut EncryptedTxBatchAllocator, + mut alloc: EncryptedTxBatchAllocator, tx_indices: &mut LazyProposedTxSet, txs: &[TxBytes], - ) -> Vec { - txs.iter() + ) -> (Vec, RemainingBatchAllocator) { + let txs = txs + .iter() .enumerate() .filter_map(|(index, tx_bytes)| { if let Ok(Ok(TxType::Wrapper(_))) = @@ -311,7 +315,10 @@ where } }) .map(|(_, tx_bytes)| tx_bytes) - .collect() + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) } /// Builds a batch of DKG decrypted transactions. @@ -324,13 +331,14 @@ where // - https://github.com/anoma/ferveo fn build_decrypted_txs( &mut self, - alloc: &mut BlockSpaceAllocator, - ) -> Vec { + mut alloc: BlockSpaceAllocator, + ) -> (Vec, BlockSpaceAllocator) { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - self.storage + let txs = self + .storage .tx_queue .iter() .map(|tx| { @@ -367,14 +375,17 @@ where true } }) - .collect() + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) } /// Builds a batch of transactions that can fit in the /// remaining space of the [`BlockSpaceAllocator`]. fn build_remaining_batch( &mut self, - alloc: &mut RemainingBatchAllocator, + mut alloc: RemainingBatchAllocator, tx_indices: &LazyProposedTxSet, txs: Vec, ) -> Vec { From 2b01c4692182fef6d604667fe9fcde1cc936bfa6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 09:44:21 +0000 Subject: [PATCH 1671/1995] Fix get_remaining_txs() --- .../lib/node/ledger/shell/prepare_proposal.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index ace4600c43..af72916520 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -431,11 +431,11 @@ fn get_remaining_txs( txs.into_iter().enumerate().filter_map(move |(index, tx)| { // this works bc/ tx indices are ordered // in ascending order - if Some(index) == skip { + if hints::likely(Some(index) == skip) { skip = skip_list.next(); - Some(tx) - } else { None + } else { + Some(tx) } }) } @@ -531,15 +531,9 @@ mod test_prepare_proposal { fn test_get_remaining_txs() { let excluded_indices = [0, 1, 3, 5, 7]; let all_txs: Vec<_> = (0..10).map(|tx_bytes| vec![tx_bytes]).collect(); - let expected_txs: Vec<_> = all_txs - .iter() - .filter(|tx_bytes| { - excluded_indices - .iter() - .copied() - .any(|other| tx_bytes[0] == other) - }) - .cloned() + let expected_txs: Vec<_> = [2, 4, 6, 8, 9] + .into_iter() + .map(|tx_bytes| vec![tx_bytes]) .collect(); let set = { From 65079dd826061080900d9fe72e1b76e6aece0aab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:21:05 +0000 Subject: [PATCH 1672/1995] Rebase fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 20 +++++++++---------- .../lib/node/ledger/shell/vote_extensions.rs | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index af72916520..fb0923fe39 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -8,6 +8,7 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Tx; +use namada::types::index_set::IndexSet; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; @@ -20,7 +21,6 @@ use self::block_space_alloc::states::{ NextStateWithoutEncryptedTxs, RemainingBatchAllocator, TryAlloc, TryAllocBatch, }; -pub use self::block_space_alloc::LazyProposedTxSet; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; @@ -61,7 +61,7 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs let alloc = BlockSpaceAllocator::from(&req); - let mut tx_indices = LazyProposedTxSet::default(); + let mut tx_indices = IndexSet::default(); // decrypt the wrapper txs included in the previous block let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); @@ -129,7 +129,7 @@ where fn build_vote_extensions_txs( &mut self, mut alloc: BlockSpaceAllocator, - tx_indices: &mut LazyProposedTxSet, + tx_indices: &mut IndexSet, #[cfg(feature = "abcipp")] local_last_commit: Option< ExtendedCommitInfo, >, @@ -259,7 +259,7 @@ where fn build_mempool_txs( &mut self, _alloc: EncryptedTxBatchAllocator, - _tx_indices: &mut LazyProposedTxSet, + _tx_indices: &mut IndexSet, txs: &[TxBytes], ) -> (Vec, RemainingBatchAllocator) { // TODO(feature = "abcipp"): implement building batch of mempool txs @@ -271,7 +271,7 @@ where fn build_mempool_txs( &mut self, mut alloc: EncryptedTxBatchAllocator, - tx_indices: &mut LazyProposedTxSet, + tx_indices: &mut IndexSet, txs: &[TxBytes], ) -> (Vec, RemainingBatchAllocator) { let txs = txs @@ -288,7 +288,7 @@ where }) .take_while(|(index, tx_bytes)| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => { - tx_indices.include_tx_index(*index); + tx_indices.insert(*index); true } AllocStatus::Rejected { tx, space_left } => { @@ -386,7 +386,7 @@ where fn build_remaining_batch( &mut self, mut alloc: RemainingBatchAllocator, - tx_indices: &LazyProposedTxSet, + tx_indices: &IndexSet, txs: Vec, ) -> Vec { get_remaining_txs(tx_indices, txs) @@ -422,7 +422,7 @@ where /// Return a list of the transactions that haven't /// been marked for inclusion in the block, yet. fn get_remaining_txs( - tx_indices: &LazyProposedTxSet, + tx_indices: &IndexSet, txs: Vec, ) -> impl Iterator + '_ { let mut skip_list = tx_indices.iter(); @@ -537,9 +537,9 @@ mod test_prepare_proposal { .collect(); let set = { - let mut s = LazyProposedTxSet::default(); + let mut s = IndexSet::default(); for idx in excluded_indices.iter().copied() { - s.include_tx_index(idx as usize); + s.insert(idx as usize); } s }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b7e831627e..a72b1c2abe 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -7,12 +7,12 @@ pub mod val_set_update; use borsh::BorshDeserialize; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; +use namada::types::index_set::IndexSet; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::vote_extensions::{ ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, }; -use super::prepare_proposal::LazyProposedTxSet; use super::*; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedVoteInfo; @@ -300,7 +300,7 @@ pub fn deserialize_vote_extensions( /// instances. #[cfg(not(feature = "abcipp"))] pub fn deserialize_vote_extensions<'prep_proposal>( - tx_indices: &'prep_proposal mut LazyProposedTxSet, + tx_indices: &'prep_proposal mut IndexSet, txs: &'prep_proposal [TxBytes], ) -> impl Iterator + 'prep_proposal { use namada::types::transaction::protocol::ProtocolTx; @@ -325,7 +325,7 @@ pub fn deserialize_vote_extensions<'prep_proposal>( // them in a block without the corresponding digests, so even // if those get rejected due to space constraints, the // behavior should be correct - tx_indices.include_tx_index(index); + tx_indices.insert(index); Some((tx_bytes.clone(), ext)) } _ => None, @@ -379,7 +379,7 @@ pub fn split_vote_extensions( /// them from Tendermint's mempool. #[cfg(not(feature = "abcipp"))] pub fn split_vote_extensions( - tx_indices: &mut LazyProposedTxSet, + tx_indices: &mut IndexSet, mempool_txs: &[TxBytes], ) -> ( Vec, From 7966a30cdcbc86f244c0c6cdfd6302798aff6552 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:29:05 +0000 Subject: [PATCH 1673/1995] Fix remaining unit tests --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index fb0923fe39..37d3aba40d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -274,6 +274,7 @@ where tx_indices: &mut IndexSet, txs: &[TxBytes], ) -> (Vec, RemainingBatchAllocator) { + let mut invalid_txs = IndexSet::default(); let txs = txs .iter() .enumerate() @@ -283,6 +284,9 @@ where { Some((index, tx_bytes.clone())) } else { + // found invalid tx. mark it, so we do not include + // it in a block during `build_remaining_batch()` + invalid_txs.insert(index); None } }) @@ -318,6 +322,8 @@ where .collect(); let alloc = alloc.next_state(); + tx_indices.merge(&invalid_txs); + (txs, alloc) } From 6e693eb5e301d44fadb0bbb67a7fa791f721c2f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:29:59 +0000 Subject: [PATCH 1674/1995] Improve comment --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index a72b1c2abe..bad192fcc5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -321,7 +321,7 @@ pub fn deserialize_vote_extensions<'prep_proposal>( tx: ProtocolTxType::VoteExtension(ext), .. }) => { - // mark every protocol tx for inclusion; we shouldn't include + // mark every vote extension for inclusion; we shouldn't include // them in a block without the corresponding digests, so even // if those get rejected due to space constraints, the // behavior should be correct From 6e8ceb34909306594c5f336379174ba1d03385ee Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:58:24 +0000 Subject: [PATCH 1675/1995] Reserve 1/3 of the total block space for protocol txs --- .../block_space_alloc/states/decrypted_txs.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index e2ea17dea5..e70810a702 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -20,9 +20,10 @@ impl NextStateImpl for BlockSpaceAllocator { self.decrypted_txs.shrink(); // reserve space for protocol txs - let remaining_free_space = self.uninitialized_space_in_bytes(); - self.protocol_txs = - TxBin::init_over_ratio(remaining_free_space, thres::ONE_THIRD); + self.protocol_txs = TxBin::init_over_ratio( + self.block.allotted_space_in_bytes, + thres::ONE_THIRD, + ); // cast state let Self { From 7ebc5451d7992587baba5bbf6d7df72a3236a173 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 10:59:34 +0000 Subject: [PATCH 1676/1995] Rename: thres -> threshold --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- .../block_space_alloc/states/decrypted_txs.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 3818603648..49cd5f6dc7 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -238,7 +238,7 @@ impl TxBin { } } -mod thres { +mod threshold { //! Transaction allotment thresholds. use num_rational::Ratio; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index e70810a702..494fd013d4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use super::super::{thres, AllocStatus, BlockSpaceAllocator, TxBin}; +use super::super::{threshold, AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, }; @@ -22,7 +22,7 @@ impl NextStateImpl for BlockSpaceAllocator { // reserve space for protocol txs self.protocol_txs = TxBin::init_over_ratio( self.block.allotted_space_in_bytes, - thres::ONE_THIRD, + threshold::ONE_THIRD, ); // cast state From 79028d4755e13c51a668477a8eca13aa7aac0a1f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 11:01:12 +0000 Subject: [PATCH 1677/1995] Rename: shrink -> shrink_to_fit --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- .../block_space_alloc/states/decrypted_txs.rs | 2 +- .../block_space_alloc/states/encrypted_txs.rs | 2 +- .../prepare_proposal/block_space_alloc/states/protocol_txs.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 49cd5f6dc7..8b66eb5c42 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -191,7 +191,7 @@ impl TxBin { /// Shrink the allotted space of this [`TxBin`] to whatever /// space is currently being utilized. #[inline] - fn shrink(&mut self) { + fn shrink_to_fit(&mut self) { self.allotted_space_in_bytes = self.occupied_space_in_bytes; } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 494fd013d4..47a866b90f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -17,7 +17,7 @@ impl NextStateImpl for BlockSpaceAllocator { #[inline] fn next_state_impl(mut self) -> Self::Next { - self.decrypted_txs.shrink(); + self.decrypted_txs.shrink_to_fit(); // reserve space for protocol txs self.protocol_txs = TxBin::init_over_ratio( diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 41cc3130b8..f88f7f55aa 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -50,7 +50,7 @@ impl NextStateImpl fn next_state( mut alloc: BlockSpaceAllocator>, ) -> BlockSpaceAllocator> { - alloc.encrypted_txs.shrink(); + alloc.encrypted_txs.shrink_to_fit(); // reserve space for any remaining txs alloc.claim_block_space(); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index b4ce10e956..bb22fd1bfc 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -27,7 +27,7 @@ impl NextStateImpl #[inline] fn next_state_impl(mut self) -> Self::Next { - self.protocol_txs.shrink(); + self.protocol_txs.shrink_to_fit(); // reserve space for encrypted txs let remaining_free_space = self.uninitialized_space_in_bytes(); @@ -60,7 +60,7 @@ impl NextStateImpl #[inline] fn next_state_impl(mut self) -> Self::Next { - self.protocol_txs.shrink(); + self.protocol_txs.shrink_to_fit(); // cast state let Self { From 19343005ce23c9a700809b6ebd738680cf044c2f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 11:18:15 +0000 Subject: [PATCH 1678/1995] Improve docstr --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 8b66eb5c42..5867b8094e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -162,8 +162,8 @@ struct TxBin { } impl TxBin { - /// Construct a new [`TxBin`], with an upper bound on the max number - /// of storable txs defined by a ratio over `max_bytes`. + /// Return a new [`TxBin`] with a total allotted space equal to the + /// floor of the fraction `frac` of the available block space `max_bytes`. #[inline] fn init_over_ratio(max_bytes: u64, frac: Ratio) -> Self { let allotted_space_in_bytes = (frac * max_bytes).to_integer(); From adf31304b0a58804f5d4bc832474db6f35cb045d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 21 Nov 2022 11:49:07 +0000 Subject: [PATCH 1679/1995] Remove abort sender channel from Ethereum oracle --- .../node/ledger/ethereum_node/oracle/mod.rs | 53 ++----------------- apps/src/lib/node/ledger/mod.rs | 10 ++-- 2 files changed, 6 insertions(+), 57 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index ac86472336..eb32e2b1c2 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -9,7 +9,6 @@ use eyre::{eyre, Result}; use namada::types::ethereum_events::EthereumEvent; use num256::Uint256; use tokio::sync::mpsc::Sender as BoundedSender; -use tokio::sync::oneshot::Sender; use tokio::task::LocalSet; #[cfg(not(test))] use web30::client::Web3; @@ -31,9 +30,6 @@ pub struct Oracle { /// A channel for sending processed and confirmed /// events to the ledger process sender: BoundedSender, - /// A channel to signal that the ledger should shut down - /// because the Oracle has stopped - abort: Option>, /// How long the oracle should wait between checking blocks backoff: Duration, /// A channel for controlling and configuring the oracle. @@ -48,38 +44,18 @@ impl Deref for Oracle { } } -impl Drop for Oracle { - fn drop(&mut self) { - // send an abort signal to shut down the - // rest of the ledger gracefully - match self.abort.take().unwrap().send(()) { - Ok(()) => tracing::info!("Oracle sent abort signal"), - Err(()) => { - // this isn't necessarily an issue as the ledger may have shut - // down first - tracing::debug!( - "Oracle was unable to send an abort signal as the abort \ - channel was already closed" - ) - } - }; - } -} - impl Oracle { /// Construct a new [`Oracle`]. Note that it can not do anything until it /// has been sent a configuration via the passed in `control` channel. pub fn new( url: &str, sender: BoundedSender, - abort: Sender<()>, backoff: Duration, control: control::Receiver, ) -> Self { Self { client: Web3::new(url, std::time::Duration::from_secs(30)), sender, - abort: Some(abort), backoff, control, } @@ -129,7 +105,6 @@ pub fn run_oracle( url: impl AsRef, sender: BoundedSender, control: control::Receiver, - abort_sender: Sender<()>, ) -> tokio::task::JoinHandle<()> { let url = url.as_ref().to_owned(); // we have to run the oracle in a [`LocalSet`] due to the web30 @@ -141,13 +116,8 @@ pub fn run_oracle( .run_until(async move { tracing::info!(?url, "Ethereum event oracle is starting"); - let oracle = Oracle::new( - &url, - sender, - abort_sender, - DEFAULT_BACKOFF, - control, - ); + let oracle = + Oracle::new(&url, sender, DEFAULT_BACKOFF, control); run_oracle_aux(oracle).await; tracing::info!( @@ -382,7 +352,7 @@ mod test_oracle { use std::num::NonZeroU64; use namada::types::ethereum_events::{EthAddress, TransferToEthereum}; - use tokio::sync::oneshot::{channel, Receiver}; + use tokio::sync::oneshot::channel; use tokio::time::timeout; use super::*; @@ -400,7 +370,6 @@ mod test_oracle { eth_recv: tokio::sync::mpsc::Receiver, control_sender: control::Sender, blocks_processed_recv: tokio::sync::mpsc::UnboundedReceiver, - abort_recv: Receiver<()>, } /// Helper function that starts running the oracle in a new thread, and @@ -433,12 +402,10 @@ mod test_oracle { let (admin_channel, blocks_processed_recv, client) = Web3::setup(); let (eth_sender, eth_receiver) = tokio::sync::mpsc::channel(1000); let (control_sender, control_receiver) = control::channel(); - let (abort, abort_recv) = channel(); TestPackage { oracle: Oracle { client, sender: eth_sender, - abort: Some(abort), // backoff should be short for tests so that they run faster backoff: Duration::from_millis(5), control: control_receiver, @@ -447,23 +414,9 @@ mod test_oracle { eth_recv: eth_receiver, control_sender, blocks_processed_recv, - abort_recv, } } - /// Test that if the oracle shuts down, it - /// sends a message to the fullnode to stop - #[test] - fn test_abort_send() { - let TestPackage { - oracle, - mut abort_recv, - .. - } = setup(); - drop(oracle); - assert!(abort_recv.try_recv().is_ok()) - } - /// Test that if the fullnode stops, the oracle /// shuts down, even if the web3 client is unresponsive #[tokio::test] diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index ee953cff75..a7d9e5c793 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -19,7 +19,7 @@ use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; -use tokio::sync::{mpsc, oneshot}; +use tokio::sync::mpsc; use tokio::task; use tower::ServiceBuilder; @@ -229,14 +229,12 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let tendermint_node = start_tendermint(&mut spawner, &config); // Start managed Ethereum node if necessary - let (eth_node, abort_sender) = + let (eth_node, _abort_sender) = maybe_start_geth(&mut spawner, &config).await; // Start oracle if necessary let (eth_receiver, oracle) = - match maybe_start_ethereum_oracle(&mut spawner, &config, abort_sender) - .await - { + match maybe_start_ethereum_oracle(&mut spawner, &config).await { EthereumOracleTask::NotEnabled { handle, eth_receiver, @@ -650,7 +648,6 @@ enum EthereumOracleTask { async fn maybe_start_ethereum_oracle( spawner: &mut AbortableSpawner, config: &config::Ledger, - abort_sender: oneshot::Sender<()>, ) -> EthereumOracleTask { let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); @@ -665,7 +662,6 @@ async fn maybe_start_ethereum_oracle( ethereum_url, eth_sender, control_receiver, - abort_sender, ); // TODO(namada#686): pass `oracle_control_sender` to the shell for From 610a8fd6407c9e6a839cfe89d504fe4fdf42ca17 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 21 Nov 2022 12:20:01 +0000 Subject: [PATCH 1680/1995] Remove abort sender channel from EthereumNode --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 58 ++++++++----------- apps/src/lib/node/ledger/mod.rs | 31 +++------- 2 files changed, 34 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 37669f195f..5ecd7a74cf 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -28,15 +28,27 @@ pub enum Error { pub type Result = std::result::Result; -/// Monitor the Ethereum fullnode. If it stops or an abort -/// signal is sent, the subprocess is halted. +/// Monitor the Ethereum fullnode subprocess, returning only once it has +/// stopped running. If a signal is sent on `abort_recv`, the fullnode +/// subprocess will be killed. pub async fn monitor( mut node: eth_fullnode::EthereumNode, abort_recv: Receiver>, -) -> Result<()> { +) { tokio::select! { - // run the ethereum fullnode - status = node.wait() => status, + // wait for the Ethereum fullnode to naturally exit + exit_status = node.wait() => { + match exit_status { + Ok(exit_status) => { + tracing::info!(%exit_status, "Ethereum fullnode exited"); + }, + Err(err) => { + tracing::warn!("Error while waiting for the Ethereum fullnode to exit: {}", err); + tracing::info!("Ensuring Ethereum fullnode is shut down..."); + node.kill().await; + }, + }; + }, // wait for an abort signal resp_sender = abort_recv => { match resp_sender { @@ -51,17 +63,17 @@ pub async fn monitor( node.kill().await; } } - Ok(()) } } } /// Tools for running a geth fullnode process pub mod eth_fullnode { + use std::io; + use std::process::ExitStatus; use std::time::Duration; use tokio::process::{Child, Command}; - use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::task::LocalSet; use web30::client::Web3; @@ -72,7 +84,6 @@ pub mod eth_fullnode { /// stops. pub struct EthereumNode { process: Child, - abort_recv: Receiver<()>, } /// Read from environment variable which Ethereum @@ -98,13 +109,12 @@ pub mod eth_fullnode { } impl EthereumNode { - /// Starts the geth process and returns a handle to it as well - /// as a channel on which an abort signal can be sent to shut it down. + /// Starts the geth process and returns a handle to it. /// /// First looks up which network to connect to from an env var. /// It then starts the process and waits for it to finish /// syncing. - pub async fn new(url: &str) -> Result<(EthereumNode, Sender<()>)> { + pub async fn new(url: &str) -> Result { // we have to start the node in a [`LocalSet`] due to the web30 // crate LocalSet::new() @@ -153,35 +163,17 @@ pub mod eth_fullnode { tokio::time::sleep(SLEEP_DUR).await; } - let (abort_sender, receiver) = channel(); let node = Self { process: ethereum_node, - abort_recv: receiver, }; - Ok((node, abort_sender)) + Ok(node) }) .await } - /// Wait for the process to finish or an abort message was - /// received from the Oracle process. If either, return the - /// status. - pub async fn wait(&mut self) -> Result<()> { - use futures::future::{self, Either}; - - let child_proc = self.process.wait(); - futures::pin_mut!(child_proc); - - match future::select(&mut self.abort_recv, child_proc).await { - Either::Left(_) => Err(Error::Oracle), - Either::Right((Ok(status), _)) if status.success() => Ok(()), - Either::Right((Ok(status), _)) => { - Err(Error::Runtime(format!("{status}"))) - } - Either::Right((Err(err), _)) => { - Err(Error::Runtime(format!("{err}"))) - } - } + /// Wait for the process to finish. + pub async fn wait(&mut self) -> io::Result { + self.process.wait().await } /// Stop the geth process diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index a7d9e5c793..7b5729cc06 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -229,8 +229,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let tendermint_node = start_tendermint(&mut spawner, &config); // Start managed Ethereum node if necessary - let (eth_node, _abort_sender) = - maybe_start_geth(&mut spawner, &config).await; + let eth_node = maybe_start_geth(&mut spawner, &config).await; // Start oracle if necessary let (eth_receiver, oracle) = @@ -268,15 +267,12 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::try_join!(tendermint_node, eth_node, abci, oracle, broadcaster); match res { - Ok((tendermint_res, eth_res, abci_res, _, _)) => { + Ok((tendermint_res, _, abci_res, _, _)) => { // we ignore errors on user-initiated shutdown if aborted { if let Err(err) = tendermint_res { tracing::error!("Tendermint error: {}", err); } - if let Err(err) = eth_res { - tracing::error!("Ethereum error: {}", err); - } if let Err(err) = abci_res { tracing::error!("ABCI error: {}", err); } @@ -738,41 +734,32 @@ async fn maybe_start_ethereum_oracle( async fn maybe_start_geth( spawner: &mut AbortableSpawner, config: &config::Ledger, -) -> ( - task::JoinHandle>, - tokio::sync::oneshot::Sender<()>, -) { +) -> task::JoinHandle<()> { if !matches!(config.tendermint.tendermint_mode, TendermintMode::Validator) || !matches!( config.ethereum_bridge.mode, ethereum_bridge::ledger::Mode::Managed ) { - let eth_node = spawn_dummy_task(Ok(())); - let (abort_sender, _) = tokio::sync::oneshot::channel::<()>(); - return (eth_node, abort_sender); + return spawn_dummy_task(()); } let ethereum_url = config.ethereum_bridge.oracle_rpc_endpoint.clone(); // Boot up geth and wait for it to finish syncing - let (eth_node, abort_sender) = - eth_fullnode::EthereumNode::new(ðereum_url) - .await - .expect("Unable to start the Ethereum fullnode"); + let eth_node = eth_fullnode::EthereumNode::new(ðereum_url) + .await + .expect("Unable to start the Ethereum fullnode"); // Run geth in the background let (eth_abort_send, eth_abort_recv) = tokio::sync::oneshot::channel::>(); let eth_node = spawner .spawn_abortable("Ethereum", move |aborter| async move { - let res = ethereum_node::monitor(eth_node, eth_abort_recv) - .map_err(Error::Ethereum) - .await; + ethereum_node::monitor(eth_node, eth_abort_recv).await; tracing::info!("Ethereum fullnode is no longer running."); drop(aborter); - res }) .with_cleanup(async move { let (eth_abort_resp_send, eth_abort_resp_recv) = @@ -790,7 +777,7 @@ async fn maybe_start_geth( } } }); - (eth_node, abort_sender) + eth_node } /// Spawn a dummy asynchronous task into the runtime, From 93376b6f8e546ae72d6bb811835609f6b022e29e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 13:39:46 +0000 Subject: [PATCH 1681/1995] Reserve half of the remaining block space for protocol txs --- .../shell/prepare_proposal/block_space_alloc.rs | 5 +++++ .../block_space_alloc/states/decrypted_txs.rs | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 5867b8094e..0a725bcef8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -245,5 +245,10 @@ mod threshold { /// The threshold over Tendermint's allotted space for all three /// (major) kinds of Namada transactions. + #[cfg(test)] + #[allow(dead_code)] pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); + + /// Divide the allotted space in two. + pub const ONE_HALF: Ratio = Ratio::new_raw(1, 2); } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 47a866b90f..60a2bec2c9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -19,11 +19,12 @@ impl NextStateImpl for BlockSpaceAllocator { fn next_state_impl(mut self) -> Self::Next { self.decrypted_txs.shrink_to_fit(); - // reserve space for protocol txs - self.protocol_txs = TxBin::init_over_ratio( - self.block.allotted_space_in_bytes, - threshold::ONE_THIRD, - ); + // reserve half of the remaining block space for protocol txs. + // using this strategy, we will eventually converge to 1/3 of + // the allotted block space for protocol txs + let remaining_free_space = self.uninitialized_space_in_bytes(); + self.protocol_txs = + TxBin::init_over_ratio(remaining_free_space, threshold::ONE_HALF); // cast state let Self { From 45210fd0f16f50b6bfa2cdbb2057f827c9542794 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 15:01:41 +0000 Subject: [PATCH 1682/1995] Add back (now broken) tx bin unit tests --- .../prepare_proposal/block_space_alloc.rs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 0a725bcef8..1cf878f005 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -252,3 +252,191 @@ mod threshold { /// Divide the allotted space in two. pub const ONE_HALF: Ratio = Ratio::new_raw(1, 2); } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use proptest::prelude::*; + + use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space_in_bytes: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } + + /// Check if the sum of all individual tx thresholds does + /// not exceed one. + /// + /// This is important, because we do not want to exceed + /// the maximum block size in Tendermint, and get randomly + /// rejected blocks. + #[test] + fn test_tx_thres_doesnt_exceed_one() { + let sum = + thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; + assert_eq!(sum.to_integer(), 1); + } + + proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`BlockSpaceAllocator`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + + /// Check if the sum of all individual bin allotments for a + /// [`BlockSpaceAllocator`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not fill up the bin. + #[test] + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) + } + } + + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; + + // make sure we can't dump any new decrypted txs in the bin + assert_eq!( + bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + AllocStatus::Rejected + ); + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space_in_bytes: u64, + ) { + let bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + assert_eq!(0, bins.uninitialized_space_in_bytes()); + } + + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + let bins = RefCell::new(BlockSpaceAllocator::init( + tendermint_max_block_space_in_bytes, + )); + + // produce new txs until we fill up the bins + // + // TODO: ideally the proptest strategy would already return + // txs whose total added size would be bounded + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + + // make sure we can keep dumping txs, + // without filling up the bins + for tx in protocol_txs { + assert_eq!( + bins.borrow_mut().try_alloc_protocol_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in encrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_encrypted_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in decrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_decrypted_tx(&tx), + AllocStatus::Accepted + ); + } + } + + prop_compose! { + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions() + // create base strategies + ( + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), + ) + // compose strategies + ( + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), + ) + -> PropTx { + PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } + } + + /// Return random bin sizes for a [`BlockSpaceAllocator`]. + fn arb_max_bin_sizes() -> impl Strategy + { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + ) + }, + ) + } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } +} From 0506aae31c0d44036ef304904ae01a71f14880f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 09:24:52 +0000 Subject: [PATCH 1683/1995] WIP: Fixing unit tests for tx bins --- .../prepare_proposal/block_space_alloc.rs | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 1cf878f005..d1a1c59c5f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -259,6 +259,7 @@ mod tests { use proptest::prelude::*; + use super::states::{NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -271,19 +272,6 @@ mod tests { decrypted_txs: Vec, } - /// Check if the sum of all individual tx thresholds does - /// not exceed one. - /// - /// This is important, because we do not want to exceed - /// the maximum block size in Tendermint, and get randomly - /// rejected blocks. - #[test] - fn test_tx_thres_doesnt_exceed_one() { - let sum = - thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert_eq!(sum.to_integer(), 1); - } - proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. @@ -321,7 +309,7 @@ mod tests { // make sure we can't dump any new decrypted txs in the bin assert_eq!( - bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + bins.try_alloc(b"arbitrary tx bytes"), AllocStatus::Rejected ); } @@ -343,49 +331,44 @@ mod tests { encrypted_txs, decrypted_txs, } = args; + + // produce new txs until the moment we would have + // filled up the bins. + // + // iterate over the produced txs to make sure we can keep + // dumping new txs without filling up the bins + let bins = RefCell::new(BlockSpaceAllocator::init( tendermint_max_block_space_in_bytes, )); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in decrypted_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } - // produce new txs until we fill up the bins - // - // TODO: ideally the proptest strategy would already return - // txs whose total added size would be bounded + let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); + for tx in protocol_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } + + let bins = + RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - - // make sure we can keep dumping txs, - // without filling up the bins - for tx in protocol_txs { - assert_eq!( - bins.borrow_mut().try_alloc_protocol_tx(&tx), - AllocStatus::Accepted - ); - } for tx in encrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_encrypted_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in decrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_decrypted_tx(&tx), - AllocStatus::Accepted - ); + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); } } @@ -422,11 +405,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From f1875abfc410ed815aa2837218bf55684121c97c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:10:01 +0000 Subject: [PATCH 1684/1995] Fix tx bins unit tests --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index d1a1c59c5f..2bc9777574 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -259,7 +259,7 @@ mod tests { use proptest::prelude::*; - use super::states::{NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; From feef4f36030891cd8cdf9c5ae0a715b062fc25d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:04:12 +0000 Subject: [PATCH 1685/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 2bc9777574..8468673a64 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -257,6 +257,7 @@ mod threshold { mod tests { use std::cell::RefCell; + use assert_matches::assert_matches; use proptest::prelude::*; use super::states::{NextState, NextStateWithEncryptedTxs, State}; @@ -308,9 +309,9 @@ mod tests { bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin - assert_eq!( + assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected + AllocStatus::Rejected { .. } ); } @@ -347,7 +348,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = RefCell::new(bins.into_inner().next_state()); @@ -357,7 +361,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = @@ -368,7 +375,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } } From ae04369dc271e4f48233f1d9063a9b043d8d24ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:55:16 +0000 Subject: [PATCH 1686/1995] Rebase fixes --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 10 +++++----- .../block_space_alloc/states/protocol_txs.rs | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 8468673a64..6076fe302f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -260,7 +260,7 @@ mod tests { use assert_matches::assert_matches; use proptest::prelude::*; - use super::states::{NextState, NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -305,7 +305,7 @@ mod tests { BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.occupied_space_in_bytes = bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin @@ -344,7 +344,7 @@ mod tests { )); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { @@ -357,7 +357,7 @@ mod tests { let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { @@ -371,7 +371,7 @@ mod tests { RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index bb22fd1bfc..1524468acd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,6 +6,14 @@ use super::{ TryAllocBatch, WithEncryptedTxs, WithoutEncryptedTxs, }; +#[cfg(test)] +impl super::TryAlloc for BlockSpaceAllocator { + #[inline] + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + self.protocol_txs.try_dump(tx) + } +} + impl TryAllocBatch for BlockSpaceAllocator { #[inline] fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> From a90780db12b509e707b2513c7b8b1f4d7eb32d74 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 12:46:27 +0000 Subject: [PATCH 1687/1995] Rebase fixes --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 6076fe302f..3122b244f0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -415,11 +415,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From 7a6b99f0e96ead94b25797708796500426eb33e9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 13:40:56 +0000 Subject: [PATCH 1688/1995] Remove #[allow(dead_code)] --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 3122b244f0..15d49222e8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -246,7 +246,6 @@ mod threshold { /// The threshold over Tendermint's allotted space for all three /// (major) kinds of Namada transactions. #[cfg(test)] - #[allow(dead_code)] pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); /// Divide the allotted space in two. From bb113b3d73d6c1bc137a66ba093b62effeacb658 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 09:50:14 +0000 Subject: [PATCH 1689/1995] WIP: Removing vote extension digests --- apps/src/lib/node/ledger/shell/vote_extensions.rs | 5 ++++- shared/src/types/transaction/protocol.rs | 13 +++++++------ shared/src/types/vote_extensions.rs | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 21c394200b..b8e3558a79 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -8,8 +8,10 @@ use borsh::BorshDeserialize; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; +#[cfg(feature = "abcipp")] +use namada::types::vote_extensions::VoteExtensionDigest; use namada::types::vote_extensions::{ - ethereum_events, validator_set_update, VoteExtension, VoteExtensionDigest, + ethereum_events, validator_set_update, VoteExtension, }; use super::*; @@ -326,6 +328,7 @@ pub fn deserialize_vote_extensions( /// Yields an iterator over the [`ProtocolTxType`] transactions /// in a [`VoteExtensionDigest`]. +#[cfg(feature = "abcipp")] pub fn iter_protocol_txs( digest: VoteExtensionDigest, ) -> impl Iterator { diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index ddd490b898..c658ac3dcc 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -35,8 +35,6 @@ mod protocol_txs { use crate::proto::Tx; use crate::types::key::*; use crate::types::transaction::{EllipticCurve, TxError, TxType}; - #[cfg(not(feature = "abcipp"))] - use crate::types::vote_extensions::VoteExtension; use crate::types::vote_extensions::{ ethereum_events, validator_set_update, }; @@ -83,14 +81,17 @@ mod protocol_txs { NewDkgKeypair(Tx), /// Ethereum events contained in vote extensions that /// are compressed before being included on chain + #[cfg(feature = "abcipp")] EthereumEvents(ethereum_events::VextDigest), /// Validator set updates contained in vote extensions + #[cfg(feature = "abcipp")] ValidatorSetUpdate(validator_set_update::VextDigest), - /// Protocol transaction type including Ethereum events - /// seen by validators and validator set updates signed - /// at the beginning of a new epoch + /// Ethereum events seen by some validator #[cfg(not(feature = "abcipp"))] - VoteExtension(VoteExtension), + EthEventsVext(ethereum_events::Vext), + /// Validator set update signed by some validator + #[cfg(not(feature = "abcipp"))] + ValSetUpdateVext(validator_set_update::Vext), } impl ProtocolTxType { diff --git a/shared/src/types/vote_extensions.rs b/shared/src/types/vote_extensions.rs index dace23a8ab..2a87ceea5c 100644 --- a/shared/src/types/vote_extensions.rs +++ b/shared/src/types/vote_extensions.rs @@ -29,6 +29,7 @@ pub struct VoteExtension { #[derive( Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema, )] +#[cfg(feature = "abcipp")] pub struct VoteExtensionDigest { /// The digest of Ethereum events vote extension signatures. pub ethereum_events: ethereum_events::VextDigest, From 598cfe97952626f13475ccf72979e820fcbf321e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 10:11:58 +0000 Subject: [PATCH 1690/1995] WIP: Removing digests from PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 039397e804..b61451814a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,12 +2,15 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; +use namada::ledger::storage_api::queries::QueriesExt; +#[cfg(feature = "abcipp")] +use namada::ledger::storage_api::queries::SendValsetUpd; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; +#[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtensionDigest; use super::super::*; @@ -16,9 +19,9 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, TxRecord, }; -use crate::node::ledger::shell::vote_extensions::{ - iter_protocol_txs, split_vote_extensions, -}; +#[cfg(feature = "abcipp")] +use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; +use crate::node::ledger::shell::vote_extensions::split_vote_extensions; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -101,39 +104,33 @@ where >, #[cfg(not(feature = "abcipp"))] txs: &[TxBytes], ) -> Vec { - // genesis block should not contain vote extensions + // genesis should not contain vote extensions if self.storage.last_height == BlockHeight(0) { return vec![]; } #[cfg(feature = "abcipp")] - let (eth_events, valset_upds) = split_vote_extensions( - local_last_commit - .expect( - "Honest Namada validators will always sign \ - ethereum_events::Vext instances, even if no Ethereum \ - events were observed at a given block height. In fact, a \ - quorum of signed empty ethereum_events::Vext instances \ - commits the fact no events were observed by a majority \ - of validators. Therefore, for block heights greater than \ - zero, we should always have vote extensions.", - ) - .votes, - ); - #[cfg(not(feature = "abcipp"))] - let (protocol_txs, eth_events, valset_upds) = - split_vote_extensions(txs); - - // TODO: remove this later, when we get rid of `abciplus` - #[cfg(feature = "abcipp")] - let protocol_txs = vec![]; + { + let (eth_events, valset_upds) = split_vote_extensions( + local_last_commit + .expect( + "Honest Namada validators will always sign \ + ethereum_events::Vext instances, even if no Ethereum \ + events were observed at a given block height. In \ + fact, a quorum of signed empty ethereum_events::Vext \ + instances commits the fact no events were observed \ + by a majority of validators. Therefore, for block \ + heights greater than zero, we should always have \ + vote extensions.", + ) + .votes, + ); - let ethereum_events = self - .compress_ethereum_events(eth_events) - .unwrap_or_else(|| panic!("{}", not_enough_voting_power_msg())); + let ethereum_events = self + .compress_ethereum_events(eth_events) + .unwrap_or_else(|| panic!("{}", not_enough_voting_power_msg())); - let validator_set_update = - if self + let validator_set_update = if self .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) { @@ -144,19 +141,23 @@ where None }; - let protocol_key = self - .mode - .get_protocol_key() - .expect("Validators should always have a protocol key"); + let protocol_key = self + .mode + .get_protocol_key() + .expect("Validators should always have a protocol key"); - iter_protocol_txs(VoteExtensionDigest { - ethereum_events, - validator_set_update, - }) - .map(|tx| tx.sign(protocol_key).to_bytes()) - // TODO: remove this later, when we get rid of `abciplus` - .chain(protocol_txs.into_iter()) - .collect() + iter_protocol_txs(VoteExtensionDigest { + ethereum_events, + validator_set_update, + }) + .map(|tx| tx.sign(protocol_key).to_bytes()) + .collect() + } + + #[cfg(not(feature = "abcipp"))] + { + split_vote_extensions(txs) + } } /// Builds a batch of mempool transactions @@ -228,16 +229,10 @@ where /// Returns a suitable message to be displayed when Tendermint /// somehow decides on a block containing vote extensions /// reflecting `<= 2/3` of the total stake. +#[cfg(feature = "abcipp")] const fn not_enough_voting_power_msg() -> &'static str { - #[cfg(feature = "abcipp")] - { - "A Tendermint quorum should never decide on a block including vote \ - extensions reflecting less than or equal to 2/3 of the total stake." - } - #[cfg(not(feature = "abcipp"))] - { - "CONSENSUS FAILURE!!!!!11one!" - } + "A Tendermint quorum should never decide on a block including vote \ + extensions reflecting less than or equal to 2/3 of the total stake." } /// Functions for creating the appropriate TxRecord given the From 9f8aaf449cb3e9fa9aefb62a18046dfdefbe0edc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 10:16:19 +0000 Subject: [PATCH 1691/1995] Split build_vote_extension_txs() into abcipp and not(abcipp) versions --- .../lib/node/ledger/shell/prepare_proposal.rs | 76 ++++++++++--------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b61451814a..e854ab73a2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -97,40 +97,36 @@ where /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update + #[cfg(feature = "abcipp")] fn build_vote_extensions_txs( &mut self, - #[cfg(feature = "abcipp")] local_last_commit: Option< - ExtendedCommitInfo, - >, - #[cfg(not(feature = "abcipp"))] txs: &[TxBytes], + local_last_commit: Option, ) -> Vec { // genesis should not contain vote extensions if self.storage.last_height == BlockHeight(0) { return vec![]; } - #[cfg(feature = "abcipp")] - { - let (eth_events, valset_upds) = split_vote_extensions( - local_last_commit - .expect( - "Honest Namada validators will always sign \ - ethereum_events::Vext instances, even if no Ethereum \ - events were observed at a given block height. In \ - fact, a quorum of signed empty ethereum_events::Vext \ - instances commits the fact no events were observed \ - by a majority of validators. Therefore, for block \ - heights greater than zero, we should always have \ - vote extensions.", - ) - .votes, - ); + let (eth_events, valset_upds) = split_vote_extensions( + local_last_commit + .expect( + "Honest Namada validators will always sign \ + ethereum_events::Vext instances, even if no Ethereum \ + events were observed at a given block height. In fact, a \ + quorum of signed empty ethereum_events::Vext instances \ + commits the fact no events were observed by a majority \ + of validators. Therefore, for block heights greater than \ + zero, we should always have vote extensions.", + ) + .votes, + ); - let ethereum_events = self - .compress_ethereum_events(eth_events) - .unwrap_or_else(|| panic!("{}", not_enough_voting_power_msg())); + let ethereum_events = self + .compress_ethereum_events(eth_events) + .unwrap_or_else(|| panic!("{}", not_enough_voting_power_msg())); - let validator_set_update = if self + let validator_set_update = + if self .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) { @@ -141,22 +137,28 @@ where None }; - let protocol_key = self - .mode - .get_protocol_key() - .expect("Validators should always have a protocol key"); + let protocol_key = self + .mode + .get_protocol_key() + .expect("Validators should always have a protocol key"); - iter_protocol_txs(VoteExtensionDigest { - ethereum_events, - validator_set_update, - }) - .map(|tx| tx.sign(protocol_key).to_bytes()) - .collect() - } + iter_protocol_txs(VoteExtensionDigest { + ethereum_events, + validator_set_update, + }) + .map(|tx| tx.sign(protocol_key).to_bytes()) + .collect() + } - #[cfg(not(feature = "abcipp"))] - { + /// Builds a batch of vote extension transactions, comprised of Ethereum + /// events and, optionally, a validator set update + #[cfg(not(feature = "abcipp"))] + fn build_vote_extensions_txs(&mut self, txs: &[TxBytes]) -> Vec { + if self.storage.last_height != BlockHeight(0) { split_vote_extensions(txs) + } else { + // genesis should not contain vote extensions + vec![] } } From e0c77b26f8d9a5876a56e376a29c8dd99e9a1550 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 10:16:43 +0000 Subject: [PATCH 1692/1995] Method rename --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e854ab73a2..eb72f17bb4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -52,9 +52,9 @@ where // add ethereum events and validator set updates as protocol txs #[cfg(feature = "abcipp")] - let txs = self.build_vote_extensions_txs(req.local_last_commit); + let txs = self.build_vote_extension_txs(req.local_last_commit); #[cfg(not(feature = "abcipp"))] - let mut txs = self.build_vote_extensions_txs(&req.txs); + let mut txs = self.build_vote_extension_txs(&req.txs); #[cfg(feature = "abcipp")] let mut txs: Vec = txs.into_iter().map(record::add).collect(); @@ -98,7 +98,7 @@ where /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update #[cfg(feature = "abcipp")] - fn build_vote_extensions_txs( + fn build_vote_extension_txs( &mut self, local_last_commit: Option, ) -> Vec { @@ -153,7 +153,7 @@ where /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update #[cfg(not(feature = "abcipp"))] - fn build_vote_extensions_txs(&mut self, txs: &[TxBytes]) -> Vec { + fn build_vote_extension_txs(&mut self, txs: &[TxBytes]) -> Vec { if self.storage.last_height != BlockHeight(0) { split_vote_extensions(txs) } else { From 62b2aeaaf2e5a5306a44cc1a8dbf1d67ddd95c6f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 10:25:43 +0000 Subject: [PATCH 1693/1995] WIP: Remove TxRecord --- .../lib/node/ledger/shell/prepare_proposal.rs | 76 +------------------ 1 file changed, 3 insertions(+), 73 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index eb72f17bb4..1c8fdba9fa 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -17,7 +17,7 @@ use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{ - tx_record::TxAction, ExtendedCommitInfo, TxRecord, + tx_record::TxAction, ExtendedCommitInfo, }; #[cfg(feature = "abcipp")] use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; @@ -55,20 +55,13 @@ where let txs = self.build_vote_extension_txs(req.local_last_commit); #[cfg(not(feature = "abcipp"))] let mut txs = self.build_vote_extension_txs(&req.txs); - #[cfg(feature = "abcipp")] - let mut txs: Vec = - txs.into_iter().map(record::add).collect(); // add mempool txs let mut mempool_txs = self.build_mempool_txs(req.txs); txs.append(&mut mempool_txs); // decrypt the wrapper txs included in the previous block - let decrypted_txs = self.build_decrypted_txs(); - #[cfg(feature = "abcipp")] - let decrypted_txs: Vec = - decrypted_txs.into_iter().map(record::add).collect(); - let mut decrypted_txs = decrypted_txs; + let mut decrypted_txs = self.build_decrypted_txs(); txs.append(&mut decrypted_txs); txs @@ -163,27 +156,6 @@ where } /// Builds a batch of mempool transactions - #[cfg(feature = "abcipp")] - fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + txs.len() / 2; - 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() - } - - /// Builds a batch of mempool transactions - #[cfg(not(feature = "abcipp"))] fn build_mempool_txs(&mut self, txs: Vec>) -> Vec { // filter in half of the new txs from Tendermint, only keeping // wrappers @@ -237,39 +209,6 @@ const fn not_enough_voting_power_msg() -> &'static str { extensions reflecting less than or equal to 2/3 of the total stake." } -/// Functions for creating the appropriate TxRecord given the -/// numeric code -#[cfg(feature = "abcipp")] -pub(super) mod record { - use super::*; - - /// 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, - } - } - - /// 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)] // TODO: write tests for validator set update vote extensions in // prepare proposals @@ -557,8 +496,7 @@ mod test_prepare_proposal { } } - /// Creates an Ethereum events digest manually, and encodes it as a - /// [`TxRecord`]. + /// Creates an Ethereum events digest manually. fn manually_assemble_digest( _protocol_key: &common::SecretKey, ext: Signed, @@ -596,11 +534,6 @@ mod test_prepare_proposal { ); vote_extension_digest - - // let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - // .sign(&protocol_key) - // .to_bytes(); - // super::record::add(tx) } /// Test if Ethereum events validation and inclusion in a block @@ -679,9 +612,6 @@ mod test_prepare_proposal { ); assert_eq!(rsp_digest, digest); - - // NOTE: this comparison will not work because of timestamps - // assert_eq!(rsp.tx_records, vec![digest]); } /// Test if Ethereum events validation and inclusion in a block From b0611548e90aeef33c5b669bb0657b1f921098c8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 11:03:59 +0000 Subject: [PATCH 1694/1995] WIP: ProcessProposal digest removal for non-abcipp builds --- .../lib/node/ledger/shell/process_proposal.rs | 101 ++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 40d5d24922..3a3c9ac04e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -12,10 +12,12 @@ use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; use crate::facade::tendermint_proto::abci::RequestProcessProposal; use crate::node::ledger::shims::abcipp_shim_types::shim::response::ProcessProposal; +use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; /// Contains stateful data about the number of vote extension /// digests found as protocol transactions in a proposed block. #[derive(Default)] +#[cfg(feature = "abcipp")] pub struct DigestCounters { /// The number of Ethereum events vote extensions found thus far. eth_ev_digest_num: usize, @@ -40,6 +42,7 @@ 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(feature = "abcipp")] pub fn process_proposal( &self, req: RequestProcessProposal, @@ -116,14 +119,67 @@ where } } - /// Checks what the [`TxResult`]s would be for the transactions in a - /// proposed block, as well as counting the number of digest transactions - /// present. `ProcessProposal` should be able to make a decision on whether - /// a proposed block is acceptable or not based solely on what this function - /// returns. + /// Check all the txs in a block. Some txs may be incorrect, + /// 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 = "abcipp"))] + pub fn process_proposal( + &self, + req: RequestProcessProposal, + ) -> ProcessProposal { + tracing::info!( + proposer = ?HEXUPPER.encode(&req.proposer_address), + height = req.height, + hash = ?HEXUPPER.encode(&req.hash), + n_txs = req.txs.len(), + "Received block proposal", + ); + let tx_results = self.check_proposal(&req.txs); + + // Erroneous transactions were detected when processing + // the leader's proposal. We allow txs that do not + // deserialize properly, that have invalid signatures + // and that have invalid wasm code to reach FinalizeBlock. + let invalid_txs = tx_results.iter().any(|res| { + let error = ErrorCodes::from_u32(res.code).expect( + "All error codes returned from process_single_tx are valid", + ); + !error.is_recoverable() + }); + if invalid_txs { + tracing::warn!( + proposer = ?HEXUPPER.encode(&req.proposer_address), + height = req.height, + hash = ?HEXUPPER.encode(&req.hash), + "Found invalid transactions, proposed block will be rejected" + ); + } + + let will_reject_proposal = invalid_txs; + + let status = if will_reject_proposal { + ProposalStatus::Reject + } else { + ProposalStatus::Accept + }; + + ProcessProposal { + status: status as i32, + tx_results, + } + } + + /// Evaluates the corresponding [`TxResult`] for each tx in a + /// proposed block, and counts the number of digest transactions. + /// + /// `ProcessProposal` should be able to make a decision on whether a + /// proposed block is acceptable or not based solely on what this + /// function returns. + #[cfg(feature = "abcipp")] pub fn check_proposal( &self, - txs: &[Vec], + txs: &[TxBytes], ) -> (Vec, DigestCounters) { let mut tx_queue_iter = self.storage.tx_queue.iter(); // the number of vote extension digests included in the block proposal @@ -141,11 +197,30 @@ where (tx_results, counters) } + /// Evaluates the corresponding [`TxResult`] for each tx in a + /// proposed block. + /// + /// `ProcessProposal` should be able to make a decision on whether a + /// proposed block is acceptable or not based solely on what this + /// function returns. + #[cfg(not(feature = "abcipp"))] + pub fn check_proposal(&self, txs: &[TxBytes]) -> Vec { + let mut tx_queue_iter = self.storage.tx_queue.iter(); + let tx_results: Vec<_> = txs + .iter() + .map(|tx_bytes| { + self.check_proposal_tx(tx_bytes, &mut tx_queue_iter) + }) + .collect(); + tx_results + } + /// Validates a list of vote extensions, included in PrepareProposal. /// /// If a vote extension is [`Some`], then it was validated properly, /// and the voting power of the validator who signed it is considered /// in the sum of the total voting power of all received vote extensions. + #[cfg(feature = "abcipp")] fn validate_vexts_in_proposal(&self, mut vote_extensions: I) -> TxResult where I: Iterator>, @@ -232,7 +307,7 @@ where &self, tx_bytes: &[u8], tx_queue_iter: &mut impl Iterator, - counters: &mut DigestCounters, + #[cfg(feature = "abcipp")] counters: &mut DigestCounters, ) -> TxResult { let maybe_tx = Tx::try_from(tx_bytes).map_or_else( |err| { @@ -275,6 +350,15 @@ where .into(), }, TxType::Protocol(protocol_tx) => match protocol_tx.tx { + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::EthEventsVext(_ext) => { + // TODO + } + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::ValSetUpdateVext(_ext) => { + // TODO + } + #[cfg(feature = "abcipp")] ProtocolTxType::EthereumEvents(digest) => { counters.eth_ev_digest_num += 1; let extensions = @@ -286,6 +370,7 @@ where self.validate_vexts_in_proposal(valid_extensions) } + #[cfg(feature = "abcipp")] ProtocolTxType::ValidatorSetUpdate(digest) => { if !self.storage.can_send_validator_set_update( SendValsetUpd::AtPrevHeight, @@ -389,6 +474,7 @@ where /// Checks if we have found the correct number of Ethereum events /// vote extensions in [`DigestCounters`]. + #[cfg(feature = "abcipp")] fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { #[cfg(feature = "abcipp")] { @@ -402,6 +488,7 @@ where /// Checks if we have found the correct number of validator set update /// vote extensions in [`DigestCounters`]. + #[cfg(feature = "abcipp")] fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { #[cfg(feature = "abcipp")] if self From 47d89d4c4bf40d2fbcf9955af8d84d3150d21648 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 11:06:42 +0000 Subject: [PATCH 1695/1995] WIP: Removing digests from ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 3a3c9ac04e..ef44fe4628 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -3,7 +3,9 @@ use data_encoding::HEXUPPER; use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; +use namada::ledger::storage_api::queries::QueriesExt; +#[cfg(feature = "abcipp")] +use namada::ledger::storage_api::queries::SendValsetUpd; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; @@ -476,21 +478,13 @@ where /// vote extensions in [`DigestCounters`]. #[cfg(feature = "abcipp")] fn has_proper_eth_events_num(&self, c: &DigestCounters) -> bool { - #[cfg(feature = "abcipp")] - { - self.storage.last_height.0 == 0 || c.eth_ev_digest_num == 1 - } - #[cfg(not(feature = "abcipp"))] - { - c.eth_ev_digest_num <= 1 - } + self.storage.last_height.0 == 0 || c.eth_ev_digest_num == 1 } /// Checks if we have found the correct number of validator set update /// vote extensions in [`DigestCounters`]. #[cfg(feature = "abcipp")] fn has_proper_valset_upd_num(&self, c: &DigestCounters) -> bool { - #[cfg(feature = "abcipp")] if self .storage .can_send_validator_set_update(SendValsetUpd::AtPrevHeight) @@ -499,10 +493,6 @@ where } else { true } - #[cfg(not(feature = "abcipp"))] - { - c.valset_upd_digest_num <= 1 - } } } From 78a3a8c4552c79e6b5270c620e8d6ef4d496cba5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 22 Nov 2022 12:03:16 +0000 Subject: [PATCH 1696/1995] Log at WARN level if geth exits with nonzero exit code --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 5ecd7a74cf..7b592fd816 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -40,7 +40,11 @@ pub async fn monitor( exit_status = node.wait() => { match exit_status { Ok(exit_status) => { - tracing::info!(%exit_status, "Ethereum fullnode exited"); + if exit_status.success() { + tracing::info!(%exit_status, "Ethereum fullnode exited"); + } else { + tracing::warn!(%exit_status, "Ethereum fullnode exited with nonzero exit code"); + } }, Err(err) => { tracing::warn!("Error while waiting for the Ethereum fullnode to exit: {}", err); From f5053cfc66cba09ab8088c29acc7caf8739c6a2b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 13:13:52 +0000 Subject: [PATCH 1697/1995] WIP: Validate protocol txs in ProcessProposal --- .../lib/node/ledger/shell/process_proposal.rs | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ef44fe4628..e2599edb54 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -353,13 +353,43 @@ where }, TxType::Protocol(protocol_tx) => match protocol_tx.tx { #[cfg(not(feature = "abcipp"))] - ProtocolTxType::EthEventsVext(_ext) => { - // TODO - } + ProtocolTxType::EthEventsVext(ext) => self + .validate_eth_events_vext_and_get_it_back( + ext, + shell.storage.last_height, + ) + .ok() + .map(|_| TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), + }) + .unwrap_or_else(|| TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this proposal \ + because one of the included Ethereum events \ + vote extensions was invalid." + .into(), + }), #[cfg(not(feature = "abcipp"))] - ProtocolTxType::ValSetUpdateVext(_ext) => { - // TODO - } + ProtocolTxType::ValSetUpdateVext(ext) => self + .validate_valset_upd_vext_and_get_it_back( + ext, + shell.storage.last_height, + ) + .ok() + .map(|_| TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), + }) + .unwrap_or_else(|| TxResult { + code: ErrorCodes::InvalidVoteExtension.into(), + info: "Process proposal rejected this proposal \ + because one of the included validator set \ + update vote extensions was invalid." + .into(), + }), #[cfg(feature = "abcipp")] ProtocolTxType::EthereumEvents(digest) => { counters.eth_ev_digest_num += 1; From c9e9663632f92564f60bc2ea18bc6d3eaa49d9f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 13:48:52 +0000 Subject: [PATCH 1698/1995] Add singleton vext digests constructor methods --- .../types/vote_extensions/ethereum_events.rs | 32 +++++++++++++++++++ .../vote_extensions/validator_set_update.rs | 17 ++++++++++ 2 files changed, 49 insertions(+) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 9e97692b53..ba19cd87cd 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -90,6 +90,38 @@ pub struct EthereumEventsVextDigest { } impl VextDigest { + /// Build a singleton [`VextDigest`], from the provided [`Vext`]. + #[inline] + #[cfg(not(feature = "abcipp"))] + pub fn singleton(ext: Signed) -> VextDigest { + VextDigest { + signatures: { + let mut m = HashMap::new(); + m.insert( + (ext.data.validator_addr.clone(), ext.data.block_height), + ext.sig, + ); + m + }, + events: ext + .data + .ethereum_events + .into_inter() + .map(|event| MultiSignedEthEvent { + event, + signers: { + let mut s = BTreeSet::new(); + s.insert(( + ext.data.validator_addr.clone(), + ext.data.block_height, + )); + s + }, + }) + .collect(), + } + } + /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, last_height: BlockHeight) -> Vec> { #[cfg(not(feature = "abcipp"))] diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index e9235b8e6f..85786dfc2c 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -46,6 +46,23 @@ pub struct ValidatorSetUpdateVextDigest { } impl VextDigest { + /// Build a singleton [`VextDigest`], from the provided [`Vext`]. + #[inline] + #[cfg(not(feature = "abcipp"))] + pub fn singleton(ext: Signed) -> VextDigest { + VextDigest { + signatures: { + let mut m = HashMap::new(); + m.insert( + (ext.data.validator_addr.clone(), ext.data.block_height), + ext.sig, + ); + m + }, + voting_powers: ext.data.voting_powers, + } + } + /// Decompresses a set of signed [`Vext`] instances. pub fn decompress(self, block_height: BlockHeight) -> Vec { #[cfg(not(feature = "abcipp"))] From 65b4ab480652e6168c4af0069a8e72c19bf6d062 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 13:52:41 +0000 Subject: [PATCH 1699/1995] Act on singleton digests --- shared/src/ledger/protocol/mod.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 888884b85a..c1aa908fab 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -202,6 +202,24 @@ where use crate::types::vote_extensions::ethereum_events; match tx { + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::EthEventsVext(ext) => { + let ethereum_events::VextDigest { events, .. } = + ethereum_events::VextDigest::singleton(ext); + self::transactions::ethereum_events::apply_derived_tx( + storage, events, + ) + .map_err(Error::ProtocolTxError) + } + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::ValSetUpdateVext(ext) => { + self::transactions::validator_set_update::aggregate_votes( + storage, + validator_set_update::VextDigest::singleton(ext), + ) + .map_err(Error::ProtocolTxError) + } + #[cfg(feature = "abcipp")] ProtocolTxType::EthereumEvents(ext) => { let ethereum_events::VextDigest { events, .. } = ext; self::transactions::ethereum_events::apply_derived_tx( @@ -209,6 +227,7 @@ where ) .map_err(Error::ProtocolTxError) } + #[cfg(feature = "abcipp")] ProtocolTxType::ValidatorSetUpdate(ext) => { // NOTE(feature = "abcipp"): we will not need to apply any // storage changes when we rollback to ABCI++; this is because From 2e3a0a65ce0a836e32760bffdc4f5daa77e6d112 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 14:33:15 +0000 Subject: [PATCH 1700/1995] Add missing import --- shared/src/ledger/protocol/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index c1aa908fab..f975fa470b 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -199,7 +199,9 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - use crate::types::vote_extensions::ethereum_events; + use crate::types::vote_extensions::{ + ethereum_events, validator_set_update, + }; match tx { #[cfg(not(feature = "abcipp"))] From ffb25a10f879d0113eff3ddea67d3d6e9bc6df17 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 14:36:14 +0000 Subject: [PATCH 1701/1995] Vote extension protocol txs should be signed --- shared/src/types/transaction/protocol.rs | 4 ++-- shared/src/types/vote_extensions/ethereum_events.rs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/shared/src/types/transaction/protocol.rs b/shared/src/types/transaction/protocol.rs index c658ac3dcc..3b4621a005 100644 --- a/shared/src/types/transaction/protocol.rs +++ b/shared/src/types/transaction/protocol.rs @@ -88,10 +88,10 @@ mod protocol_txs { ValidatorSetUpdate(validator_set_update::VextDigest), /// Ethereum events seen by some validator #[cfg(not(feature = "abcipp"))] - EthEventsVext(ethereum_events::Vext), + EthEventsVext(ethereum_events::SignedVext), /// Validator set update signed by some validator #[cfg(not(feature = "abcipp"))] - ValSetUpdateVext(validator_set_update::Vext), + ValSetUpdateVext(validator_set_update::SignedVext), } impl ProtocolTxType { diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index ba19cd87cd..1e3ae5ea84 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -14,6 +14,10 @@ use crate::types::storage::BlockHeight; /// Type alias for an [`EthereumEventsVext`]. pub type Vext = EthereumEventsVext; +/// Represents a [`Vext`] signed by some validator, with +/// a Namada protocol key. +pub type SignedVext = Signed; + /// Represents a set of [`EthereumEvent`] instances /// seen by some validator. /// From eaf02057562791cfcc42c1bc12fa2e8583939519 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 14:37:23 +0000 Subject: [PATCH 1702/1995] Use correct signed vext variant --- shared/src/types/vote_extensions/validator_set_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 85786dfc2c..027a38b05b 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -49,7 +49,7 @@ impl VextDigest { /// Build a singleton [`VextDigest`], from the provided [`Vext`]. #[inline] #[cfg(not(feature = "abcipp"))] - pub fn singleton(ext: Signed) -> VextDigest { + pub fn singleton(ext: SignedVext) -> VextDigest { VextDigest { signatures: { let mut m = HashMap::new(); From 4ae3ef555c2ba8a3d9ce9737530a9042a2cceebb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 14:38:11 +0000 Subject: [PATCH 1703/1995] Fix typo --- shared/src/types/vote_extensions/ethereum_events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index 1e3ae5ea84..e05818abb9 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -110,7 +110,7 @@ impl VextDigest { events: ext .data .ethereum_events - .into_inter() + .into_iter() .map(|event| MultiSignedEthEvent { event, signers: { From f31615915fa67bae99e802847b57ae4226fc1872 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 14:48:21 +0000 Subject: [PATCH 1704/1995] WIP: Progressively getting less yelled at by the almighty clippy --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 1 + apps/src/lib/node/ledger/shell/process_proposal.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 1c8fdba9fa..0f07221705 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,6 +2,7 @@ use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; +#[cfg(feature = "abcipp")] use namada::ledger::storage_api::queries::QueriesExt; #[cfg(feature = "abcipp")] use namada::ledger::storage_api::queries::SendValsetUpd; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e2599edb54..06ed44490f 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,6 +2,7 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use data_encoding::HEXUPPER; +#[cfg(feature = "abcipp")] use namada::ledger::pos::types::VotingPower; use namada::ledger::storage_api::queries::QueriesExt; #[cfg(feature = "abcipp")] @@ -356,7 +357,7 @@ where ProtocolTxType::EthEventsVext(ext) => self .validate_eth_events_vext_and_get_it_back( ext, - shell.storage.last_height, + self.storage.last_height, ) .ok() .map(|_| TxResult { @@ -375,7 +376,7 @@ where ProtocolTxType::ValSetUpdateVext(ext) => self .validate_valset_upd_vext_and_get_it_back( ext, - shell.storage.last_height, + self.storage.last_height, ) .ok() .map(|_| TxResult { From 6df142d27a38ff02a2d6fe64dce864fa39e6f520 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 15:16:19 +0000 Subject: [PATCH 1705/1995] WIP: Removing protocol tx digests --- apps/src/lib/node/ledger/shell/mod.rs | 20 ++++--- .../lib/node/ledger/shell/prepare_proposal.rs | 5 +- .../lib/node/ledger/shell/vote_extensions.rs | 54 ++++++++----------- 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8b3953cd78..fa9415eb3a 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -673,18 +673,22 @@ where { use namada::types::transaction::protocol::ProtocolTxType; + use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; + if let ShellMode::Validator { .. } = &self.mode { - let ext = self.craft_extension(); - let ext = self + let protocol_key = self .mode .get_protocol_key() - .map(|protocol_key| { - ProtocolTxType::VoteExtension(ext) - .sign(protocol_key) - .to_bytes() - }) .expect("Validators should have protocol keys"); - self.mode.broadcast(ext); + + let protocol_txs = iter_protocol_txs(self.craft_extension()) + .map(|protocol_tx| { + protocol_tx.sign(protocol_key).to_bytes() + }); + + for tx in protocol_txs { + self.mode.broadcast(tx); + } } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 0f07221705..d501323b13 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -20,8 +20,11 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; use crate::facade::tendermint_proto::abci::{ tx_record::TxAction, ExtendedCommitInfo, }; +#[cfg(not(feature = "abcipp"))] +use crate::node::ledger::shell::vote_extensions::deserialize_vote_extensions; #[cfg(feature = "abcipp")] use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; +#[cfg(feature = "abcipp")] use crate::node::ledger::shell::vote_extensions::split_vote_extensions; use crate::node::ledger::shell::{process_tx, ShellMode}; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -149,7 +152,7 @@ where #[cfg(not(feature = "abcipp"))] fn build_vote_extension_txs(&mut self, txs: &[TxBytes]) -> Vec { if self.storage.last_height != BlockHeight(0) { - split_vote_extensions(txs) + deserialize_vote_extensions(txs) } else { // genesis should not contain vote extensions vec![] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index b8e3558a79..c2a7f132e9 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -297,12 +297,12 @@ pub fn deserialize_vote_extensions( } /// Given a slice of [`TxBytes`], return an iterator over the -/// ones we could deserialize to [`VoteExtension`] +/// ones we could deserialize to vote extension [`ProtocolTx`] /// instances. #[cfg(not(feature = "abcipp"))] pub fn deserialize_vote_extensions( txs: &[TxBytes], -) -> impl Iterator + '_ { +) -> impl Iterator + '_ { use namada::types::transaction::protocol::ProtocolTx; txs.iter().filter_map(|tx_bytes| { @@ -318,9 +318,11 @@ pub fn deserialize_vote_extensions( }; match process_tx(tx).ok()? { TxType::Protocol(ProtocolTx { - tx: ProtocolTxType::VoteExtension(ext), + tx: + ProtocolTxType::EthEventsVext(_) + | ProtocolTxType::ValSetUpdateVext(_), .. - }) => Some((tx_bytes.clone(), ext)), + }) => Some(tx_bytes.clone()), _ => None, } }) @@ -342,6 +344,21 @@ pub fn iter_protocol_txs( .flatten() } +/// Yields an iterator over the [`ProtocolTxType`] transactions +/// in a [`VoteExtension`]. +#[cfg(not(feature = "abcipp"))] +pub fn iter_protocol_txs( + ext: VoteExtension, +) -> impl Iterator { + [ + Some(ProtocolTxType::EthEventsVext(ext.ethereum_events)), + ext.validator_set_update + .map(ProtocolTxType::ValSetUpdateVext), + ] + .into_iter() + .flatten() +} + /// Deserializes `vote_extensions` as [`VoteExtension`] instances, filtering /// out invalid data, and splits these into [`ethereum_events::Vext`] /// and [`validator_set_update::Vext`] instances. @@ -364,32 +381,3 @@ pub fn split_vote_extensions( (eth_evs, valset_upds) } - -/// Deserializes [`VoteExtension`] instances from mempool protocol txs, -/// filtering out non-protocol txs, and splits these into -/// [`ethereum_events::Vext`] and [`validator_set_update::Vext`] instances. -/// -/// The original [`TxBytes`] are also returned, such that we can remove -/// them from Tendermint's mempool. -#[cfg(not(feature = "abcipp"))] -pub fn split_vote_extensions( - mempool_txs: &[TxBytes], -) -> ( - Vec, - Vec>, - Vec, -) { - let mut txs = vec![]; - let mut eth_evs = vec![]; - let mut valset_upds = vec![]; - - for (tx, ext) in deserialize_vote_extensions(mempool_txs) { - if let Some(validator_set_update) = ext.validator_set_update { - valset_upds.push(validator_set_update); - } - eth_evs.push(ext.ethereum_events); - txs.push(tx); - } - - (txs, eth_evs, valset_upds) -} From 77f62b817c1138ba4a2695ffa2664420c56d2e4f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 15:42:32 +0000 Subject: [PATCH 1706/1995] WIP: Removing digests --- apps/src/lib/node/ledger/shell/finalize_block.rs | 13 +++++++++++++ apps/src/lib/node/ledger/shell/mod.rs | 11 +++++------ apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a02c9b7559..72ca9d88fb 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -149,6 +149,18 @@ where continue; } TxType::Protocol(protocol_tx) => match protocol_tx.tx { + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::EthEventsVext(ref ext) => { + for event in ext.data.ethereum_events.iter() { + self.mode.deque_eth_event(event); + } + Event::new_tx_event(&tx_type, height.0) + } + #[cfg(not(feature = "abcipp"))] + ProtocolTxType::ValSetUpdateVext(_) => { + Event::new_tx_event(&tx_type, height.0) + } + #[cfg(feature = "abcipp")] ProtocolTxType::EthereumEvents(ref digest) => { for event in digest.events.iter().map(|signed| &signed.event) @@ -157,6 +169,7 @@ where } Event::new_tx_event(&tx_type, height.0) } + #[cfg(feature = "abcipp")] ProtocolTxType::ValidatorSetUpdate(_) => { Event::new_tx_event(&tx_type, height.0) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index fa9415eb3a..02692e64aa 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -671,20 +671,19 @@ where #[cfg(not(feature = "abcipp"))] { - use namada::types::transaction::protocol::ProtocolTxType; - use crate::node::ledger::shell::vote_extensions::iter_protocol_txs; if let ShellMode::Validator { .. } = &self.mode { + let ext = self.craft_extension(); + let protocol_key = self .mode .get_protocol_key() .expect("Validators should have protocol keys"); - let protocol_txs = iter_protocol_txs(self.craft_extension()) - .map(|protocol_tx| { - protocol_tx.sign(protocol_key).to_bytes() - }); + let protocol_txs = iter_protocol_txs(ext).map(|protocol_tx| { + protocol_tx.sign(protocol_key).to_bytes() + }); for tx in protocol_txs { self.mode.broadcast(tx); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d501323b13..3007e98ca3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -152,7 +152,7 @@ where #[cfg(not(feature = "abcipp"))] fn build_vote_extension_txs(&mut self, txs: &[TxBytes]) -> Vec { if self.storage.last_height != BlockHeight(0) { - deserialize_vote_extensions(txs) + deserialize_vote_extensions(txs).collect() } else { // genesis should not contain vote extensions vec![] diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 6975207c07..3d1ebbe10b 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -137,7 +137,7 @@ impl AbcippShim { } #[cfg(not(feature = "abcipp"))] Req::EndBlock(_) => { - let (processing_results, _) = + let processing_results = self.service.check_proposal(&self.delivered_txs); let mut txs = Vec::with_capacity(self.delivered_txs.len()); let mut delivered = vec![]; From 33052bd3ccd6e9e457830bf72ad72bd83afe9e5c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 16:04:10 +0000 Subject: [PATCH 1707/1995] WIP: Fixing ProcessProposal unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 61 ++++++++++++++++--- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 06ed44490f..eb0e93b6c0 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -555,6 +555,7 @@ mod test_process_proposal { use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; use crate::wallet; + #[cfg(feature = "abcipp")] fn get_empty_eth_ev_digest(shell: &TestShell) -> TxBytes { let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let addr = shell @@ -870,7 +871,11 @@ mod test_process_proposal { .to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { - txs: vec![tx.clone(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + tx.clone(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell @@ -950,7 +955,11 @@ mod test_process_proposal { panic!("Test failed"); }; let request = ProcessProposal { - txs: vec![new_tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + new_tx.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [response, _] = shell .process_proposal(request) @@ -995,7 +1004,11 @@ mod test_process_proposal { .sign(&keypair) .expect("Test failed"); let request = ProcessProposal { - txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + wrapper.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell .process_proposal(request) @@ -1041,7 +1054,11 @@ mod test_process_proposal { .expect("Test failed"); let request = ProcessProposal { - txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + wrapper.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell @@ -1089,7 +1106,11 @@ mod test_process_proposal { txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(tx)))); } let req_1 = ProcessProposal { - txs: vec![txs[0].to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + txs[0].to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response_1 = if let [resp, _] = shell .process_proposal(req_1) @@ -1103,7 +1124,11 @@ mod test_process_proposal { assert_eq!(response_1.result.code, u32::from(ErrorCodes::Ok)); let req_2 = ProcessProposal { - txs: vec![txs[2].to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + txs[2].to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response_2 = if let Err(TestError::RejectProposal(resp)) = @@ -1155,7 +1180,11 @@ mod test_process_proposal { Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); let request = ProcessProposal { - txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + tx.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell @@ -1209,7 +1238,11 @@ mod test_process_proposal { ))); let request = ProcessProposal { - txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + tx.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell .process_proposal(request) @@ -1252,7 +1285,11 @@ mod test_process_proposal { wrapper.clone(), ))); let request = ProcessProposal { - txs: vec![signed.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + signed.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell .process_proposal(request) @@ -1311,7 +1348,11 @@ mod test_process_proposal { ); let tx = Tx::from(TxType::Raw(tx)); let request = ProcessProposal { - txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + txs: vec![ + tx.to_bytes(), + #[cfg(feature = "abcipp")] + get_empty_eth_ev_digest(&shell), + ], }; let response = if let [resp, _] = shell .process_proposal(request) From 172f869c6683dea6006038a9d0cf6780b2808210 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 16:16:25 +0000 Subject: [PATCH 1708/1995] Remove unused import --- apps/src/lib/node/ledger/shell/process_proposal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index eb0e93b6c0..4ee8c724f0 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -552,7 +552,6 @@ mod test_process_proposal { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; - use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; use crate::wallet; #[cfg(feature = "abcipp")] From 0932b4650b24a89daf680d1105f96dc92f567ef9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 22 Nov 2022 16:28:14 +0000 Subject: [PATCH 1709/1995] WIP: Splitting ProcessProposal unit tests based on abcipp feature flag --- .../lib/node/ledger/shell/process_proposal.rs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 4ee8c724f0..9a75c0e82e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -552,6 +552,7 @@ mod test_process_proposal { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; use crate::wallet; #[cfg(feature = "abcipp")] @@ -1372,3 +1373,45 @@ mod test_process_proposal { ); } } + +#[cfg(all(test, feature = "abcipp"))] +mod test_process_proposal_abcipp { + //! We test the failure cases of [`Shell::process_proposal`]. The happy + //! flows are covered by the e2e tests. + //! + //! These tests are specific to `abcipp` builds of the ledger. +} + +#[cfg(all(test, not(feature = "abcipp")))] +mod test_process_proposal_abciplus { + //! We test the failure cases of [`Shell::process_proposal`]. The happy + //! flows are covered by the e2e tests. + //! + //! These tests are specific to `abciplus` builds of the ledger. + + fn check_rejected_eth_events( + shell: &mut TestShell, + vote_extension: ethereum_events::Vext, + protocol_key: common::SecretKey, + ) { + let tx = ProtocolTxType::EthEventsVext(vote_extension) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExtension) + ); + } +} From d2f748c04aa17c06f4826e3550a0196038cc3136 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 09:33:06 +0000 Subject: [PATCH 1710/1995] WIP: Fixing ProcessProposal unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 86 ++++++++----------- 1 file changed, 34 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9a75c0e82e..1937c1e8c3 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -552,6 +552,7 @@ mod test_process_proposal { use crate::node::ledger::shell::test_utils::{ self, gen_keypair, ProcessProposal, TestError, TestShell, }; + #[cfg(feature = "abcipp")] use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; use crate::wallet; @@ -590,6 +591,7 @@ mod test_process_proposal { /// Test that if a proposal contains more than one /// `ethereum_events::VextDigest`, we reject it. #[test] + #[cfg(feature = "abcipp")] fn test_more_than_one_vext_digest_rejected() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); let (mut shell, _recv, _) = test_utils::setup(); @@ -610,13 +612,7 @@ mod test_process_proposal { ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); - #[cfg(feature = "abcipp")] s.insert(validator_addr, signed_vote_extension.sig); - #[cfg(not(feature = "abcipp"))] - s.insert( - (validator_addr, LAST_HEIGHT), - signed_vote_extension.sig, - ); s }, events: vec![], @@ -625,7 +621,7 @@ mod test_process_proposal { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) .sign(&protocol_key) .to_bytes(); - #[allow(clippy::redundant_clone)] + //#[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone(), tx], }; @@ -635,6 +631,7 @@ mod test_process_proposal { ); } + #[cfg(feature = "abcipp")] fn check_rejected_eth_events_digest( shell: &mut TestShell, vote_extension_digest: ethereum_events::VextDigest, @@ -661,10 +658,37 @@ mod test_process_proposal { ); } + #[cfg(not(feature = "abcipp"))] + fn check_rejected_eth_events( + shell: &mut TestShell, + vote_extension: ethereum_events::Vext, + protocol_key: common::SecretKey, + ) { + let tx = ProtocolTxType::EthEventsVext(vote_extension) + .sign(&protocol_key) + .to_bytes(); + let request = ProcessProposal { txs: vec![tx] }; + let response = if let Err(TestError::RejectProposal(resp)) = + shell.process_proposal(request) + { + if let [resp] = resp.as_slice() { + resp.clone() + } else { + panic!("Test failed") + } + } else { + panic!("Test failed") + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::InvalidVoteExtension) + ); + } + /// Test that if a proposal contains Ethereum events with /// invalid validator signatures, we reject it. #[test] - fn test_drop_vext_digest_with_invalid_sigs() { + fn test_drop_vext_with_invalid_sigs() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; @@ -721,7 +745,7 @@ mod test_process_proposal { /// Test that if a proposal contains Ethereum events with /// invalid block heights, we reject it. #[test] - fn test_drop_vext_digest_with_invalid_bheights() { + fn test_drop_vext_with_invalid_bheights() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); let (mut shell, _recv, _) = test_utils::setup(); @@ -790,7 +814,7 @@ mod test_process_proposal { /// Test that if a proposal contains Ethereum events with /// invalid validators, we reject it. #[test] - fn test_drop_vext_digest_with_invalid_validators() { + fn test_drop_vext_with_invalid_validators() { const LAST_HEIGHT: BlockHeight = BlockHeight(2); let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; @@ -1373,45 +1397,3 @@ mod test_process_proposal { ); } } - -#[cfg(all(test, feature = "abcipp"))] -mod test_process_proposal_abcipp { - //! We test the failure cases of [`Shell::process_proposal`]. The happy - //! flows are covered by the e2e tests. - //! - //! These tests are specific to `abcipp` builds of the ledger. -} - -#[cfg(all(test, not(feature = "abcipp")))] -mod test_process_proposal_abciplus { - //! We test the failure cases of [`Shell::process_proposal`]. The happy - //! flows are covered by the e2e tests. - //! - //! These tests are specific to `abciplus` builds of the ledger. - - fn check_rejected_eth_events( - shell: &mut TestShell, - vote_extension: ethereum_events::Vext, - protocol_key: common::SecretKey, - ) { - let tx = ProtocolTxType::EthEventsVext(vote_extension) - .sign(&protocol_key) - .to_bytes(); - let request = ProcessProposal { txs: vec![tx] }; - let response = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(request) - { - if let [resp] = resp.as_slice() { - resp.clone() - } else { - panic!("Test failed") - } - } else { - panic!("Test failed") - }; - assert_eq!( - response.result.code, - u32::from(ErrorCodes::InvalidVoteExtension) - ); - } -} From b636fde2d466b1612af80e22a849c7870ee346a2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 09:47:55 +0000 Subject: [PATCH 1711/1995] WIP: Fixing test_drop_vext_with_invalid_sigs() unit test --- .../lib/node/ledger/shell/process_proposal.rs | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1937c1e8c3..669cdbc167 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -544,9 +544,9 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; - use namada::types::vote_extensions::ethereum_events::{ - self, MultiSignedEthEvent, - }; + use namada::types::vote_extensions::ethereum_events; + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use super::*; use crate::node::ledger::shell::test_utils::{ @@ -693,53 +693,53 @@ mod test_process_proposal { let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _, _) = wallet::defaults::validator_keys(); - let vote_extension_digest = { - let addr = wallet::defaults::validator_address(); - let event = EthereumEvent::TransfersToNamada { - nonce: 1u64.into(), - transfers: vec![], - }; - let ext = { - // generate a valid signature - let mut ext = ethereum_events::Vext { - validator_addr: addr.clone(), - block_height: LAST_HEIGHT, - ethereum_events: vec![event.clone()], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + let addr = wallet::defaults::validator_address(); + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ext = { + // generate a valid signature + #[allow(clippy::redundant_clone)] + let mut ext = ethereum_events::Vext { + validator_addr: addr.clone(), + block_height: LAST_HEIGHT, + ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - // modify this signature such that it becomes invalid - ext.sig = test_utils::invalidate_signature(ext.sig); - ext - }; - ethereum_events::VextDigest { + // modify this signature such that it becomes invalid + ext.sig = test_utils::invalidate_signature(ext.sig); + ext + }; + #[cfg(feature = "abcipp")] + { + let vote_extension_digest = ethereum_events::VextDigest { signatures: { let mut s = HashMap::new(); - #[cfg(feature = "abcipp")] s.insert(addr.clone(), ext.sig); - #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), LAST_HEIGHT), ext.sig); s }, events: vec![MultiSignedEthEvent { event, signers: { let mut s = BTreeSet::new(); - #[cfg(feature = "abcipp")] s.insert(addr); - #[cfg(not(feature = "abcipp"))] - s.insert((addr, LAST_HEIGHT)); s }, }], - } - }; - check_rejected_eth_events_digest( - &mut shell, - vote_extension_digest, - protocol_key, - ); + }; + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); + } + #[cfg(not(feature = "abcipp"))] + { + check_rejected_eth_events(&mut shell, ext, protocol_key); + } } /// Test that if a proposal contains Ethereum events with From a52f96e89a63fcc224445058144ab7447821c34c Mon Sep 17 00:00:00 2001 From: James Date: Wed, 23 Nov 2022 10:27:58 +0000 Subject: [PATCH 1712/1995] Update apps/src/lib/node/ledger/ethereum_node/mod.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/node/ledger/ethereum_node/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/mod.rs b/apps/src/lib/node/ledger/ethereum_node/mod.rs index 7b592fd816..def2bbad96 100644 --- a/apps/src/lib/node/ledger/ethereum_node/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/mod.rs @@ -47,7 +47,7 @@ pub async fn monitor( } }, Err(err) => { - tracing::warn!("Error while waiting for the Ethereum fullnode to exit: {}", err); + tracing::warn!("Error while waiting for the Ethereum fullnode to exit: {err}"); tracing::info!("Ensuring Ethereum fullnode is shut down..."); node.kill().await; }, From a13b52bdf869e40436cc6a31f94974400d3cbdec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 11:38:17 +0000 Subject: [PATCH 1713/1995] CheckTx: Reject invalid protocol txs --- apps/src/lib/node/ledger/shell/mod.rs | 56 ++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 02692e64aa..a15998e488 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -702,14 +702,68 @@ where tx_bytes: &[u8], r#_type: MempoolTxType, ) -> response::CheckTx { + use namada::types::transaction::protocol::{ + ProtocolTx, ProtocolTxType, + }; + let mut response = response::CheckTx::default(); + const VALID_MSG: &str = "Mempool validation passed"; + match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { - Ok(_) => response.log = String::from("Mempool validation passed"), + Ok(tx) => { + match process_tx(tx) { + #[cfg(not(feature = "abcipp"))] + Ok(TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::EthEventsVext(ext), + .. + })) => { + if self.validate_eth_events_vext( + ext, + self.storage.last_height, + ) { + response.log = String::from(VALID_MSG); + } else { + response.code = 1; + response.log = String::from( + "Invalid Ethereum events vote extension", + ); + } + } + #[cfg(not(feature = "abcipp"))] + Ok(TxType::Protocol(ProtocolTx { + tx: ProtocolTxType::ValSetUpdateVext(ext), + .. + })) => { + if self.validate_valset_upd_vext( + ext, + self.storage.last_height, + ) { + response.log = String::from(VALID_MSG); + } else { + response.code = 1; + response.log = String::from( + "Invalid validator set update vote extension", + ); + } + } + Ok(TxType::Protocol(ProtocolTx { tx, .. })) => { + response.code = 1; + response.log = format!( + "The following protocol tx cannot be added to the \ + mempool: {tx:?}" + ); + } + // `process_tx` errors are handled by + // `Shell::finalize_block` + _ => response.log = String::from(VALID_MSG), + } + } Err(msg) => { response.code = 1; response.log = msg.to_string(); } } + response } From b85a95716c49e29457de6835f1833b1c2defae37 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 11:39:09 +0000 Subject: [PATCH 1714/1995] Remove dead code hints --- apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs | 1 - apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 076687ec56..2be4a2bdf4 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -32,7 +32,6 @@ where /// * The validator signed over the correct height inside of the extension. /// * There are no duplicate Ethereum events in this vote extension, and /// the events are sorted in ascending order. - #[allow(dead_code)] #[inline] pub fn validate_eth_events_vext( &self, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 985c3ba2a5..719476a42f 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -34,7 +34,6 @@ where /// * The voting powers are normalized to `2^32`, and sorted in descending /// order. #[inline] - #[allow(dead_code)] pub fn validate_valset_upd_vext( &self, ext: validator_set_update::SignedVext, From a9fed34a29184318b4caadebaba588cf7ae3f131 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 14:23:08 +0000 Subject: [PATCH 1715/1995] WIP: Fixing unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 169 +++++++++--------- 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 669cdbc167..558a3966ce 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -751,63 +751,54 @@ mod test_process_proposal { let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _, _) = wallet::defaults::validator_keys(); - let vote_extension_digest = { - let addr = wallet::defaults::validator_address(); - let event = EthereumEvent::TransfersToNamada { - nonce: 1u64.into(), - transfers: vec![], - }; - let ext = { - let ext = ethereum_events::Vext { - validator_addr: addr.clone(), - block_height: PRED_LAST_HEIGHT, - ethereum_events: vec![event.clone()], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - ext - }; - ethereum_events::VextDigest { - signatures: { - let mut s = HashMap::new(); + let addr = wallet::defaults::validator_address(); + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ext = { + #[allow(clippy::redundant_clone)] + let ext = ethereum_events::Vext { + validator_addr: addr.clone(), + block_height: PRED_LAST_HEIGHT, + ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let vote_extension_digest = ethereum_events::VextDigest { + signatures: { + let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] + s.insert(addr.clone(), ext.sig); + #[cfg(not(feature = "abcipp"))] + s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); + s + }, + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] - s.insert(addr.clone(), ext.sig); + s.insert(addr); #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); + s.insert((addr, PRED_LAST_HEIGHT)); s }, - events: vec![MultiSignedEthEvent { - event, - signers: { - let mut s = BTreeSet::new(); - #[cfg(feature = "abcipp")] - s.insert(addr); - #[cfg(not(feature = "abcipp"))] - s.insert((addr, PRED_LAST_HEIGHT)); - s - }, - }], - } + }], }; #[cfg(feature = "abcipp")] - check_rejected_eth_events_digest( - &mut shell, - vote_extension_digest, - protocol_key, - ); + { + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); + } #[cfg(not(feature = "abcipp"))] { - let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) - .sign(&protocol_key) - .to_bytes(); - let request = ProcessProposal { txs: vec![tx] }; - if let Ok(mut resp) = shell.process_proposal(request) { - assert_eq!(resp.len(), 1); - let processed = resp.remove(0); - assert_eq!(processed.result.code, ErrorCodes::Ok as u32); - } else { - panic!("Test failed"); - } + check_rejected_eth_events(&mut shell, ext, protocol_key); } } @@ -823,48 +814,54 @@ mod test_process_proposal { let bertha_addr = wallet::defaults::bertha_address(); (bertha_addr, bertha_key) }; - let vote_extension_digest = { - let event = EthereumEvent::TransfersToNamada { - nonce: 1u64.into(), - transfers: vec![], - }; - let ext = { - let ext = ethereum_events::Vext { - validator_addr: addr.clone(), - block_height: LAST_HEIGHT, - ethereum_events: vec![event.clone()], - } - .sign(&protocol_key); - assert!(ext.verify(&protocol_key.ref_to()).is_ok()); - ext - }; - ethereum_events::VextDigest { - signatures: { - let mut s = HashMap::new(); + let event = EthereumEvent::TransfersToNamada { + nonce: 1u64.into(), + transfers: vec![], + }; + let ext = { + #[allow(clippy::redundant_clone)] + let ext = ethereum_events::Vext { + validator_addr: addr.clone(), + block_height: LAST_HEIGHT, + ethereum_events: vec![event.clone()], + } + .sign(&protocol_key); + assert!(ext.verify(&protocol_key.ref_to()).is_ok()); + ext + }; + let vote_extension_digest = ethereum_events::VextDigest { + signatures: { + let mut s = HashMap::new(); + #[cfg(feature = "abcipp")] + s.insert(addr.clone(), ext.sig); + #[cfg(not(feature = "abcipp"))] + s.insert((addr.clone(), LAST_HEIGHT), ext.sig); + s + }, + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = BTreeSet::new(); #[cfg(feature = "abcipp")] - s.insert(addr.clone(), ext.sig); + s.insert(addr); #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), LAST_HEIGHT), ext.sig); + s.insert((addr, LAST_HEIGHT)); s }, - events: vec![MultiSignedEthEvent { - event, - signers: { - let mut s = BTreeSet::new(); - #[cfg(feature = "abcipp")] - s.insert(addr); - #[cfg(not(feature = "abcipp"))] - s.insert((addr, LAST_HEIGHT)); - s - }, - }], - } + }], }; - check_rejected_eth_events_digest( - &mut shell, - vote_extension_digest, - protocol_key, - ); + #[cfg(feature = "abcipp")] + { + check_rejected_eth_events_digest( + &mut shell, + vote_extension_digest, + protocol_key, + ); + } + #[cfg(not(feature = "abcipp"))] + { + check_rejected_eth_events(&mut shell, ext, protocol_key); + } } /// Test that if a wrapper tx is not signed, it is rejected From b979862272a91d3323f504d33965a8fc054e6ee0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 14:26:09 +0000 Subject: [PATCH 1716/1995] WIP: Feature gate digests --- .../lib/node/ledger/shell/process_proposal.rs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 558a3966ce..6b97ead671 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -767,29 +767,29 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - let vote_extension_digest = ethereum_events::VextDigest { - signatures: { - let mut s = HashMap::new(); - #[cfg(feature = "abcipp")] - s.insert(addr.clone(), ext.sig); - #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); - s - }, - events: vec![MultiSignedEthEvent { - event, - signers: { - let mut s = BTreeSet::new(); + #[cfg(feature = "abcipp")] + { + let vote_extension_digest = ethereum_events::VextDigest { + signatures: { + let mut s = HashMap::new(); #[cfg(feature = "abcipp")] - s.insert(addr); + s.insert(addr.clone(), ext.sig); #[cfg(not(feature = "abcipp"))] - s.insert((addr, PRED_LAST_HEIGHT)); + s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); s }, - }], - }; - #[cfg(feature = "abcipp")] - { + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = BTreeSet::new(); + #[cfg(feature = "abcipp")] + s.insert(addr); + #[cfg(not(feature = "abcipp"))] + s.insert((addr, PRED_LAST_HEIGHT)); + s + }, + }], + }; check_rejected_eth_events_digest( &mut shell, vote_extension_digest, @@ -829,29 +829,29 @@ mod test_process_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - let vote_extension_digest = ethereum_events::VextDigest { - signatures: { - let mut s = HashMap::new(); - #[cfg(feature = "abcipp")] - s.insert(addr.clone(), ext.sig); - #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), LAST_HEIGHT), ext.sig); - s - }, - events: vec![MultiSignedEthEvent { - event, - signers: { - let mut s = BTreeSet::new(); + #[cfg(feature = "abcipp")] + { + let vote_extension_digest = ethereum_events::VextDigest { + signatures: { + let mut s = HashMap::new(); #[cfg(feature = "abcipp")] - s.insert(addr); + s.insert(addr.clone(), ext.sig); #[cfg(not(feature = "abcipp"))] - s.insert((addr, LAST_HEIGHT)); + s.insert((addr.clone(), LAST_HEIGHT), ext.sig); s }, - }], - }; - #[cfg(feature = "abcipp")] - { + events: vec![MultiSignedEthEvent { + event, + signers: { + let mut s = BTreeSet::new(); + #[cfg(feature = "abcipp")] + s.insert(addr); + #[cfg(not(feature = "abcipp"))] + s.insert((addr, LAST_HEIGHT)); + s + }, + }], + }; check_rejected_eth_events_digest( &mut shell, vote_extension_digest, From 743d8395f3304c3b93aebedd939a05b004447f59 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 14:27:44 +0000 Subject: [PATCH 1717/1995] Misc fixes --- apps/src/lib/node/ledger/shell/process_proposal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6b97ead671..44b2527a3a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -531,8 +531,10 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { + #[cfg(feature = "abcipp")] use std::collections::HashMap; + #[cfg(feature = "abcipp")] use assert_matches::assert_matches; use borsh::BorshDeserialize; use namada::proto::SignedTxData; @@ -661,7 +663,7 @@ mod test_process_proposal { #[cfg(not(feature = "abcipp"))] fn check_rejected_eth_events( shell: &mut TestShell, - vote_extension: ethereum_events::Vext, + vote_extension: ethereum_events::SignedVext, protocol_key: common::SecretKey, ) { let tx = ProtocolTxType::EthEventsVext(vote_extension) From d413e8ab0f2e7706e62fe8c9f3e7ebdcab900f24 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 14:52:22 +0000 Subject: [PATCH 1718/1995] WIP: Fixing PrepareProposal unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3007e98ca3..270eb3fa48 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -501,6 +501,7 @@ mod test_prepare_proposal { } /// Creates an Ethereum events digest manually. + #[cfg(feature = "abcipp")] fn manually_assemble_digest( _protocol_key: &common::SecretKey, ext: Signed, @@ -637,7 +638,7 @@ mod test_prepare_proposal { nonce: 1u64.into(), transfers: vec![], }; - let signed_vote_extension = { + let ext = { let ext = ethereum_events::Vext { validator_addr, block_height: LAST_HEIGHT, @@ -648,23 +649,16 @@ mod test_prepare_proposal { ext }; - let rsp_digest = { - let vote_extension = VoteExtension { - ethereum_events: signed_vote_extension.clone(), - validator_set_update: None, - }; - let tx = ProtocolTxType::VoteExtension(vote_extension) + let rsp_ext = { + let tx = ProtocolTxType::EthEventsVext(ext.clone()) .sign(&protocol_key) .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { txs: vec![tx], ..Default::default() }); - assert_eq!(rsp.txs.len(), 3); + assert_eq!(rsp.txs.len(), 1); - // NOTE: we remove the first pos, bc the ethereum events - // vote extension protocol tx will always precede the - // valset upd vext protocol tx let tx_bytes = rsp.txs.remove(0); let got = Tx::try_from(&tx_bytes[..]).unwrap(); let got_signed_tx = @@ -678,21 +672,12 @@ mod test_prepare_proposal { }; match protocol_tx { - ProtocolTxType::EthereumEvents(digest) => digest, + ProtocolTxType::EthEventsVext(ext) => ext, _ => panic!("Test failed"), } }; - let digest = manually_assemble_digest( - &protocol_key, - signed_vote_extension, - LAST_HEIGHT, - ); - - assert_eq!(rsp_digest, digest); - - // NOTE: this comparison will not work because of timestamps - // assert_eq!(rsp.tx_records, vec![digest]); + assert_eq!(rsp_ext, ext); } /// Test if Ethereum events validation and inclusion in a block From 31dc2ce44767c28555bc8134cceb08e7dc5db8bd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 15:02:30 +0000 Subject: [PATCH 1719/1995] Fix PrepareProposal unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 270eb3fa48..b21d27f33f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -217,6 +217,7 @@ const fn not_enough_voting_power_msg() -> &'static str { // TODO: write tests for validator set update vote extensions in // prepare proposals mod test_prepare_proposal { + #[cfg(feature = "abcipp")] use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; @@ -228,13 +229,14 @@ mod test_prepare_proposal { use namada::proto::{Signed, SignedTxData}; use namada::types::address::nam; use namada::types::ethereum_events::EthereumEvent; - use namada::types::key::{common, RefTo}; + #[cfg(feature = "abcipp")] + use namada::types::key::common; + use namada::types::key::RefTo; use namada::types::storage::{BlockHeight, Epoch}; use namada::types::transaction::protocol::ProtocolTxType; use namada::types::transaction::{Fee, TxType, WrapperTx}; - use namada::types::vote_extensions::ethereum_events::{ - self, MultiSignedEthEvent, - }; + use namada::types::vote_extensions::ethereum_events; + #[cfg(feature = "abcipp")] use namada::types::vote_extensions::VoteExtension; use super::*; @@ -507,26 +509,19 @@ mod test_prepare_proposal { ext: Signed, last_height: BlockHeight, ) -> ethereum_events::VextDigest { + use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; + let events = vec![MultiSignedEthEvent { event: ext.data.ethereum_events[0].clone(), signers: { let mut s = BTreeSet::new(); - #[cfg(feature = "abcipp")] s.insert(ext.data.validator_addr.clone()); - #[cfg(not(feature = "abcipp"))] - s.insert((ext.data.validator_addr.clone(), last_height)); s }, }]; let signatures = { let mut s = HashMap::new(); - #[cfg(feature = "abcipp")] - s.insert(ext.data.validator_addr.clone(), ext.sig.clone()); - #[cfg(not(feature = "abcipp"))] - s.insert( - (ext.data.validator_addr.clone(), last_height), - ext.sig.clone(), - ); + s.insert(ext.data.validator_addr, ext.sig.clone()); s }; @@ -750,13 +745,12 @@ mod test_prepare_proposal { assert!(ext.verify(&protocol_key.ref_to()).is_ok()); ext }; - #[allow(clippy::redundant_clone)] - let vote_extension = VoteExtension { - ethereum_events: signed_eth_ev_vote_extension.clone(), - validator_set_update: None, - }; #[cfg(feature = "abcipp")] { + let vote_extension = VoteExtension { + ethereum_events: signed_eth_ev_vote_extension.clone(), + validator_set_update: None, + }; let vote = ExtendedVoteInfo { vote_extension: vote_extension.try_to_vec().unwrap(), ..Default::default() @@ -772,14 +766,16 @@ mod test_prepare_proposal { } #[cfg(not(feature = "abcipp"))] { - let vote = ProtocolTxType::VoteExtension(vote_extension) - .sign(&protocol_key) - .to_bytes(); + let vote = ProtocolTxType::EthEventsVext( + signed_eth_ev_vote_extension.clone(), + ) + .sign(&protocol_key) + .to_bytes(); let mut rsp = shell.prepare_proposal(RequestPrepareProposal { txs: vec![vote], ..Default::default() }); - assert_eq!(rsp.txs.len(), 3); + assert_eq!(rsp.txs.len(), 1); let tx_bytes = rsp.txs.remove(0); let got = Tx::try_from(&tx_bytes[..]).unwrap(); @@ -793,18 +789,12 @@ mod test_prepare_proposal { _ => panic!("Test failed"), }; - let digest = match protocol_tx { - ProtocolTxType::EthereumEvents(digest) => digest, + let rsp_ext = match protocol_tx { + ProtocolTxType::EthEventsVext(ext) => ext, _ => panic!("Test failed"), }; - let expected = manually_assemble_digest( - &protocol_key, - signed_eth_ev_vote_extension, - LAST_HEIGHT, - ); - - assert_eq!(expected, digest); + assert_eq!(signed_eth_ev_vote_extension, rsp_ext); } } From b7860767e3866d65da8f441a2cabacd7cdb9dd98 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 15:36:59 +0000 Subject: [PATCH 1720/1995] WIP: Fix FinalizeBlock tests --- .../lib/node/ledger/shell/finalize_block.rs | 84 +++++++++++++------ 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 72ca9d88fb..5c4c73252e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -358,9 +358,9 @@ mod test_finalize_block { use namada::types::ethereum_events::EthAddress; use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee}; - use namada::types::vote_extensions::ethereum_events::{ - self, MultiSignedEthEvent, - }; + use namada::types::vote_extensions::ethereum_events; + #[cfg(feature = "abcipp")] + use namada::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use super::*; use crate::node::ledger::shell::test_utils::*; @@ -672,14 +672,16 @@ mod test_finalize_block { assert_eq!(counter, 2); } - /// Test that if a rejected protocol tx is applied and emits + /// Test if a rejected protocol tx is applied and emits /// the correct event #[test] fn test_rejected_protocol_tx() { - let (mut shell, _, _) = setup(); + const LAST_HEIGHT: BlockHeight = BlockHeight(3); + let (mut shell, _, _) = setup_at_height(LAST_HEIGHT); let protocol_key = shell.mode.get_protocol_key().expect("Test failed").clone(); + #[cfg(feature = "abcipp")] let tx = ProtocolTxType::EthereumEvents(ethereum_events::VextDigest { signatures: Default::default(), events: vec![], @@ -687,6 +689,21 @@ mod test_finalize_block { .sign(&protocol_key) .to_bytes(); + #[cfg(not(feature = "abcipp"))] + let tx = ProtocolTxType::EthEventsVext( + ethereum_events::Vext::empty( + LAST_HEIGHT, + shell + .mode + .get_validator_address() + .expect("Test failed") + .clone(), + ) + .sign(&protocol_key), + ) + .sign(&protocol_key) + .to_bytes(); + let req = FinalizeBlock { txs: vec![ProcessedTx { tx, @@ -729,35 +746,52 @@ mod test_finalize_block { assert_eq!(queued_event, event); // ---- The protocol tx that includes this event on-chain - let signature = ethereum_events::Vext { + let ext = ethereum_events::Vext { block_height: shell.storage.last_height, ethereum_events: vec![event.clone()], validator_addr: address.clone(), } - .sign(&protocol_key) - .sig; - let signed = MultiSignedEthEvent { - event, - #[cfg(feature = "abcipp")] - signers: BTreeSet::from([address.clone()]), - #[cfg(not(feature = "abcipp"))] - signers: BTreeSet::from([( - address.clone(), - shell.storage.last_height, - )]), - }; + .sign(&protocol_key); + + #[cfg(feature = "abcipp")] + let processed_tx = { + let signed = MultiSignedEthEvent { + event, + #[cfg(feature = "abcipp")] + signers: BTreeSet::from([address.clone()]), + #[cfg(not(feature = "abcipp"))] + signers: BTreeSet::from([( + address.clone(), + shell.storage.last_height, + )]), + }; - let digest = ethereum_events::VextDigest { - #[cfg(feature = "abcipp")] - signatures: vec![(address, signature)].into_iter().collect(), - #[cfg(not(feature = "abcipp"))] - signatures: vec![((address, shell.storage.last_height), signature)] + let digest = ethereum_events::VextDigest { + #[cfg(feature = "abcipp")] + signatures: vec![(address, ext.sig)].into_iter().collect(), + #[cfg(not(feature = "abcipp"))] + signatures: vec![( + (address, shell.storage.last_height), + ext.sig, + )] .into_iter() .collect(), - events: vec![signed], + events: vec![signed], + }; + ProcessedTx { + tx: ProtocolTxType::EthereumEvents(digest) + .sign(&protocol_key) + .to_bytes(), + result: TxResult { + code: ErrorCodes::Ok.into(), + info: "".into(), + }, + } }; + + #[cfg(not(feature = "abcipp"))] let processed_tx = ProcessedTx { - tx: ProtocolTxType::EthereumEvents(digest) + tx: ProtocolTxType::EthEventsVext(ext) .sign(&protocol_key) .to_bytes(), result: TxResult { From 7b7b8b4089cb6a153b8f920b68419de686e6634d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 15:45:09 +0000 Subject: [PATCH 1721/1995] Praise the almighty clippy --- apps/src/lib/node/ledger/shell/finalize_block.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 5c4c73252e..fffb41c6e9 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -746,6 +746,7 @@ mod test_finalize_block { assert_eq!(queued_event, event); // ---- The protocol tx that includes this event on-chain + #[allow(clippy::redundant_clone)] let ext = ethereum_events::Vext { block_height: shell.storage.last_height, ethereum_events: vec![event.clone()], From 06c9d173aee9f0a63a7713ce4ebecd31955ecfd3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 15:55:47 +0000 Subject: [PATCH 1722/1995] Fix test_error_in_processing_tx() unit test --- .../src/lib/node/ledger/shell/prepare_proposal.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b21d27f33f..36c047ebe0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -844,20 +844,7 @@ mod test_prepare_proposal { record::remove(wrapper) ); #[cfg(not(feature = "abcipp"))] - assert!({ - let mut assertion = true; - // this includes valset upd and eth events - // vote extension diggests - let transactions = shell.prepare_proposal(req).txs; - assert_eq!(transactions.len(), 2); - for tx in transactions { - if tx == wrapper { - assertion = false; - break; - } - } - assertion - }); + assert_eq!(shell.prepare_proposal(req).txs.len(), 0); } /// Test that the decrypted txs are included From d0504ddc0cc863ca19cc9be977a02ecfc69da2be Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 15:58:14 +0000 Subject: [PATCH 1723/1995] Fix test_prepare_proposal_rejects_non_wrapper_tx() unit test --- .../lib/node/ledger/shell/prepare_proposal.rs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 36c047ebe0..da4c1adf42 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -315,21 +315,7 @@ mod test_prepare_proposal { record::remove(non_wrapper_tx.to_bytes()) ); #[cfg(not(feature = "abcipp"))] - assert!({ - let mut assertion = true; - // this includes valset upd and eth events - // vote extension diggests - let transactions = shell.prepare_proposal(req).txs; - assert_eq!(transactions.len(), 2); - let non_wrapper_tx = non_wrapper_tx.to_bytes(); - for tx in transactions { - if tx == non_wrapper_tx { - assertion = false; - break; - } - } - assertion - }); + assert_eq!(shell.prepare_proposal(req).txs.len(), 0); } /// Check if we are filtering out an invalid vote extension `vext` From 7b7eb01d4400e2d4de03cf06a84939a003b489b8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 23 Nov 2022 16:32:37 +0000 Subject: [PATCH 1724/1995] WIP: Fixing ProcessProposal unit tests --- .../lib/node/ledger/shell/process_proposal.rs | 377 ++++++++++++------ 1 file changed, 248 insertions(+), 129 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 44b2527a3a..d79c901b36 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -892,24 +892,36 @@ mod test_process_proposal { Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), ) .to_bytes(); - #[allow(clippy::redundant_clone)] - let request = ProcessProposal { - txs: vec![ - tx.clone(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], - }; - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![tx, get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } + }; + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { txs: vec![tx] }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, @@ -1026,21 +1038,35 @@ mod test_process_proposal { ) .sign(&keypair) .expect("Test failed"); - let request = ProcessProposal { - txs: vec![ - wrapper.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( @@ -1076,22 +1102,35 @@ mod test_process_proposal { .sign(&keypair) .expect("Test failed"); - let request = ProcessProposal { - txs: vec![ - wrapper.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( @@ -1128,42 +1167,67 @@ mod test_process_proposal { shell.enqueue_tx(wrapper); txs.push(Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(tx)))); } - let req_1 = ProcessProposal { - txs: vec![ - txs[0].to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response_1 = { + let request = ProcessProposal { + txs: vec![txs[0].to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response_1 = if let [resp, _] = shell - .process_proposal(req_1) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response_1 = { + let request = ProcessProposal { + txs: vec![txs[0].to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response_1.result.code, u32::from(ErrorCodes::Ok)); - let req_2 = ProcessProposal { - txs: vec![ - txs[2].to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response_2 = { + let request = ProcessProposal { + txs: vec![txs[2].to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - - let response_2 = if let Err(TestError::RejectProposal(resp)) = - shell.process_proposal(req_2) - { - if let [resp, _] = resp.as_slice() { + #[cfg(not(feature = "abcipp"))] + let response_2 = { + let request = ProcessProposal { + txs: vec![txs[2].to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { resp.clone() } else { panic!("Test failed") } - } else { - panic!("Test failed") }; assert_eq!(response_2.result.code, u32::from(ErrorCodes::InvalidOrder)); assert_eq!( @@ -1202,22 +1266,35 @@ mod test_process_proposal { let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); - let request = ProcessProposal { - txs: vec![ - tx.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( @@ -1260,21 +1337,35 @@ mod test_process_proposal { wrapper.clone(), ))); - let request = ProcessProposal { - txs: vec![ - tx.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); } @@ -1307,21 +1398,35 @@ mod test_process_proposal { #[allow(clippy::redundant_clone)] wrapper.clone(), ))); - let request = ProcessProposal { - txs: vec![ - signed.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![signed.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![signed.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); } @@ -1370,21 +1475,35 @@ mod test_process_proposal { Some("transaction data".as_bytes().to_owned()), ); let tx = Tx::from(TxType::Raw(tx)); - let request = ProcessProposal { - txs: vec![ - tx.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response = if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - resp.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![tx.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidTx)); assert_eq!( From ec2be8fecd253b98c4d07d4f90d584a26623b8f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 09:20:11 +0000 Subject: [PATCH 1725/1995] Fix test_decrypted_txs_out_of_order() unit test --- .../lib/node/ledger/shell/process_proposal.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index d79c901b36..c655d70ff8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1204,12 +1204,11 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![txs[2].to_bytes(), get_empty_eth_ev_digest(&shell)], }; - if let [resp, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(mut resp)) = + shell.process_proposal(request) { - resp.clone() + assert_eq!(resp.len(), 2); + resp.remove(0) } else { panic!("Test failed") } @@ -1219,12 +1218,11 @@ mod test_process_proposal { let request = ProcessProposal { txs: vec![txs[2].to_bytes()], }; - if let [resp] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() + if let Err(TestError::RejectProposal(mut resp)) = + shell.process_proposal(request) { - resp.clone() + assert_eq!(resp.len(), 1); + resp.remove(0) } else { panic!("Test failed") } From ce0b76e15658626c92b6895c0d5ba9b144035940 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 09:44:56 +0000 Subject: [PATCH 1726/1995] Fix test_drop_vext_with_invalid_bheights() unit test --- apps/src/lib/node/ledger/shell/process_proposal.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c655d70ff8..e35cc40d14 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -749,7 +749,10 @@ mod test_process_proposal { #[test] fn test_drop_vext_with_invalid_bheights() { const LAST_HEIGHT: BlockHeight = BlockHeight(3); - const PRED_LAST_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + #[cfg(feature = "abcipp")] + const INVALID_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 - 1); + #[cfg(not(feature = "abcipp"))] + const INVALID_HEIGHT: BlockHeight = BlockHeight(LAST_HEIGHT.0 + 1); let (mut shell, _recv, _) = test_utils::setup(); shell.storage.last_height = LAST_HEIGHT; let (protocol_key, _, _) = wallet::defaults::validator_keys(); @@ -762,7 +765,7 @@ mod test_process_proposal { #[allow(clippy::redundant_clone)] let ext = ethereum_events::Vext { validator_addr: addr.clone(), - block_height: PRED_LAST_HEIGHT, + block_height: INVALID_HEIGHT, ethereum_events: vec![event.clone()], } .sign(&protocol_key); @@ -777,7 +780,7 @@ mod test_process_proposal { #[cfg(feature = "abcipp")] s.insert(addr.clone(), ext.sig); #[cfg(not(feature = "abcipp"))] - s.insert((addr.clone(), PRED_LAST_HEIGHT), ext.sig); + s.insert((addr.clone(), INVALID_HEIGHT), ext.sig); s }, events: vec![MultiSignedEthEvent { @@ -787,7 +790,7 @@ mod test_process_proposal { #[cfg(feature = "abcipp")] s.insert(addr); #[cfg(not(feature = "abcipp"))] - s.insert((addr, PRED_LAST_HEIGHT)); + s.insert((addr, INVALID_HEIGHT)); s }, }], From 9a6c4594d4ffc48a394ef45765e2ba761dda0cbb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 09:51:49 +0000 Subject: [PATCH 1727/1995] Fix test_wrapper_bad_signature_rejected() unit test --- .../lib/node/ledger/shell/process_proposal.rs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e35cc40d14..b37239f899 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -992,21 +992,35 @@ mod test_process_proposal { } else { panic!("Test failed"); }; - let request = ProcessProposal { - txs: vec![ - new_tx.to_bytes(), - #[cfg(feature = "abcipp")] - get_empty_eth_ev_digest(&shell), - ], + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![new_tx.to_bytes(), get_empty_eth_ev_digest(&shell)], + }; + if let [resp, _] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; - let response = if let [response, _] = shell - .process_proposal(request) - .expect("Test failed") - .as_slice() - { - response.clone() - } else { - panic!("Test failed") + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![new_tx.to_bytes()], + }; + if let [resp] = shell + .process_proposal(request) + .expect("Test failed") + .as_slice() + { + resp.clone() + } else { + panic!("Test failed") + } }; let expected_error = "Signature verification failed: Invalid signature"; assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); From 768886f7f8eef03dcca3539014871ac578dcf24f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 10:40:02 +0000 Subject: [PATCH 1728/1995] Fix module docstrings --- .../src/ledger/protocol/transactions/ethereum_events/mod.rs | 5 ++--- .../ledger/protocol/transactions/validator_set_update/mod.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 125dbeda30..820bc66494 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -1,6 +1,5 @@ -//! Code for handling -//! [`crate::types::transaction::protocol::ProtocolTxType::EthereumEvents`] -//! transactions. +//! Code for handling Ethereum events protocol txs. + mod eth_msgs; mod events; diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 22591cd834..7fbc82f896 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -1,4 +1,4 @@ -//! Code for handling [`ProtocolTxType::ValidatorSetUpdate`] protocol txs. +//! Code for handling validator set update protocol txs. use std::collections::{HashMap, HashSet}; From 5209d743eb6cf96dd851be47efa2eb49d36aaf62 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 13:47:34 +0000 Subject: [PATCH 1729/1995] Remove unnecessary clippy hint --- apps/src/lib/node/ledger/shell/process_proposal.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index b37239f899..d328ee36db 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -623,7 +623,6 @@ mod test_process_proposal { let tx = ProtocolTxType::EthereumEvents(vote_extension_digest) .sign(&protocol_key) .to_bytes(); - //#[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone(), tx], }; From def5f2c2a067e9af5792545e11c8d879ef36ab45 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 14:25:27 +0000 Subject: [PATCH 1730/1995] Update shared/src/types/vote_extensions/ethereum_events.rs Co-authored-by: James --- .../types/vote_extensions/ethereum_events.rs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/shared/src/types/vote_extensions/ethereum_events.rs index e05818abb9..ff6c19e14c 100644 --- a/shared/src/types/vote_extensions/ethereum_events.rs +++ b/shared/src/types/vote_extensions/ethereum_events.rs @@ -99,28 +99,20 @@ impl VextDigest { #[cfg(not(feature = "abcipp"))] pub fn singleton(ext: Signed) -> VextDigest { VextDigest { - signatures: { - let mut m = HashMap::new(); - m.insert( - (ext.data.validator_addr.clone(), ext.data.block_height), - ext.sig, - ); - m - }, + signatures: HashMap::from([( + (ext.data.validator_addr.clone(), ext.data.block_height), + ext.sig, + )]), events: ext .data .ethereum_events .into_iter() .map(|event| MultiSignedEthEvent { event, - signers: { - let mut s = BTreeSet::new(); - s.insert(( - ext.data.validator_addr.clone(), - ext.data.block_height, - )); - s - }, + signers: BTreeSet::from([( + ext.data.validator_addr.clone(), + ext.data.block_height, + )]), }) .collect(), } From 4ea08fd98c660ae71bbc3a1224bf35ca7fef1a79 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 14:27:45 +0000 Subject: [PATCH 1731/1995] Condense construction of signatures field in singleton valset upd digest --- .../types/vote_extensions/validator_set_update.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/shared/src/types/vote_extensions/validator_set_update.rs index 027a38b05b..697a7aabd2 100644 --- a/shared/src/types/vote_extensions/validator_set_update.rs +++ b/shared/src/types/vote_extensions/validator_set_update.rs @@ -51,14 +51,10 @@ impl VextDigest { #[cfg(not(feature = "abcipp"))] pub fn singleton(ext: SignedVext) -> VextDigest { VextDigest { - signatures: { - let mut m = HashMap::new(); - m.insert( - (ext.data.validator_addr.clone(), ext.data.block_height), - ext.sig, - ); - m - }, + signatures: HashMap::from([( + (ext.data.validator_addr.clone(), ext.data.block_height), + ext.sig, + )]), voting_powers: ext.data.voting_powers, } } From e71298980a7f0f675a816d124f8509812d3d51e2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 14:47:45 +0000 Subject: [PATCH 1732/1995] Remove unnecessary feature flags --- shared/src/ledger/protocol/mod.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index f975fa470b..a102505019 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -204,7 +204,6 @@ where }; match tx { - #[cfg(not(feature = "abcipp"))] ProtocolTxType::EthEventsVext(ext) => { let ethereum_events::VextDigest { events, .. } = ethereum_events::VextDigest::singleton(ext); @@ -213,24 +212,7 @@ where ) .map_err(Error::ProtocolTxError) } - #[cfg(not(feature = "abcipp"))] ProtocolTxType::ValSetUpdateVext(ext) => { - self::transactions::validator_set_update::aggregate_votes( - storage, - validator_set_update::VextDigest::singleton(ext), - ) - .map_err(Error::ProtocolTxError) - } - #[cfg(feature = "abcipp")] - ProtocolTxType::EthereumEvents(ext) => { - let ethereum_events::VextDigest { events, .. } = ext; - self::transactions::ethereum_events::apply_derived_tx( - storage, events, - ) - .map_err(Error::ProtocolTxError) - } - #[cfg(feature = "abcipp")] - ProtocolTxType::ValidatorSetUpdate(ext) => { // NOTE(feature = "abcipp"): we will not need to apply any // storage changes when we rollback to ABCI++; this is because // the decided vote extension digest should have >2/3 of the @@ -244,7 +226,8 @@ where // for this, we need to receive a mutable reference to the // event log, in `apply_protocol_tx()` self::transactions::validator_set_update::aggregate_votes( - storage, ext, + storage, + validator_set_update::VextDigest::singleton(ext), ) .map_err(Error::ProtocolTxError) } From 95607f8a4d1d17c7ebbdc2379459e916ee75c64e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 15:31:38 +0000 Subject: [PATCH 1733/1995] Fix typo in eth events queue --- apps/src/lib/node/ledger/shell/finalize_block.rs | 4 ++-- apps/src/lib/node/ledger/shell/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index fffb41c6e9..4c4e3efa3f 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -152,7 +152,7 @@ where #[cfg(not(feature = "abcipp"))] ProtocolTxType::EthEventsVext(ref ext) => { for event in ext.data.ethereum_events.iter() { - self.mode.deque_eth_event(event); + self.mode.dequeue_eth_event(event); } Event::new_tx_event(&tx_type, height.0) } @@ -165,7 +165,7 @@ where for event in digest.events.iter().map(|signed| &signed.event) { - self.mode.deque_eth_event(event); + self.mode.dequeue_eth_event(event); } Event::new_tx_event(&tx_type, height.0) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a15998e488..4d06bd440b 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -233,7 +233,7 @@ impl ShellMode { } /// Remove an Ethereum event from the internal queue - pub fn deque_eth_event(&mut self, event: &EthereumEvent) { + pub fn dequeue_eth_event(&mut self, event: &EthereumEvent) { if let ShellMode::Validator { ethereum_recv: EthereumReceiver { ref mut queue, .. }, .. From 335d94fe8c67a47922c400b71915c32ea3fd1d59 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 15:41:38 +0000 Subject: [PATCH 1734/1995] Refactor dequeue_eth_event() --- apps/src/lib/node/ledger/shell/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4d06bd440b..ac55664202 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -213,12 +213,12 @@ impl EthereumReceiver { self.queue.iter().cloned().collect() } - /// Given a list of events, remove them from the queue if present - /// Note that this method preserves the sorting and de-duplication + /// Remove the given [`EthereumEvent`] from the queue, if present. + /// + /// **INVARIANT:** This method preserves the sorting and de-duplication /// of events in the queue. - #[allow(dead_code)] - pub fn remove(&mut self, events: &[EthereumEvent]) { - self.queue.retain(|event| !events.contains(event)); + pub fn remove_event(&mut self, event: &EthereumEvent) { + self.queue.remove(event); } } @@ -234,12 +234,8 @@ impl ShellMode { /// Remove an Ethereum event from the internal queue pub fn dequeue_eth_event(&mut self, event: &EthereumEvent) { - if let ShellMode::Validator { - ethereum_recv: EthereumReceiver { ref mut queue, .. }, - .. - } = self - { - queue.remove(event); + if let ShellMode::Validator { ethereum_recv, .. } = self { + ethereum_recv.remove_event(event); } } From 03f0b561a0541f4072b6600214f2117ec1a58e3d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 15:43:39 +0000 Subject: [PATCH 1735/1995] Decrease the scope of the dead code warnings --- apps/src/lib/node/ledger/shell/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ac55664202..d97c25bfb6 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -222,7 +222,6 @@ impl EthereumReceiver { } } -#[allow(dead_code)] impl ShellMode { /// Get the validator address if ledger is in validator mode pub fn get_validator_address(&self) -> Option<&address::Address> { @@ -258,6 +257,7 @@ impl ShellMode { } /// Get the Ethereum bridge keypair for this validator. + #[cfg_attr(not(test), allow(dead_code))] pub fn get_eth_bridge_keypair(&self) -> Option<&common::SecretKey> { match self { ShellMode::Validator { @@ -277,6 +277,7 @@ impl ShellMode { /// If this node is a validator, broadcast a tx /// to the mempool using the broadcaster subprocess + #[cfg_attr(feature = "abcipp", allow(dead_code))] pub fn broadcast(&self, data: Vec) { if let Self::Validator { broadcast_sender, .. From a3437dc81e711ab54b997acc116626c4bcdd7f28 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 24 Nov 2022 15:48:16 +0000 Subject: [PATCH 1736/1995] Restrict Ethereum events dequeuing Only allow dequeuing Ethereum events from a nodes's internal storage of events if: 1) The given node is a validator. This is because only validators will vote for Ethereum events they have observed. 2) The validator who voted for the Ethereum event is the same as the one that is currently executing FinalizeBlock. --- .../lib/node/ledger/shell/finalize_block.rs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4c4e3efa3f..778aa56c74 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -151,8 +151,17 @@ where TxType::Protocol(protocol_tx) => match protocol_tx.tx { #[cfg(not(feature = "abcipp"))] ProtocolTxType::EthEventsVext(ref ext) => { - for event in ext.data.ethereum_events.iter() { - self.mode.dequeue_eth_event(event); + if self + .mode + .get_validator_address() + .map(|validator| { + validator == &ext.data.validator_addr + }) + .unwrap_or(false) + { + for event in ext.data.ethereum_events.iter() { + self.mode.dequeue_eth_event(event); + } } Event::new_tx_event(&tx_type, height.0) } @@ -162,10 +171,19 @@ where } #[cfg(feature = "abcipp")] ProtocolTxType::EthereumEvents(ref digest) => { - for event in - digest.events.iter().map(|signed| &signed.event) + if self + .mode + .get_validator_address() + .map(|validator| { + validator == &ext.data.validator_addr + }) + .unwrap_or(false) { - self.mode.dequeue_eth_event(event); + for event in + digest.events.iter().map(|signed| &signed.event) + { + self.mode.dequeue_eth_event(event); + } } Event::new_tx_event(&tx_type, height.0) } From 5d489eff9ca7139c4c5f63b82eecafc3ed332243 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 17:15:16 +0000 Subject: [PATCH 1737/1995] Fix up merge of v0.10.1 --- Cargo.lock | 882 +++++++++++++++++- apps/src/lib/client/eth_bridge_pool.rs | 3 +- apps/src/lib/client/tx.rs | 14 +- apps/src/lib/node/ledger/mod.rs | 3 +- .../lib/node/ledger/shell/finalize_block.rs | 3 +- apps/src/lib/node/ledger/shell/mod.rs | 44 +- .../lib/node/ledger/shell/process_proposal.rs | 102 +- apps/src/lib/wallet/mod.rs | 15 +- apps/src/lib/wallet/pre_genesis.rs | 10 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 2 + shared/src/ledger/eth_bridge/vp/mod.rs | 2 + shared/src/ledger/ibc/vp/packet.rs | 4 +- shared/src/ledger/protocol/mod.rs | 6 +- shared/src/ledger/queries/shell.rs | 13 +- shared/src/ledger/storage/mod.rs | 12 +- tests/src/e2e/ibc_tests.rs | 2 +- tests/src/e2e/ledger_tests.rs | 4 +- tests/src/vm_host_env/tx.rs | 1 + wasm/Cargo.lock | 246 ++++- wasm/wasm_source/src/tx_bridge_pool.rs | 6 + wasm_for_tests/wasm_source/Cargo.lock | 246 ++++- 21 files changed, 1411 insertions(+), 209 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d72635662f..3b3f0391dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes 1.2.1", + "futures-core", + "futures-sink", + "log 0.4.17", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util 0.7.4", +] + +[[package]] +name = "actix-http" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64 0.13.1", + "bitflags", + "bytes 1.2.1", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags 0.3.2", + "local-channel", + "mime 0.3.16", + "percent-encoding 2.2.0", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec 1.10.0", + "tracing 0.1.37", + "zstd", +] + +[[package]] +name = "actix-rt" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "http", + "log 0.4.17", + "openssl", + "pin-project-lite", + "tokio-openssl", + "tokio-util 0.7.4", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + [[package]] name = "addr2line" version = "0.17.0" @@ -130,7 +233,7 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "paste", "rustc_version 0.3.3", @@ -153,7 +256,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits 0.2.15", "quote", "syn", @@ -414,7 +517,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-rustls", - "tungstenite", + "tungstenite 0.12.0", "webpki-roots 0.21.1", ] @@ -450,6 +553,40 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "awc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80ca7ff88063086d2e2c70b9f3b29b2fcd999bac68ac21731e66781970d68519" +dependencies = [ + "actix-codec", + "actix-http", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "ahash", + "base64 0.13.1", + "bytes 1.2.1", + "cfg-if 1.0.0", + "derive_more", + "futures-core", + "futures-util", + "h2", + "http", + "itoa", + "log 0.4.17", + "mime 0.3.16", + "openssl", + "percent-encoding 2.2.0", + "pin-project-lite", + "rand 0.8.5", + "serde 1.0.147", + "serde_json", + "serde_urlencoded", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.66" @@ -514,7 +651,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -638,10 +775,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -820,7 +969,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -856,12 +1005,28 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byte-tools" version = "0.3.1" @@ -927,6 +1092,15 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "bytestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" +dependencies = [ + "bytes 1.2.1", +] + [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -1057,6 +1231,15 @@ dependencies = [ "generic-array 0.14.6", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check 0.9.4", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -1085,6 +1268,24 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clarity" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "880114aafee14fa3a183582a82407474d53f4950b1695658e95bbb5d049bb253" +dependencies = [ + "lazy_static", + "num-bigint 0.4.3", + "num-traits 0.2.15", + "num256", + "secp256k1 0.24.1", + "serde 1.0.147", + "serde-rlp", + "serde_bytes", + "serde_derive", + "sha3 0.10.6", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1212,6 +1413,12 @@ dependencies = [ "syn", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1621,8 +1828,10 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.0", "syn", ] @@ -1882,6 +2091,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e606f14042bb87cc02ef6a14db6c90ab92ed6f62d87e69377bc759fd7987cc" +dependencies = [ + "traitobject", + "typeable", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -1903,6 +2122,50 @@ dependencies = [ "serde_json", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde 1.0.147", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1978,7 +2241,7 @@ dependencies = [ "itertools", "measure_time", "miracl_core", - "num", + "num 0.4.0", "rand 0.7.3", "rand 0.8.5", "serde 1.0.147", @@ -2008,7 +2271,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] @@ -2048,6 +2311,18 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2113,7 +2388,7 @@ dependencies = [ "block-modes", "cipher", "libm", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", ] @@ -2152,6 +2427,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.1.31" @@ -2521,6 +2802,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2625,7 +2912,7 @@ checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" dependencies = [ "base64 0.9.3", "httparse", - "language-tags", + "language-tags 0.2.2", "log 0.3.9", "mime 0.2.6", "num_cpus", @@ -2852,8 +3139,8 @@ dependencies = [ "k256", "moka", "nanoid", - "num-bigint", - "num-rational", + "num-bigint 0.4.3", + "num-rational 0.4.1", "prost", "prost-types", "regex", @@ -2893,7 +3180,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2924,6 +3211,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde 1.0.147", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2971,6 +3296,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + [[package]] name = "iovec" version = "0.1.4" @@ -3019,24 +3350,13 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" -dependencies = [ - "log 0.4.17", - "serde 1.0.147", - "serde_json", -] - [[package]] name = "jubjub" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -3088,6 +3408,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -3259,6 +3585,24 @@ dependencies = [ "serde 1.0.147", ] +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.3.4" @@ -3348,7 +3692,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -3482,6 +3826,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "message-io" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" +dependencies = [ + "crossbeam-channel 0.5.6", + "crossbeam-utils 0.8.12", + "integer-encoding", + "lazy_static", + "log 0.4.17", + "mio 0.7.14", + "serde 1.0.147", + "strum", + "tungstenite 0.16.0", + "url 2.3.1", +] + [[package]] name = "mime" version = "0.2.6" @@ -3548,12 +3910,25 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow", + "miow 0.2.2", "net2", "slab", "winapi 0.2.8", ] +[[package]] +name = "mio" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +dependencies = [ + "libc", + "log 0.4.17", + "miow 0.3.7", + "ntapi", + "winapi 0.3.9", +] + [[package]] name = "mio" version = "0.8.5" @@ -3578,6 +3953,15 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "miracl_core" version = "2.3.0" @@ -3627,6 +4011,24 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "quick-error 1.2.3", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "namada" version = "0.10.1" @@ -3643,13 +4045,17 @@ dependencies = [ "borsh", "byte-unit", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", + "hex", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -3661,6 +4067,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_proof_of_stake", + "num-rational 0.4.1", "parity-wasm", "paste", "pretty_assertions", @@ -3685,7 +4092,9 @@ dependencies = [ "tendermint-rpc 0.23.6", "test-log", "thiserror", + "tiny-keccak", "tokio", + "toml", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", @@ -3705,6 +4114,7 @@ version = "0.10.1" dependencies = [ "ark-serialize", "ark-std", + "assert_matches", "async-std", "async-trait", "base64 0.13.1", @@ -3715,12 +4125,15 @@ dependencies = [ "borsh", "byte-unit", "byteorder", + "bytes 1.2.1", "clap", + "clarity", "color-eyre", "config", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", "eyre", "ferveo", "ferveo-common", @@ -3729,14 +4142,15 @@ dependencies = [ "futures 0.3.25", "git2", "itertools", - "jsonpath_lib", "libc", "libloading", "masp_primitives", "masp_proofs", + "message-io", "namada", "num-derive", "num-traits 0.2.15", + "num256", "num_cpus", "once_cell", "orion", @@ -3751,9 +4165,11 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "semver 1.0.14", "serde 1.0.147", "serde_bytes", "serde_json", + "serde_regex", "sha2 0.9.9", "signal-hook", "sparse-merkle-tree", @@ -3780,6 +4196,8 @@ dependencies = [ "tracing 0.1.37", "tracing-log", "tracing-subscriber 0.3.16", + "warp", + "web30", "websocket", "winapi 0.3.9", ] @@ -4012,17 +4430,42 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex 0.2.4", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits 0.2.15", +] + [[package]] name = "num" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.4.3", + "num-complex 0.4.2", "num-integer", "num-iter", - "num-rational", + "num-rational 0.4.1", + "num-traits 0.2.15", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.1.0", + "num-integer", "num-traits 0.2.15", ] @@ -4038,6 +4481,16 @@ dependencies = [ "serde 1.0.147", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.1.0", + "num-traits 0.2.15", +] + [[package]] name = "num-complex" version = "0.4.2" @@ -4079,6 +4532,18 @@ dependencies = [ "num-traits 0.2.15", ] +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.1.0", + "num-bigint 0.2.6", + "num-integer", + "num-traits 0.2.15", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -4086,7 +4551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", "serde 1.0.147", @@ -4110,6 +4575,20 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "num256" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9b5179e82f0867b23e0b9b822493821f9345561f271364f409c8e4a058367d" +dependencies = [ + "lazy_static", + "num 0.4.0", + "num-derive", + "num-traits 0.2.15", + "serde 1.0.147", + "serde_derive", +] + [[package]] name = "num_cpus" version = "1.14.0" @@ -4213,7 +4692,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -4279,6 +4758,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.147", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -4574,6 +5079,19 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -4583,6 +5101,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4652,7 +5181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.2.1", - "heck", + "heck 0.3.3", "itertools", "lazy_static", "log 0.4.17", @@ -4782,6 +5311,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.6.5" @@ -5235,6 +5770,16 @@ dependencies = [ "libc", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes 1.2.1", + "rustc-hex", +] + [[package]] name = "rocksdb" version = "0.19.0" @@ -5284,6 +5829,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.2.3" @@ -5302,6 +5853,15 @@ 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.14", +] + [[package]] name = "rustls" version = "0.19.1" @@ -5339,6 +5899,15 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "rustversion" version = "1.0.9" @@ -5444,6 +6013,12 @@ dependencies = [ "parking_lot 0.12.1", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -5514,6 +6089,15 @@ dependencies = [ "serde 1.0.147", ] +[[package]] +name = "secp256k1" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" +dependencies = [ + "secp256k1-sys 0.6.1", +] + [[package]] name = "secp256k1-sys" version = "0.4.2" @@ -5532,6 +6116,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.3.1" @@ -5624,6 +6217,18 @@ dependencies = [ "serde 0.8.23", ] +[[package]] +name = "serde-rlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69472f967577700225f282233c0625f7b73c371c3953b72d6dcfb91bd0133ca9" +dependencies = [ + "byteorder", + "error", + "num 0.2.1", + "serde 1.0.147", +] + [[package]] name = "serde_bytes" version = "0.11.7" @@ -5660,12 +6265,21 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ - "indexmap", "itoa", "ryu", "serde 1.0.147", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde 1.0.147", +] + [[package]] name = "serde_repr" version = "0.1.9" @@ -5726,6 +6340,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.5", +] + [[package]] name = "sha1" version = "0.10.5" @@ -5773,6 +6398,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -5924,6 +6559,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subproductdomain" version = "0.1.0" @@ -6549,6 +7206,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + [[package]] name = "tokio-reactor" version = "0.1.12" @@ -6638,6 +7307,18 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log 0.4.17", + "tokio", + "tungstenite 0.17.3", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -6981,6 +7662,53 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.9.8", + "thiserror", + "url 2.3.1", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64 0.13.1", + "byteorder", + "bytes 1.2.1", + "http", + "httparse", + "log 0.4.17", + "rand 0.8.5", + "sha-1 0.10.0", + "thiserror", + "url 2.3.1", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typeable" version = "0.1.2" @@ -7262,6 +7990,37 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +dependencies = [ + "bytes 1.2.1", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.23", + "log 0.4.17", + "mime 0.3.16", + "mime_guess", + "multipart", + "percent-encoding 2.2.0", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde 1.0.147", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.7.4", + "tower-service", + "tracing 0.1.37", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -7622,6 +8381,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web30" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426f817a02df256fec6bff3ec5ef3859204658774af9cd5ef2525ca8d50f6f2c" +dependencies = [ + "awc", + "clarity", + "futures 0.3.25", + "lazy_static", + "log 0.4.17", + "num 0.4.0", + "num256", + "serde 1.0.147", + "serde_derive", + "serde_json", + "tokio", +] + [[package]] name = "webpki" version = "0.21.4" @@ -7935,6 +8713,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "0.2.3" @@ -7992,7 +8779,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -8056,6 +8843,25 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + [[package]] name = "zstd-sys" version = "2.0.1+zstd.1.5.2" diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index 2a97540fe5..b697995eb4 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -5,6 +5,7 @@ use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; +use super::signing::TxSigningKey; use super::tx::process_tx; use crate::cli::{args, Context}; use crate::facade::tendermint_rpc::HttpClient; @@ -43,7 +44,7 @@ pub async fn add_to_eth_bridge_pool( let data = transfer.try_to_vec().unwrap(); let transfer_tx = Tx::new(tx_code, Some(data)); // this should not initialize any new addresses, so we ignore the result. - process_tx(ctx, tx, transfer_tx, None).await; + process_tx(ctx, tx, transfer_tx, TxSigningKey::None).await; } /// Construct a proof that a set of transfers are in the bridge pool. diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 7c29763f65..82b21c2b0f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,7 +1,6 @@ use std::borrow::Cow; use std::collections::hash_map::Entry; use std::collections::{BTreeMap, HashMap, HashSet}; -use std::convert::TryFrom; use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; @@ -47,11 +46,9 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; use namada::types::key::{self, *}; -use namada::types::storage::{Epoch, Key}; -use namada::types::key::*; use namada::types::masp::{PaymentAddress, TransferTarget}; use namada::types::storage::{ - BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, + self, BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; use namada::types::token::{ @@ -63,9 +60,9 @@ use namada::types::transaction::governance::{ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, token}; use namada::{ledger, vm}; -use tokio::time::{Duration, Instant}; use rand_core::{CryptoRng, OsRng, RngCore}; use sha2::Digest; +use tokio::time::Instant; use super::rpc; use super::types::ShieldedTransferContext; @@ -74,9 +71,6 @@ use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::{query_conversion, query_storage_value}; use crate::client::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::tendermint_websocket_client::{ - Error as WsError, TendermintWebsocketClient, WebSocketAddress, -}; use crate::client::types::ParsedTxTransferArgs; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -261,7 +255,7 @@ pub async fn submit_init_validator( let eth_cold_key = ctx .get_opt_cached(ð_cold_key) - .map(|key| match *key { + .map(|key| match key { common::SecretKey::Secp256k1(_) => key, common::SecretKey::Ed25519(_) => { eprintln!("Eth cold key can only be secp256k1"); @@ -282,7 +276,7 @@ pub async fn submit_init_validator( let eth_hot_key = ctx .get_opt_cached(ð_hot_key) - .map(|key| match *key { + .map(|key| match key { common::SecretKey::Secp256k1(_) => key, common::SecretKey::Ed25519(_) => { eprintln!("Eth hot key can only be secp256k1"); diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 8401a51f88..f047d748bd 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,8 +1,7 @@ mod abortable; mod broadcaster; -pub mod events; -pub mod rpc; mod ethereum_node; +pub mod rpc; mod shell; mod shims; pub mod storage; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e3b539d81e..217dd8bbe6 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,9 +1,8 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use namada::ledger::protocol; -use namada::types::storage::{BlockHash, Header}; -use namada::types::transaction::protocol::ProtocolTxType; use namada::types::storage::{BlockHash, BlockResults, Header}; +use namada::types::transaction::protocol::ProtocolTxType; use super::governance::execute_governance_proposals; use super::*; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 7ba832a08a..e3363fc9d8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -40,8 +40,6 @@ use namada::types::chain::ChainId; use namada::types::ethereum_events::EthereumEvent; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; -use namada::types::time::{DateTimeUtc, TimeZone, Utc}; -use namada::types::storage::{BlockHeight, Key}; use namada::types::transaction::{ hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, @@ -770,45 +768,6 @@ where response } - #[allow(dead_code)] - /// Simulate validation and application of a transaction. - fn dry_run_tx(&self, tx_bytes: &[u8]) -> response::Query { - let mut response = response::Query::default(); - let mut gas_meter = BlockGasMeter::default(); - let mut write_log = WriteLog::default(); - let mut vp_wasm_cache = self.vp_wasm_cache.read_only(); - let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); - match Tx::try_from(tx_bytes) { - Ok(tx) => { - let tx = TxType::Decrypted(DecryptedTx::Decrypted(tx)); - match protocol::apply_tx( - tx, - tx_bytes.len(), - TxIndex::default(), - &mut gas_meter, - &mut write_log, - &self.storage, - &mut vp_wasm_cache, - &mut tx_wasm_cache, - ) - .map_err(Error::TxApply) - { - Ok(result) => response.info = result.to_string(), - Err(error) => { - response.code = 1; - response.log = format!("{}", error); - } - } - response - } - Err(err) => { - response.code = 1; - response.log = format!("{}", Error::TxDecoding(err)); - response - } - } - } - /// Lookup a validator's keypair for their established account from their /// wallet. If the node is not validator, this function returns None #[allow(dead_code)] @@ -878,9 +837,8 @@ mod test_utils { use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; - use namada::types::storage::{BlockHash, Epoch, Header}; - use namada::types::time::DateTimeUtc; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; + use namada::types::time::DateTimeUtc; use namada::types::transaction::Fee; use tempfile::tempdir; use tokio::sync::mpsc::{Sender, UnboundedReceiver}; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 319065ff58..935f609e4a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -432,64 +432,62 @@ where code: ErrorCodes::InvalidTx.into(), info: "Unsupported protocol transaction type".into(), }, - TxType::Decrypted(tx) => match tx_queue_iter.next() { - Some(wrapper) => { - if wrapper.tx_hash != tx.hash_commitment() { - TxResult { - code: ErrorCodes::InvalidOrder.into(), - info: "Process proposal rejected a decrypted \ - transaction that violated the tx order \ - determined in the previous block" - .into(), - } - } else if verify_decrypted_correctly(&tx, privkey) { - TxResult { - code: ErrorCodes::Ok.into(), - info: "Process Proposal accepted this \ - transaction" - .into(), - } - } else { - TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The encrypted payload of tx was \ - incorrectly marked as un-decryptable" - .into(), - } + }, + TxType::Decrypted(tx) => match tx_queue_iter.next() { + Some(wrapper) => { + if wrapper.tx_hash != tx.hash_commitment() { + TxResult { + code: ErrorCodes::InvalidOrder.into(), + info: "Process proposal rejected a decrypted \ + transaction that violated the tx order \ + determined in the previous block" + .into(), } - } - None => TxResult { - code: ErrorCodes::ExtraTxs.into(), - info: "Received more decrypted txs than expected" - .into(), - }, - }, - TxType::Wrapper(tx) => { - // validate the ciphertext via Ferveo - if !tx.validate_ciphertext() { + } else if verify_decrypted_correctly(&tx, privkey) { + TxResult { + code: ErrorCodes::Ok.into(), + info: "Process Proposal accepted this transaction" + .into(), + } + } else { TxResult { code: ErrorCodes::InvalidTx.into(), - info: format!( - "The ciphertext of the wrapped tx {} is \ - invalid", - hash_tx(tx_bytes) - ), + info: "The encrypted payload of tx was \ + incorrectly marked as un-decryptable" + .into(), } + } + } + None => TxResult { + code: ErrorCodes::ExtraTxs.into(), + info: "Received more decrypted txs than expected".into(), + }, + }, + TxType::Wrapper(tx) => { + // validate the ciphertext via Ferveo + if !tx.validate_ciphertext() { + TxResult { + code: ErrorCodes::InvalidTx.into(), + info: format!( + "The ciphertext of the wrapped tx {} is invalid", + hash_tx(tx_bytes) + ), + } + } else { + // If the public key corresponds to the MASP sentinel + // transaction key, then the fee payer is effectively + // the MASP, otherwise derive + // they payer from public key. + let fee_payer = if tx.pk != masp_tx_key().ref_to() { + tx.fee_payer() } else { - // If the public key corresponds to the MASP sentinel - // transaction key, then the fee payer is effectively - // the MASP, otherwise derive - // they payer from public key. - let fee_payer = if tx.pk != masp_tx_key().ref_to() { - tx.fee_payer() - } else { - masp() - }; - // check that the fee payer has sufficient balance - let balance = - self.get_balance(&tx.fee.token, &fee_payer); + masp() + }; + // check that the fee payer has sufficient balance + let balance = + self.storage.get_balance(&tx.fee.token, &fee_payer); - if wrapper.fee.amount <= balance { + if tx.fee.amount <= balance { TxResult { code: ErrorCodes::Ok.into(), info: "Process proposal accepted this transaction" diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 249a07bf14..37d8cc3a63 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -168,16 +168,15 @@ impl Wallet { protocol_pk: Option, protocol_key_scheme: SchemeType, ) -> Result { - let protocol_keypair = self.find_secret_key(protocol_pk, |data| { - Rc::new(data.keys.protocol_keypair) - })?; + let protocol_keypair = self + .find_secret_key(protocol_pk, |data| data.keys.protocol_keypair)?; let eth_bridge_keypair = self .find_secret_key(eth_bridge_pk, |data| { - Rc::new(data.keys.eth_bridge_keypair) + data.keys.eth_bridge_keypair })?; Ok(Store::gen_validator_keys( - eth_bridge_keypair.map(|sk| sk.as_ref().clone()), - protocol_keypair.map(|sk| sk.as_ref().clone()), + eth_bridge_keypair, + protocol_keypair, protocol_key_scheme, )) } @@ -191,9 +190,9 @@ impl Wallet { &mut self, maybe_pk: Option, extract_key: F, - ) -> Result>, FindKeyError> + ) -> Result, FindKeyError> where - F: Fn(ValidatorData) -> Rc, + F: Fn(ValidatorData) -> common::SecretKey, { maybe_pk .map(|pk| { diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 07f828c738..2b665d7546 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -40,9 +40,9 @@ pub struct ValidatorWallet { /// Cryptographic keypair for consensus key pub consensus_key: common::SecretKey, /// Cryptographic keypair for eth cold key - pub eth_cold_key: Rc, + pub eth_cold_key: common::SecretKey, /// Cryptographic keypair for eth hot key - pub eth_hot_key: Rc, + pub eth_hot_key: common::SecretKey, /// Cryptographic keypair for rewards key pub rewards_key: common::SecretKey, /// Cryptographic keypair for Tendermint node key @@ -58,7 +58,7 @@ pub struct ValidatorStore { /// Cryptographic keypair for consensus key pub consensus_key: wallet::StoredKeypair, /// Cryptographic keypair for eth cold key - pub eth_cold_key: wallet::StoredKeypair, + pub eth_cold_key: wallet::StoredKeypair, /// Cryptographic keypair for rewards key pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key @@ -127,7 +127,7 @@ impl ValidatorWallet { let eth_cold_key = store.eth_cold_key.get(true, password.clone())?; let eth_hot_key = - Rc::new(store.validator_keys.eth_bridge_keypair.clone()); + store.validator_keys.eth_bridge_keypair.clone(); let rewards_key = store.rewards_key.get(true, password.clone())?; @@ -172,7 +172,7 @@ impl ValidatorWallet { ); let validator_keys = store::Store::gen_validator_keys(None, None, scheme); - let eth_hot_key = Rc::new(validator_keys.eth_bridge_keypair.clone()); + let eth_hot_key = validator_keys.eth_bridge_keypair.clone(); let store = ValidatorStore { account_key, consensus_key, diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index 1e6e198e21..a9be95f49a 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -411,6 +411,7 @@ mod test_bridge_pool_vp { use crate::types::ethereum_events::EthAddress; use crate::types::hash::Hash; use crate::types::key::{common, ed25519, SecretKey, SigScheme}; + use crate::types::storage::TxIndex; use crate::vm::wasm::VpCache; use crate::vm::WasmCacheRwAccess; @@ -591,6 +592,7 @@ mod test_bridge_pool_vp { storage, write_log, tx, + &TxIndex(0), VpGasMeter::new(0u64), keys_changed, verifiers, diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index e613fb8d8d..a21945ebad 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -427,6 +427,7 @@ mod tests { use crate::types::chain::ChainId; use crate::types::ethereum_events; use crate::types::ethereum_events::EthAddress; + use crate::types::storage::TxIndex; use crate::vm::wasm::VpCache; use crate::vm::WasmCacheRwAccess; @@ -501,6 +502,7 @@ mod tests { storage, write_log, tx, + &TxIndex(0), VpGasMeter::new(0u64), keys_changed, verifiers, diff --git a/shared/src/ledger/ibc/vp/packet.rs b/shared/src/ledger/ibc/vp/packet.rs index 9b25503d2f..42aac43d1c 100644 --- a/shared/src/ledger/ibc/vp/packet.rs +++ b/shared/src/ledger/ibc/vp/packet.rs @@ -33,11 +33,9 @@ use crate::ibc::core::ics24_host::identifier::{ }; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; +use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{self}; -use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self, StorageHasher}; use crate::types::ibc::data::{ Error as IbcDataError, FungibleTokenPacketData, IbcMessage, }; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 3888be1b85..c10079ef2c 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -21,8 +21,8 @@ use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::proto::{self, Tx}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage; -use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use crate::types::storage::TxIndex; +use crate::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; use crate::types::transaction::{DecryptedTx, TxResult, TxType, VpsResult}; use crate::vm::wasm::{TxCache, VpCache}; use crate::vm::{self, wasm, WasmCacheAccess}; @@ -157,7 +157,7 @@ where .map_err(Error::GasError)?; let verifiers = execute_tx( &tx, - &tx_index, + tx_index, storage, block_gas_meter, write_log, @@ -167,7 +167,7 @@ where let vps_result = check_vps( &tx, - &tx_index, + tx_index, storage, block_gas_meter, write_log, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7daf92f5e8..5523eac0de 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -1,6 +1,4 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use tendermint_proto::crypto::{ProofOp, ProofOps}; -use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; @@ -16,6 +14,7 @@ use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, MerkleTree, StoreRef, StoreType, DB}; use crate::ledger::storage_api::{self, CustomError, ResultExt, StorageRead}; +use crate::types::address::Address; use crate::types::eth_abi::EncodeCell; use crate::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, @@ -23,13 +22,11 @@ use crate::types::eth_bridge_pool::{ use crate::types::hash::Hash; use crate::types::keccak::KeccakHash; use crate::types::storage::MembershipProof::BridgePool; -use crate::types::storage::{self, Epoch, MerkleValue, PrefixValue}; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead}; -use crate::types::address::Address; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::storage::TxIndex; -use crate::types::storage::{self, BlockResults, Epoch, PrefixValue}; +use crate::types::storage::{ + self, BlockResults, Epoch, MerkleValue, PrefixValue, +}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::transaction::TxResult; @@ -142,7 +139,7 @@ where let data = protocol::apply_wasm_tx( tx, request.data.len(), - TxIndex(0), + &TxIndex(0), ShellParams { block_gas_meter: &mut gas_meter, write_log: &mut write_log, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index f1f586e87d..2a3ab2200d 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -10,10 +10,10 @@ pub mod write_log; use core::fmt::Debug; use std::array; -use std::collections::BTreeSet; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use borsh::{BorshDeserialize, BorshSerialize}; +use ferveo_common::TendermintValidator; use masp_primitives::asset_type::AssetType; use masp_primitives::convert::AllowedConversion; use masp_primitives::merkle_tree::FrozenCommitmentTree; @@ -24,8 +24,6 @@ use rayon::iter::{ }; #[cfg(feature = "wasm-runtime")] use rayon::prelude::ParallelSlice; -use borsh::{BorshDeserialize, BorshSerialize}; -use ferveo_common::TendermintValidator; use thiserror::Error; use super::parameters::Parameters; @@ -49,13 +47,11 @@ use crate::ledger::storage_api::queries::{self, QueriesExt, SendValsetUpd}; use crate::tendermint::merkle::proof::Proof; use crate::tendermint_proto::google::protobuf; use crate::tendermint_proto::types::EvidenceParams; -use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::address::{ masp, Address, EstablishedAddressGen, InternalAddress, }; use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; use crate::types::ethereum_events::EthAddress; -use crate::types::key; use crate::types::key::dkg_session_keys::DkgPublicKey; #[cfg(feature = "ferveo-tpke")] use crate::types::storage::TxQueue; @@ -64,10 +60,10 @@ use crate::types::storage::{ MembershipProof, MerkleValue, TxIndex, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; -use crate::types::token; -use crate::types::token::{self, Amount}; +use crate::types::token::Amount; use crate::types::transaction::EllipticCurve; use crate::types::vote_extensions::validator_set_update::EthAddrBook; +use crate::types::{key, token}; /// A result of a function that may fail pub type Result = std::result::Result; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 39f9c9f8af..1001d64952 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -67,7 +67,7 @@ use ibc_relayer::light_client::{LightClient, Verified}; use namada::ledger::ibc::handler::{commitment_prefix, port_channel_id}; use namada::ledger::ibc::storage::*; use namada::ledger::storage::ics23_specs::ibc_proof_specs; -use namada::ledger::storage::Sha256Hasher; +use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address::{Address, InternalAddress}; use namada::types::key::PublicKey; use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b34d895726..df34f9cedc 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,8 +20,8 @@ use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::{btc, eth, masp_rewards}; use namada::types::token; -use namada_apps::config::ethereum_bridge; use namada_apps::client::tx::ShieldedContext; +use namada_apps::config::ethereum_bridge; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; @@ -29,8 +29,6 @@ use serde_json::json; use setup::constants::*; use super::helpers::{get_height, is_debug_mode, wait_for_block_height}; -use super::setup::get_all_wasms_hashes; -use super::helpers::{get_height, wait_for_block_height}; use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_voting_power, get_actor_rpc, get_epoch, diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index e15f32ae32..733c354783 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -191,6 +191,7 @@ impl TestTxEnv { &self.storage, &mut self.write_log, &mut self.gas_meter, + &TxIndex::default(), &self.tx.code, self.tx.data.as_ref().unwrap_or(&empty_data), &mut self.vp_wasm_cache, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 3967368aaa..ae17eeb34d 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -287,7 +287,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -374,10 +374,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -502,7 +514,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -533,6 +545,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -667,6 +685,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1325,6 +1352,50 @@ dependencies = [ "version_check", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -1369,11 +1440,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1425,6 +1508,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -1992,7 +2081,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2012,6 +2101,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2086,7 +2213,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -2254,7 +2381,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2436,11 +2563,15 @@ dependencies = [ "bls12_381", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", @@ -2450,6 +2581,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_proof_of_stake", + "num-rational", "parity-wasm", "paste", "proptest", @@ -2468,6 +2600,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", @@ -2709,7 +2842,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -2736,6 +2869,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -2929,6 +3088,19 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2938,6 +3110,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3128,6 +3311,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3414,6 +3603,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3437,6 +3636,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3801,6 +4006,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -5229,6 +5444,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5268,7 +5492,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 9e3e1feab2..812e95b25f 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -19,6 +19,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { &address::nam(), None, amount, + &None, + &None, )?; let TransferToEthereum { ref asset, @@ -35,6 +37,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { &address::nam(), None, amount, + &None, + &None, )?; } else { // Otherwise we escrow ERC20 tokens. @@ -46,6 +50,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { ð_bridge::ADDRESS, Some(sub_prefix), amount, + &None, + &None, )?; } // add transfer into the pool diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index a43b493805..02279f7d57 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -287,7 +287,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -374,10 +374,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -502,7 +514,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -533,6 +545,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -667,6 +685,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1325,6 +1352,50 @@ dependencies = [ "version_check", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -1369,11 +1440,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1425,6 +1508,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -1992,7 +2081,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2012,6 +2101,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2086,7 +2213,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -2254,7 +2381,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2436,11 +2563,15 @@ dependencies = [ "bls12_381", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", @@ -2450,6 +2581,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_proof_of_stake", + "num-rational", "parity-wasm", "paste", "proptest", @@ -2468,6 +2600,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "wasmer", @@ -2701,7 +2834,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -2728,6 +2861,32 @@ dependencies = [ "group", ] +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "parity-wasm" version = "0.42.2" @@ -2921,6 +3080,19 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2930,6 +3102,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3120,6 +3303,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3406,6 +3595,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3429,6 +3628,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3793,6 +3998,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -5199,6 +5414,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5238,7 +5462,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", From 88d657c632f13dc84b7fead9e6740e44cf7e22c6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 24 Nov 2022 17:57:14 +0000 Subject: [PATCH 1738/1995] wasm checksums update --- wasm/checksums.json | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 6df1d206a1..24e4c0b43e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,17 @@ { - "tx_bond.wasm": "tx_bond.4e64b7884c8df422fc00db221fbe0eb48bfb494c2baf385c5552e041cacdea64.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.233f790468cc70489111201f6f066280d7bc579b2884d94448e357bf243d0a14.wasm", - "tx_ibc.wasm": "tx_ibc.e4dccee1e462e84f2b2a5c2cdbc06dc1570679363d7480e37c86e505583ca6fa.wasm", - "tx_init_account.wasm": "tx_init_account.db84fb031ffac56b59dc0383f0ad3e528e5d7635292de02e7cda4c4545b2cbe5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3227005afc52c2f002fee13de88ea6b4e1b26ab5e53d66453bda45230f670565.wasm", - "tx_init_validator.wasm": "tx_init_validator.419ce3750a8a70087cb5b4010418570f73534e68ebb918a16d93b346c5189de5.wasm", - "tx_transfer.wasm": "tx_transfer.a0fe87d8e4b48ed679d8e16a8800085cb3d7ac9556fb5fe4b37cff4cd66fee81.wasm", - "tx_unbond.wasm": "tx_unbond.4480c046edf32bf97d2d40d45b2bd05547049cae22df48f3fc433f151b495e2b.wasm", - "tx_update_vp.wasm": "tx_update_vp.b057de2c9f17a634aaf011f6bf6bb0bac198ae522c5a24a44dec71d9b649c7e7.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.4625c309121620a10b9b87b4f4622b8a899f758c08d5ae0341a6f80ce932e089.wasm", - "tx_withdraw.wasm": "tx_withdraw.98503dc2b6d931e9303567567de4d395ae7689f0ef852810e75a904953684d08.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e417000da4f7640a5fb5f124755ed02b8654aa67db600bea6e93e1f04e6d71a3.wasm", - "vp_token.wasm": "vp_token.37749e16d9fcd17dc2cf3351b0a140b5821668f80d6cc805caeb3e8e9af06cd9.wasm", - "vp_user.wasm": "vp_user.8c117faa94e44833fb7a16d13067f7626ecfca6a70fec1b7822c7d9a6c3e909e.wasm" + "tx_bond.wasm": "tx_bond.4fc2b1c226d57d94e4043109d1af164ac69f8eb72e12525d97a123d8100817af.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.f41d51ed01d5c60909c8ff9a7ed31f19b2d3ef42c24d1daa4143d59e0e797d97.wasm", + "tx_ibc.wasm": "tx_ibc.582a0a6433782caaee708205fc22f40d2af34fcd6994ac8f5fb8bef627778b4d.wasm", + "tx_init_account.wasm": "tx_init_account.0c204ba845658516b20670346c0682595d16d1ca99f42eddde51d1cde4295ffb.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0a9cbdd68510d723818bb5d95cb0770b4f054ca402f8bbcff18108ea413875de.wasm", + "tx_init_validator.wasm": "tx_init_validator.09d0e561565cb083cb243b4594882c4f424eda73a91f89cce1e814b35ba2eae5.wasm", + "tx_transfer.wasm": "tx_transfer.9f4ad0aed0f1fcf21398c4d12da4ae26937415b01524811c0dceb810f1d1bf4a.wasm", + "tx_unbond.wasm": "tx_unbond.72018d106cca5a856bca041ea30eee579ec61d29f246f1387f3da4ef72c6cfbd.wasm", + "tx_update_vp.wasm": "tx_update_vp.4d585d52ed7f3b1507ed092df82ff7edec362305eb9e84f4dae06f09d75cc727.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.49638241211faf327390a0f07920d8dd40c9d7676d9c1beccd3bad0ff5f6f1a9.wasm", + "tx_withdraw.wasm": "tx_withdraw.1f8faa002868664e42d6e4b7140d5f295a2a0f1e99968656ee4d91c496b8f08d.wasm", + "vp_masp.wasm": "vp_masp.ad12a384f8690ad1a7c084b0a2ce7e72b9743fac7b2229f9a0e290fd0e75619a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e7d6cc83ad23f7db10484a051e6ba25105900b154e179768ccc79b955d6f47ca.wasm", + "vp_token.wasm": "vp_token.fbec5c10439c0f2d421799188f9ab677967b931535262dbf4cc7591ea0978aa7.wasm", + "vp_user.wasm": "vp_user.4db5100a828f04ed1fe6d2fee09b56facd8dde1e3bc0ea8d288875bf934d581a.wasm" } \ No newline at end of file From b94d1c688a8b068d4ec83ae8753025a5e48945c3 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 25 Nov 2022 11:34:22 +0000 Subject: [PATCH 1739/1995] Disable Ethereum bridge for ibc e2e tests, + update namadac arguments --- tests/src/e2e/ibc_tests.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 1001d64952..1566103f65 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -74,6 +74,7 @@ use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; use namada::types::token::Amount; use namada_apps::client::rpc::query_storage_value_bytes; use namada_apps::client::utils::id_from_pk; +use namada_apps::config::ethereum_bridge; use setup::constants::*; use tendermint::block::Header as TmHeader; use tendermint::merkle::proof::Proof as TmProof; @@ -83,6 +84,7 @@ use tendermint_proto::Protobuf; use tendermint_rpc::{Client, HttpClient, Url}; use tokio::runtime::Runtime; +use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::{find_address, get_actor_rpc, get_validator_pk}; use crate::e2e::setup::{self, sleep, AnomaCmd, Bin, Test, Who}; use crate::{run, run_as}; @@ -90,6 +92,18 @@ use crate::{run, run_as}; #[test] fn run_ledger_ibc() -> Result<()> { let (test_a, test_b) = setup::two_single_node_nets()?; + set_ethereum_bridge_mode( + &test_a, + &test_a.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); + set_ethereum_bridge_mode( + &test_b, + &test_b.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // Run Chain A let mut ledger_a = @@ -768,11 +782,11 @@ fn transfer_received_token( &sub_prefix, "--amount", "50000", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &rpc, @@ -1059,11 +1073,11 @@ fn submit_ibc_tx( &data_path, "--signer", signer, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &rpc From 2ea8d18517c76c80cda242eb32dc06661b86e2bb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 25 Nov 2022 11:40:53 +0000 Subject: [PATCH 1740/1995] Disable Ethereum bridge for masp e2e tests --- tests/src/e2e/ledger_tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index df34f9cedc..9e585ee95f 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -522,6 +522,12 @@ fn masp_txs_and_queries() -> Result<()> { }, None, )?; + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -788,6 +794,12 @@ fn masp_pinned_txs() -> Result<()> { }, None, )?; + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = @@ -949,6 +961,12 @@ fn masp_incentives() -> Result<()> { }, None, )?; + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::Off, + ); // 1. Run the ledger node let mut ledger = From be0e369c1f82e308f457bf9ed6413a39df4440fd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 25 Nov 2022 12:10:40 +0000 Subject: [PATCH 1741/1995] Wait for RPC servers to start in masp e2e tests --- tests/src/e2e/ledger_tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9e585ee95f..4e187361c2 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -534,6 +534,7 @@ fn masp_txs_and_queries() -> Result<()> { run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server")?; let _bg_ledger = ledger.background(); @@ -806,6 +807,7 @@ fn masp_pinned_txs() -> Result<()> { run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server")?; let _bg_ledger = ledger.background(); @@ -973,6 +975,7 @@ fn masp_incentives() -> Result<()> { run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Starting RPC HTTP server")?; let _bg_ledger = ledger.background(); From b718a494c5a08617c7c0a2c07ddebc7087113700 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 13:24:17 +0000 Subject: [PATCH 1742/1995] Random fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 11 +- .../prepare_proposal/block_space_alloc.rs | 181 ------------------ 2 files changed, 5 insertions(+), 187 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 2dcb698fd1..4c8a248956 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -22,7 +22,6 @@ use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, EncryptedTxBatchAllocator, NextState, NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, RemainingBatchAllocator, TryAlloc, - TryAllocBatch, }; use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; use super::super::*; @@ -129,7 +128,7 @@ where &mut self, mut alloc: BlockSpaceAllocator, local_last_commit: Option, - ) -> Vec { + ) -> (Vec, EncryptedTxBatchAllocator) { // genesis should not contain vote extensions if self.storage.last_height == BlockHeight(0) { return (vec![], self.get_encrypted_txs_allocator(alloc)); @@ -193,7 +192,7 @@ where mut alloc: BlockSpaceAllocator, tx_indices: &mut IndexSet, txs: &[TxBytes], - ) -> Vec { + ) -> (Vec, EncryptedTxBatchAllocator) { if self.storage.last_height == BlockHeight(0) { // genesis should not contain vote extensions return (vec![], self.get_encrypted_txs_allocator(alloc)); @@ -202,7 +201,7 @@ where let txs = deserialize_vote_extensions(txs).enumerate().take_while(|(index, tx_bytes)| { match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => { - tx_indices.insert(index); + tx_indices.insert(*index); true }, AllocStatus::Rejected { tx, space_left } => { @@ -331,7 +330,7 @@ where .collect(); let alloc = alloc.next_state(); - tx_indices.merge(&invalid_txs); + tx_indices.union(&invalid_txs); (txs, alloc) } @@ -363,7 +362,7 @@ where }) .to_bytes() }) - // TODO: make sure all txs are accepted; + // TODO: make sure all txs are accepted .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { AllocStatus::Accepted => true, AllocStatus::Rejected { tx, space_left } => { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 68618b24ed..15d49222e8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -432,184 +432,3 @@ mod tests { prop::collection::vec(tx, 0..=MAX_TX_NUM) } } - -#[cfg(test)] -mod tests { - use std::cell::RefCell; - - use assert_matches::assert_matches; - use proptest::prelude::*; - - use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; - use super::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - - /// Proptest generated txs. - #[derive(Debug)] - struct PropTx { - tendermint_max_block_space_in_bytes: u64, - protocol_txs: Vec, - encrypted_txs: Vec, - decrypted_txs: Vec, - } - - proptest! { - /// Check if we reject a tx when its respective bin - /// capacity has been reached on a [`BlockSpaceAllocator`]. - #[test] - fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { - proptest_reject_tx_on_bin_cap_reached(max) - } - - /// Check if the sum of all individual bin allotments for a - /// [`BlockSpaceAllocator`] corresponds to the total space ceded - /// by Tendermint. - #[test] - fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { - proptest_bin_capacity_eq_provided_space(max) - } - - /// Test that dumping txs whose total combined size - /// is less than the bin cap does not fill up the bin. - #[test] - fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { - proptest_tx_dump_doesnt_fill_up_bin(args) - } - } - - /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. - fn proptest_reject_tx_on_bin_cap_reached( - tendermint_max_block_space_in_bytes: u64, - ) { - let mut bins = - BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); - - // fill the entire bin of decrypted txs - bins.decrypted_txs.occupied_space_in_bytes = - bins.decrypted_txs.allotted_space_in_bytes; - - // make sure we can't dump any new decrypted txs in the bin - assert_matches!( - bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected { .. } - ); - } - - /// Implementation of [`test_bin_capacity_eq_provided_space`]. - fn proptest_bin_capacity_eq_provided_space( - tendermint_max_block_space_in_bytes: u64, - ) { - let bins = - BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); - assert_eq!(0, bins.uninitialized_space_in_bytes()); - } - - /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. - fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { - let PropTx { - tendermint_max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } = args; - - // produce new txs until the moment we would have - // filled up the bins. - // - // iterate over the produced txs to make sure we can keep - // dumping new txs without filling up the bins - - let bins = RefCell::new(BlockSpaceAllocator::init( - tendermint_max_block_space_in_bytes, - )); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.occupied_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - for tx in decrypted_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); - } - - let bins = RefCell::new(bins.into_inner().next_state()); - let protocol_txs = protocol_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().protocol_txs; - let new_size = bin.occupied_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - for tx in protocol_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); - } - - let bins = - RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); - let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().encrypted_txs; - let new_size = bin.occupied_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - for tx in encrypted_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); - } - } - - prop_compose! { - /// Generate arbitrarily sized txs of different kinds. - fn arb_transactions() - // create base strategies - ( - (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, - decrypted_tx_max_bin_size) in arb_max_bin_sizes(), - ) - // compose strategies - ( - tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), - protocol_txs in arb_tx_list(protocol_tx_max_bin_size), - encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), - decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), - ) - -> PropTx { - PropTx { - tendermint_max_block_space_in_bytes, - protocol_txs, - encrypted_txs, - decrypted_txs, - } - } - } - - /// Return random bin sizes for a [`BlockSpaceAllocator`]. - fn arb_max_bin_sizes() -> impl Strategy - { - const MAX_BLOCK_SIZE_BYTES: u64 = 1000; - (1..=MAX_BLOCK_SIZE_BYTES).prop_map( - |tendermint_max_block_space_in_bytes| { - ( - tendermint_max_block_space_in_bytes, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - ) - }, - ) - } - - /// Return a list of txs. - fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { - const MAX_TX_NUM: usize = 64; - let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); - prop::collection::vec(tx, 0..=MAX_TX_NUM) - } -} From 5eca9b3ed46c6a908091036f0b624dbe03e5a48b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 13:37:36 +0000 Subject: [PATCH 1743/1995] Remove batch allocation of txs --- .../prepare_proposal/block_space_alloc.rs | 22 ------------------- .../block_space_alloc/states.rs | 12 ---------- .../block_space_alloc/states/protocol_txs.rs | 17 +++++--------- 3 files changed, 5 insertions(+), 46 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 0a725bcef8..b29092aaa3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -214,28 +214,6 @@ impl TxBin { AllocStatus::Rejected { tx, space_left } } } - - /// Try to dump a new batch of transactions into this [`TxBin`]. - /// - /// If an allocation fails, rollback the state of the [`TxBin`], - /// and return the respective status of the failure. - fn try_dump_all<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - let mut space_diff = 0; - for tx in txs { - match self.try_dump(tx) { - AllocStatus::Accepted => space_diff += tx.len() as u64, - status @ (AllocStatus::Rejected { .. } - | AllocStatus::OverflowsBin { .. }) => { - self.occupied_space_in_bytes -= space_diff; - return status; - } - } - } - AllocStatus::Accepted - } } mod threshold { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 2bf3202d20..5e076858f4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -88,18 +88,6 @@ pub trait TryAlloc { fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx>; } -/// Try to allocate a new batch of transactions on a -/// [`BlockSpaceAllocator`] state. -/// -/// For more info, read the module docs of -/// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub trait TryAllocBatch { - /// Try to allocate space for a new batch of transactions. - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx; -} - /// Represents a state transition in the [`BlockSpaceAllocator`] state machine. /// /// This trait should not be used directly. Instead, consider using one of diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index bb22fd1bfc..d2bf1a5768 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -2,21 +2,14 @@ use std::marker::PhantomData; use super::super::{AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ - BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, - TryAllocBatch, WithEncryptedTxs, WithoutEncryptedTxs, + BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, + WithEncryptedTxs, WithoutEncryptedTxs, }; -impl TryAllocBatch for BlockSpaceAllocator { +impl TryAlloc for BlockSpaceAllocator { #[inline] - fn try_alloc_batch<'tx, T>(&mut self, txs: T) -> AllocStatus<'tx> - where - T: IntoIterator + 'tx, - { - // TODO: prioritize certain kinds of protocol txs; - // this can be done at the `CheckTx` level, - // we don't need the `TxBin`s to be aware - // of different prioriy hints for protocol txs - self.protocol_txs.try_dump_all(txs) + fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + self.protocol_txs.try_dump(tx) } } From d3b6b28144db1c9fd7a88ffca5730eb47c936f8f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 15:01:41 +0000 Subject: [PATCH 1744/1995] Add back (now broken) tx bin unit tests --- .../prepare_proposal/block_space_alloc.rs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index b29092aaa3..5db9e45a5d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -230,3 +230,191 @@ mod threshold { /// Divide the allotted space in two. pub const ONE_HALF: Ratio = Ratio::new_raw(1, 2); } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use proptest::prelude::*; + + use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space_in_bytes: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } + + /// Check if the sum of all individual tx thresholds does + /// not exceed one. + /// + /// This is important, because we do not want to exceed + /// the maximum block size in Tendermint, and get randomly + /// rejected blocks. + #[test] + fn test_tx_thres_doesnt_exceed_one() { + let sum = + thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; + assert_eq!(sum.to_integer(), 1); + } + + proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`BlockSpaceAllocator`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + + /// Check if the sum of all individual bin allotments for a + /// [`BlockSpaceAllocator`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not fill up the bin. + #[test] + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) + } + } + + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; + + // make sure we can't dump any new decrypted txs in the bin + assert_eq!( + bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + AllocStatus::Rejected + ); + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space_in_bytes: u64, + ) { + let bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + assert_eq!(0, bins.uninitialized_space_in_bytes()); + } + + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + let bins = RefCell::new(BlockSpaceAllocator::init( + tendermint_max_block_space_in_bytes, + )); + + // produce new txs until we fill up the bins + // + // TODO: ideally the proptest strategy would already return + // txs whose total added size would be bounded + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + + // make sure we can keep dumping txs, + // without filling up the bins + for tx in protocol_txs { + assert_eq!( + bins.borrow_mut().try_alloc_protocol_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in encrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_encrypted_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in decrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_decrypted_tx(&tx), + AllocStatus::Accepted + ); + } + } + + prop_compose! { + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions() + // create base strategies + ( + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), + ) + // compose strategies + ( + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), + ) + -> PropTx { + PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } + } + + /// Return random bin sizes for a [`BlockSpaceAllocator`]. + fn arb_max_bin_sizes() -> impl Strategy + { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + ) + }, + ) + } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } +} From cdda64b9438bf2833215031dd388b8fd1dd22b90 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 09:24:52 +0000 Subject: [PATCH 1745/1995] WIP: Fixing unit tests for tx bins --- .../prepare_proposal/block_space_alloc.rs | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 5db9e45a5d..aaf257aa8f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -237,6 +237,7 @@ mod tests { use proptest::prelude::*; + use super::states::{NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -249,19 +250,6 @@ mod tests { decrypted_txs: Vec, } - /// Check if the sum of all individual tx thresholds does - /// not exceed one. - /// - /// This is important, because we do not want to exceed - /// the maximum block size in Tendermint, and get randomly - /// rejected blocks. - #[test] - fn test_tx_thres_doesnt_exceed_one() { - let sum = - thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert_eq!(sum.to_integer(), 1); - } - proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. @@ -299,7 +287,7 @@ mod tests { // make sure we can't dump any new decrypted txs in the bin assert_eq!( - bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + bins.try_alloc(b"arbitrary tx bytes"), AllocStatus::Rejected ); } @@ -321,49 +309,44 @@ mod tests { encrypted_txs, decrypted_txs, } = args; + + // produce new txs until the moment we would have + // filled up the bins. + // + // iterate over the produced txs to make sure we can keep + // dumping new txs without filling up the bins + let bins = RefCell::new(BlockSpaceAllocator::init( tendermint_max_block_space_in_bytes, )); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in decrypted_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } - // produce new txs until we fill up the bins - // - // TODO: ideally the proptest strategy would already return - // txs whose total added size would be bounded + let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); + for tx in protocol_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } + + let bins = + RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - - // make sure we can keep dumping txs, - // without filling up the bins - for tx in protocol_txs { - assert_eq!( - bins.borrow_mut().try_alloc_protocol_tx(&tx), - AllocStatus::Accepted - ); - } for tx in encrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_encrypted_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in decrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_decrypted_tx(&tx), - AllocStatus::Accepted - ); + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); } } @@ -400,11 +383,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From 6ab5c2ba5d5d4259d4152ea028ae57a1410a3b73 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:10:01 +0000 Subject: [PATCH 1746/1995] Fix tx bins unit tests --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index aaf257aa8f..08f253a624 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -237,7 +237,7 @@ mod tests { use proptest::prelude::*; - use super::states::{NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; From 0668b6829d7e5fa94d0e8829bf868292bc31806f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:04:12 +0000 Subject: [PATCH 1747/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 08f253a624..e9e69842bd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -235,6 +235,7 @@ mod threshold { mod tests { use std::cell::RefCell; + use assert_matches::assert_matches; use proptest::prelude::*; use super::states::{NextState, NextStateWithEncryptedTxs, State}; @@ -286,9 +287,9 @@ mod tests { bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin - assert_eq!( + assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected + AllocStatus::Rejected { .. } ); } @@ -325,7 +326,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = RefCell::new(bins.into_inner().next_state()); @@ -335,7 +339,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = @@ -346,7 +353,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } } From b99ebeba59dc505800cbbe09060a236e6736ff85 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:55:16 +0000 Subject: [PATCH 1748/1995] Rebase fixes --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 10 +++++----- .../block_space_alloc/states/protocol_txs.rs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index e9e69842bd..aac9e382e0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -238,7 +238,7 @@ mod tests { use assert_matches::assert_matches; use proptest::prelude::*; - use super::states::{NextState, NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -283,7 +283,7 @@ mod tests { BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.occupied_space_in_bytes = bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin @@ -322,7 +322,7 @@ mod tests { )); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { @@ -335,7 +335,7 @@ mod tests { let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { @@ -349,7 +349,7 @@ mod tests { RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index d2bf1a5768..a322764ad3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,7 +6,8 @@ use super::{ WithEncryptedTxs, WithoutEncryptedTxs, }; -impl TryAlloc for BlockSpaceAllocator { +#[cfg(test)] +impl super::TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.protocol_txs.try_dump(tx) From 768415b329d161b203924ddaa31485e7d481ea6e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 12:46:27 +0000 Subject: [PATCH 1749/1995] Rebase fixes --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index aac9e382e0..1ca2902f1c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -393,11 +393,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From c6e5659e9f1c353233c4e45a5eb025beabd9a79f Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 13:40:56 +0000 Subject: [PATCH 1750/1995] Remove #[allow(dead_code)] --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 1ca2902f1c..716308a749 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -224,7 +224,6 @@ mod threshold { /// The threshold over Tendermint's allotted space for all three /// (major) kinds of Namada transactions. #[cfg(test)] - #[allow(dead_code)] pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); /// Divide the allotted space in two. From 659b911e76da0baec2179ce39180775d0450ca89 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 13:42:47 +0000 Subject: [PATCH 1751/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc/states/protocol_txs.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index a322764ad3..d2bf1a5768 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,8 +6,7 @@ use super::{ WithEncryptedTxs, WithoutEncryptedTxs, }; -#[cfg(test)] -impl super::TryAlloc for BlockSpaceAllocator { +impl TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.protocol_txs.try_dump(tx) From 70fe9449a20f3285503aec7d9c3b88219e695f22 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 14:59:56 +0000 Subject: [PATCH 1752/1995] Add a Threshold type --- .../prepare_proposal/block_space_alloc.rs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index b29092aaa3..027b875f33 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -52,8 +52,6 @@ pub mod states; use std::marker::PhantomData; -use num_rational::Ratio; - use crate::facade::tendermint_proto::abci::RequestPrepareProposal; /// All status responses from trying to allocate block space for a tx. @@ -165,8 +163,8 @@ impl TxBin { /// Return a new [`TxBin`] with a total allotted space equal to the /// floor of the fraction `frac` of the available block space `max_bytes`. #[inline] - fn init_over_ratio(max_bytes: u64, frac: Ratio) -> Self { - let allotted_space_in_bytes = (frac * max_bytes).to_integer(); + fn init_over_ratio(max_bytes: u64, frac: threshold::Threshold) -> Self { + let allotted_space_in_bytes = frac.over(max_bytes); Self { allotted_space_in_bytes, occupied_space_in_bytes: 0, @@ -221,12 +219,27 @@ mod threshold { use num_rational::Ratio; - /// The threshold over Tendermint's allotted space for all three - /// (major) kinds of Namada transactions. - #[cfg(test)] - #[allow(dead_code)] - pub const ONE_THIRD: Ratio = Ratio::new_raw(1, 3); + /// Threshold over a portion of block space. + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct Threshold(Ratio); + + impl Threshold { + /// Return a new [`Threshold`]. + const fn new(numer: u64, denom: u64) -> Self { + // constrain ratio to a max of 1 + let numer = if numer > denom { denom } else { numer }; + Self(Ratio::new_raw(numer, denom)) + } + + /// Return a [`Threshold`] over some free space. + pub fn over(self, free_space_in_bytes: u64) -> u64 { + (self.0 * free_space_in_bytes).to_integer() + } + } + + /// Divide free space in three. + pub const ONE_THIRD: Threshold = Threshold::new(1, 3); - /// Divide the allotted space in two. - pub const ONE_HALF: Ratio = Ratio::new_raw(1, 2); + /// Divide free space in two. + pub const ONE_HALF: Threshold = Threshold::new(1, 2); } From 5d0ff360a5e7e570463bd3134267fea45e6104a1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 15:01:08 +0000 Subject: [PATCH 1753/1995] Reserve at most 1/3 of block space to encrypted txs --- .../block_space_alloc/states/protocol_txs.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index d2bf1a5768..a5c1f3931c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use super::super::{AllocStatus, BlockSpaceAllocator, TxBin}; +use super::super::{threshold, AllocStatus, BlockSpaceAllocator, TxBin}; use super::{ BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, @@ -22,9 +22,16 @@ impl NextStateImpl fn next_state_impl(mut self) -> Self::Next { self.protocol_txs.shrink_to_fit(); - // reserve space for encrypted txs + // reserve space for encrypted txs; encrypted txs can use up to + // 1/3 of the max block space; the rest goes to protocol txs, once + // more + let one_third_of_block_space = + threshold::ONE_THIRD.over(self.block.allotted_space_in_bytes); let remaining_free_space = self.uninitialized_space_in_bytes(); - self.encrypted_txs = TxBin::init(remaining_free_space); + self.encrypted_txs = TxBin::init(std::cmp::min( + one_third_of_block_space, + remaining_free_space, + )); // cast state let Self { From f419b5342f0c1fcf0e05bafb45416b4faa36f54d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 15:13:59 +0000 Subject: [PATCH 1754/1995] Edit TODO msg --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 4c8a248956..5953218e28 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -176,7 +176,7 @@ where .map(|tx| tx.sign(protocol_key).to_bytes()) .collect(); - // TODO: + // TODO(feature = "abcipp"): // - alloc space for each protocol tx // - handle space allocation errors // - transition to new allocator state From cd173d8a9187f92457fcd6130b4a5bfb52fb30fd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 15 Nov 2022 15:01:41 +0000 Subject: [PATCH 1755/1995] Add back (now broken) tx bin unit tests --- .../prepare_proposal/block_space_alloc.rs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 027b875f33..2c507698d9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -243,3 +243,191 @@ mod threshold { /// Divide free space in two. pub const ONE_HALF: Threshold = Threshold::new(1, 2); } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + + use proptest::prelude::*; + + use super::*; + use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; + + /// Proptest generated txs. + #[derive(Debug)] + struct PropTx { + tendermint_max_block_space_in_bytes: u64, + protocol_txs: Vec, + encrypted_txs: Vec, + decrypted_txs: Vec, + } + + /// Check if the sum of all individual tx thresholds does + /// not exceed one. + /// + /// This is important, because we do not want to exceed + /// the maximum block size in Tendermint, and get randomly + /// rejected blocks. + #[test] + fn test_tx_thres_doesnt_exceed_one() { + let sum = + thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; + assert_eq!(sum.to_integer(), 1); + } + + proptest! { + /// Check if we reject a tx when its respective bin + /// capacity has been reached on a [`BlockSpaceAllocator`]. + #[test] + fn test_reject_tx_on_bin_cap_reached(max in prop::num::u64::ANY) { + proptest_reject_tx_on_bin_cap_reached(max) + } + + /// Check if the sum of all individual bin allotments for a + /// [`BlockSpaceAllocator`] corresponds to the total space ceded + /// by Tendermint. + #[test] + fn test_bin_capacity_eq_provided_space(max in prop::num::u64::ANY) { + proptest_bin_capacity_eq_provided_space(max) + } + + /// Test that dumping txs whose total combined size + /// is less than the bin cap does not fill up the bin. + #[test] + fn test_tx_dump_doesnt_fill_up_bin(args in arb_transactions()) { + proptest_tx_dump_doesnt_fill_up_bin(args) + } + } + + /// Implementation of [`test_reject_tx_on_bin_cap_reached`]. + fn proptest_reject_tx_on_bin_cap_reached( + tendermint_max_block_space_in_bytes: u64, + ) { + let mut bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + + // fill the entire bin of decrypted txs + bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.allotted_space_in_bytes; + + // make sure we can't dump any new decrypted txs in the bin + assert_eq!( + bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + AllocStatus::Rejected + ); + } + + /// Implementation of [`test_bin_capacity_eq_provided_space`]. + fn proptest_bin_capacity_eq_provided_space( + tendermint_max_block_space_in_bytes: u64, + ) { + let bins = + BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); + assert_eq!(0, bins.uninitialized_space_in_bytes()); + } + + /// Implementation of [`test_tx_dump_doesnt_fill_up_bin`]. + fn proptest_tx_dump_doesnt_fill_up_bin(args: PropTx) { + let PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } = args; + let bins = RefCell::new(BlockSpaceAllocator::init( + tendermint_max_block_space_in_bytes, + )); + + // produce new txs until we fill up the bins + // + // TODO: ideally the proptest strategy would already return + // txs whose total added size would be bounded + let protocol_txs = protocol_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().protocol_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().encrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + + // make sure we can keep dumping txs, + // without filling up the bins + for tx in protocol_txs { + assert_eq!( + bins.borrow_mut().try_alloc_protocol_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in encrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_encrypted_tx(&tx), + AllocStatus::Accepted + ); + } + for tx in decrypted_txs { + assert_eq!( + bins.borrow_mut().try_alloc_decrypted_tx(&tx), + AllocStatus::Accepted + ); + } + } + + prop_compose! { + /// Generate arbitrarily sized txs of different kinds. + fn arb_transactions() + // create base strategies + ( + (tendermint_max_block_space_in_bytes, protocol_tx_max_bin_size, encrypted_tx_max_bin_size, + decrypted_tx_max_bin_size) in arb_max_bin_sizes(), + ) + // compose strategies + ( + tendermint_max_block_space_in_bytes in Just(tendermint_max_block_space_in_bytes), + protocol_txs in arb_tx_list(protocol_tx_max_bin_size), + encrypted_txs in arb_tx_list(encrypted_tx_max_bin_size), + decrypted_txs in arb_tx_list(decrypted_tx_max_bin_size), + ) + -> PropTx { + PropTx { + tendermint_max_block_space_in_bytes, + protocol_txs, + encrypted_txs, + decrypted_txs, + } + } + } + + /// Return random bin sizes for a [`BlockSpaceAllocator`]. + fn arb_max_bin_sizes() -> impl Strategy + { + const MAX_BLOCK_SIZE_BYTES: u64 = 1000; + (1..=MAX_BLOCK_SIZE_BYTES).prop_map( + |tendermint_max_block_space_in_bytes| { + ( + tendermint_max_block_space_in_bytes, + (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + .to_integer() as usize, + ) + }, + ) + } + + /// Return a list of txs. + fn arb_tx_list(max_bin_size: usize) -> impl Strategy>> { + const MAX_TX_NUM: usize = 64; + let tx = prop::collection::vec(prop::num::u8::ANY, 0..=max_bin_size); + prop::collection::vec(tx, 0..=MAX_TX_NUM) + } +} From a17c87a6d14fcb007869a259b68ca416cedc6187 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 09:24:52 +0000 Subject: [PATCH 1756/1995] WIP: Fixing unit tests for tx bins --- .../prepare_proposal/block_space_alloc.rs | 73 +++++++------------ 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 2c507698d9..a4b47476c2 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -250,6 +250,7 @@ mod tests { use proptest::prelude::*; + use super::states::{NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -262,19 +263,6 @@ mod tests { decrypted_txs: Vec, } - /// Check if the sum of all individual tx thresholds does - /// not exceed one. - /// - /// This is important, because we do not want to exceed - /// the maximum block size in Tendermint, and get randomly - /// rejected blocks. - #[test] - fn test_tx_thres_doesnt_exceed_one() { - let sum = - thres::PROTOCOL_TX + thres::ENCRYPTED_TX + thres::DECRYPTED_TX; - assert_eq!(sum.to_integer(), 1); - } - proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. @@ -312,7 +300,7 @@ mod tests { // make sure we can't dump any new decrypted txs in the bin assert_eq!( - bins.try_alloc_decrypted_tx(b"arbitrary tx bytes"), + bins.try_alloc(b"arbitrary tx bytes"), AllocStatus::Rejected ); } @@ -334,49 +322,44 @@ mod tests { encrypted_txs, decrypted_txs, } = args; + + // produce new txs until the moment we would have + // filled up the bins. + // + // iterate over the produced txs to make sure we can keep + // dumping new txs without filling up the bins + let bins = RefCell::new(BlockSpaceAllocator::init( tendermint_max_block_space_in_bytes, )); + let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { + let bin = bins.borrow().decrypted_txs; + let new_size = bin.current_space_in_bytes + tx.len() as u64; + new_size < bin.allotted_space_in_bytes + }); + for tx in decrypted_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } - // produce new txs until we fill up the bins - // - // TODO: ideally the proptest strategy would already return - // txs whose total added size would be bounded + let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); + for tx in protocol_txs { + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + } + + let bins = + RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; let new_size = bin.current_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); - let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { - let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; - new_size < bin.allotted_space_in_bytes - }); - - // make sure we can keep dumping txs, - // without filling up the bins - for tx in protocol_txs { - assert_eq!( - bins.borrow_mut().try_alloc_protocol_tx(&tx), - AllocStatus::Accepted - ); - } for tx in encrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_encrypted_tx(&tx), - AllocStatus::Accepted - ); - } - for tx in decrypted_txs { - assert_eq!( - bins.borrow_mut().try_alloc_decrypted_tx(&tx), - AllocStatus::Accepted - ); + assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); } } @@ -413,11 +396,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::PROTOCOL_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ENCRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::DECRYPTED_TX * tendermint_max_block_space_in_bytes) + (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From 27faba35b003d871e829264918682757f94950e8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 10:10:01 +0000 Subject: [PATCH 1757/1995] Fix tx bins unit tests --- .../lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index a4b47476c2..830972221d 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -250,7 +250,7 @@ mod tests { use proptest::prelude::*; - use super::states::{NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, State}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; From 4241f986b066719645f1a89b6be02824ac437398 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 16 Nov 2022 16:04:12 +0000 Subject: [PATCH 1758/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 830972221d..583c75bdb5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -248,6 +248,7 @@ mod threshold { mod tests { use std::cell::RefCell; + use assert_matches::assert_matches; use proptest::prelude::*; use super::states::{NextState, NextStateWithEncryptedTxs, State}; @@ -299,9 +300,9 @@ mod tests { bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin - assert_eq!( + assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected + AllocStatus::Rejected { .. } ); } @@ -338,7 +339,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = RefCell::new(bins.into_inner().next_state()); @@ -348,7 +352,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } let bins = @@ -359,7 +366,10 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { - assert_eq!(bins.borrow_mut().try_alloc(&tx), AllocStatus::Accepted); + assert_matches!( + bins.borrow_mut().try_alloc(&tx), + AllocStatus::Accepted + ); } } From 6ddab9ecc1d55cc12c017315b7a58afbda9dc37d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 18 Nov 2022 13:55:16 +0000 Subject: [PATCH 1759/1995] Rebase fixes --- .../ledger/shell/prepare_proposal/block_space_alloc.rs | 10 +++++----- .../block_space_alloc/states/protocol_txs.rs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 583c75bdb5..14578cacc0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -251,7 +251,7 @@ mod tests { use assert_matches::assert_matches; use proptest::prelude::*; - use super::states::{NextState, NextStateWithEncryptedTxs, State}; + use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -296,7 +296,7 @@ mod tests { BlockSpaceAllocator::init(tendermint_max_block_space_in_bytes); // fill the entire bin of decrypted txs - bins.decrypted_txs.current_space_in_bytes = + bins.decrypted_txs.occupied_space_in_bytes = bins.decrypted_txs.allotted_space_in_bytes; // make sure we can't dump any new decrypted txs in the bin @@ -335,7 +335,7 @@ mod tests { )); let decrypted_txs = decrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().decrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { @@ -348,7 +348,7 @@ mod tests { let bins = RefCell::new(bins.into_inner().next_state()); let protocol_txs = protocol_txs.into_iter().take_while(|tx| { let bin = bins.borrow().protocol_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { @@ -362,7 +362,7 @@ mod tests { RefCell::new(bins.into_inner().next_state_with_encrypted_txs()); let encrypted_txs = encrypted_txs.into_iter().take_while(|tx| { let bin = bins.borrow().encrypted_txs; - let new_size = bin.current_space_in_bytes + tx.len() as u64; + let new_size = bin.occupied_space_in_bytes + tx.len() as u64; new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index a5c1f3931c..ac8f861d35 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,7 +6,8 @@ use super::{ WithEncryptedTxs, WithoutEncryptedTxs, }; -impl TryAlloc for BlockSpaceAllocator { +#[cfg(test)] +impl super::TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.protocol_txs.try_dump(tx) From 377a05edfbd7a61715edaa913722d9e9669412eb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 21 Nov 2022 12:46:27 +0000 Subject: [PATCH 1760/1995] Rebase fixes --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 14578cacc0..38da3df8c4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -406,11 +406,11 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, - (thres::ONE_THIRD * tendermint_max_block_space_in_bytes) + (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) .to_integer() as usize, ) }, From 9838ea237d94a6dd352f50f5016a3d9a393907ad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 13:42:47 +0000 Subject: [PATCH 1761/1995] Rebase fixes --- .../prepare_proposal/block_space_alloc/states/protocol_txs.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index ac8f861d35..a5c1f3931c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -6,8 +6,7 @@ use super::{ WithEncryptedTxs, WithoutEncryptedTxs, }; -#[cfg(test)] -impl super::TryAlloc for BlockSpaceAllocator { +impl TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { self.protocol_txs.try_dump(tx) From 437f073f11aacfbf2527863829f3e2ef60e9990a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 15:35:00 +0000 Subject: [PATCH 1762/1995] Rebase fixes --- .../shell/prepare_proposal/block_space_alloc.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 38da3df8c4..2bc0e937fd 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -406,12 +406,15 @@ mod tests { |tendermint_max_block_space_in_bytes| { ( tendermint_max_block_space_in_bytes, - (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, - (threshold::ONE_THIRD * tendermint_max_block_space_in_bytes) - .to_integer() as usize, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, + threshold::ONE_THIRD + .over(tendermint_max_block_space_in_bytes) + as usize, ) }, ) From 5cb05afa1991ce2f8b9ad1b1bbda59befcf17c40 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 16:25:04 +0000 Subject: [PATCH 1763/1995] Do not include encrypted txs if the state disallows it --- .../lib/node/ledger/shell/prepare_proposal.rs | 9 ++++--- .../block_space_alloc/states.rs | 26 ++++++++++++------- .../block_space_alloc/states/encrypted_txs.rs | 24 ++++++++++++----- .../block_space_alloc/states/remaining_txs.rs | 22 +++++++++++++--- 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5953218e28..a3bc33b9cc 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -3,7 +3,6 @@ mod block_space_alloc; use index_set::IndexSet; -use itertools::Either::*; use namada::hints; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; @@ -256,9 +255,13 @@ where ?self.storage.get_current_decision_height(), "No mempool txs are being included in the current proposal" ); - Right(alloc.next_state_without_encrypted_txs()) + EncryptedTxBatchAllocator::WithoutEncryptedTxs( + alloc.next_state_without_encrypted_txs(), + ) } else { - Left(alloc.next_state_with_encrypted_txs()) + EncryptedTxBatchAllocator::WithEncryptedTxs( + alloc.next_state_with_encrypted_txs(), + ) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 5009b957c9..b7ed54a0b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -28,23 +28,29 @@ mod encrypted_txs; mod protocol_txs; mod remaining_txs; -use itertools::Either; - use super::{AllocStatus, BlockSpaceAllocator}; /// Convenience wrapper for a [`BlockSpaceAllocator`] state that allocates /// encrypted transactions. -pub type EncryptedTxBatchAllocator = Either< - BlockSpaceAllocator>, - BlockSpaceAllocator>, ->; +pub enum EncryptedTxBatchAllocator { + WithEncryptedTxs( + BlockSpaceAllocator>, + ), + WithoutEncryptedTxs( + BlockSpaceAllocator>, + ), +} /// Convenience wrapper for a [`BlockSpaceAllocator`] state that fills up the /// remaining block space. -pub type RemainingBatchAllocator = Either< - BlockSpaceAllocator>, - BlockSpaceAllocator>, ->; +pub enum RemainingBatchAllocator { + WithEncryptedTxs( + BlockSpaceAllocator>, + ), + WithoutEncryptedTxs( + BlockSpaceAllocator>, + ), +} /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 6463b99b59..a2636d8d9b 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -1,7 +1,5 @@ use std::marker::PhantomData; -use itertools::Either::*; - use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ BuildingEncryptedTxBatch, EncryptedTxBatchAllocator, FillingRemainingSpace, @@ -80,8 +78,14 @@ impl TryAlloc for EncryptedTxBatchAllocator { #[inline] fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { match self { - Left(state) => state.try_alloc(tx), - Right(state) => state.try_alloc(tx), + EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { + state.try_alloc(tx) + } + EncryptedTxBatchAllocator::WithoutEncryptedTxs(state) => { + // NOTE: this operation will cause the allocator to + // run out of memory immediately + state.try_alloc(tx) + } } } } @@ -92,8 +96,16 @@ impl NextStateImpl for EncryptedTxBatchAllocator { #[inline] fn next_state_impl(self) -> Self::Next { match self { - Left(state) => Left(state.next_state_impl()), - Right(state) => Right(state.next_state_impl()), + EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { + RemainingBatchAllocator::WithEncryptedTxs( + state.next_state_impl(), + ) + } + EncryptedTxBatchAllocator::WithoutEncryptedTxs(state) => { + RemainingBatchAllocator::WithoutEncryptedTxs( + state.next_state_impl(), + ) + } } } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index 4a2a17d684..77e6c52923 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -1,4 +1,6 @@ -use itertools::Either::*; +use namada::proto::Tx; +use namada::types::transaction::process_tx; +use namada::types::transaction::tx_types::TxType; use super::super::{AllocStatus, BlockSpaceAllocator}; use super::{ @@ -27,10 +29,22 @@ impl TryAlloc impl TryAlloc for RemainingBatchAllocator { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc<'tx>(&mut self, tx_bytes: &'tx [u8]) -> AllocStatus<'tx> { match self { - Left(state) => state.try_alloc(tx), - Right(state) => state.try_alloc(tx), + RemainingBatchAllocator::WithEncryptedTxs(state) => { + state.try_alloc(tx_bytes) + } + RemainingBatchAllocator::WithoutEncryptedTxs(state) => { + let tx = Tx::try_from(tx_bytes) + .expect("Tx passed mempool validation"); + if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + // do not allocate anything if we + // find an encrypted tx + AllocStatus::Accepted + } else { + state.try_alloc(tx_bytes) + } + } } } } From 1448848291985c830dccece1049e8d9d64f1c741 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 25 Nov 2022 16:25:33 +0000 Subject: [PATCH 1764/1995] Remove outdated NOTE --- .../prepare_proposal/block_space_alloc/states/remaining_txs.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index 77e6c52923..3dd508dc15 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -15,9 +15,6 @@ impl TryAlloc for BlockSpaceAllocator> { } } -// TODO: limit txs that can go in the bins at this level? so we don't misuse -// the abstraction. it's not like we can't push encrypted txs into the bins, -// right now... impl TryAlloc for BlockSpaceAllocator> { From 9e155cffaec4b4d80058d01fda17f07c3abf9d95 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 09:15:59 +0000 Subject: [PATCH 1765/1995] Change PrepareProposal docstr to reflect allocator design --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a3bc33b9cc..b29d7e4a58 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -45,15 +45,15 @@ where { /// 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. + /// Block space is roughly divided into three equal parts, which we call + /// allotments. In the following order, each allotted area is home to: + /// decrypted, protocol and encrypted transactions, respectively. During + /// some protocol phases, we might omit encrypted and decrypted transactions + /// entirely, such as when we negotiate DKG parameters. /// /// 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). - // TODO: change second paragraph of the docstr, to reflect new - // allotted space per block design pub fn prepare_proposal( &mut self, req: RequestPrepareProposal, From f5235123de4e7b91452e1c030f3972f7068ef90e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 09:24:34 +0000 Subject: [PATCH 1766/1995] Fix tx indexing bug --- .../lib/node/ledger/shell/prepare_proposal.rs | 8 ++------ .../src/lib/node/ledger/shell/vote_extensions.rs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index b29d7e4a58..11ba2a4784 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -197,12 +197,9 @@ where return (vec![], self.get_encrypted_txs_allocator(alloc)); } - let txs = deserialize_vote_extensions(txs).enumerate().take_while(|(index, tx_bytes)| { + let txs = deserialize_vote_extensions(txs, tx_indices).take_while(|tx_bytes| { match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => { - tx_indices.insert(*index); - true - }, + AllocStatus::Accepted => true, AllocStatus::Rejected { tx, space_left } => { // TODO: maybe we should find a way to include // validator set updates all the time. for instance, @@ -234,7 +231,6 @@ where } } }) - .map(|(_, tx_bytes)| tx_bytes) .collect(); (txs, self.get_encrypted_txs_allocator(alloc)) diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c2a7f132e9..17548fea67 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -5,6 +5,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; +use index_set::IndexSet; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; @@ -300,12 +301,13 @@ pub fn deserialize_vote_extensions( /// ones we could deserialize to vote extension [`ProtocolTx`] /// instances. #[cfg(not(feature = "abcipp"))] -pub fn deserialize_vote_extensions( - txs: &[TxBytes], -) -> impl Iterator + '_ { +pub fn deserialize_vote_extensions<'shell>( + txs: &'shell [TxBytes], + tx_indices: &'shell mut IndexSet, +) -> impl Iterator + 'shell { use namada::types::transaction::protocol::ProtocolTx; - txs.iter().filter_map(|tx_bytes| { + txs.iter().enumerate().filter_map(|(index, tx_bytes)| { let tx = match Tx::try_from(tx_bytes.as_slice()) { Ok(tx) => tx, Err(err) => { @@ -322,7 +324,11 @@ pub fn deserialize_vote_extensions( ProtocolTxType::EthEventsVext(_) | ProtocolTxType::ValSetUpdateVext(_), .. - }) => Some(tx_bytes.clone()), + }) => { + // mark tx for inclusion + tx_indices.insert(index); + Some(tx_bytes.clone()) + } _ => None, } }) From 2a8b38741cd386abd74c82da7776567271391b37 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 09:42:37 +0000 Subject: [PATCH 1767/1995] Update index-set --- Cargo.lock | 4 ++-- apps/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd57247971..726a06449b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2690,8 +2690,8 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "index-set" -version = "0.4.0" -source = "git+https://github.com/heliaxdev/index-set?tag=v0.4.0#45bb669be1c0babd9e2a2ebcf1b91720642193d8" +version = "0.4.1" +source = "git+https://github.com/heliaxdev/index-set?tag=v0.4.1#32e81b025cfb2be32be861e7f488f628e5ce9ffb" [[package]] name = "indexmap" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1d33ca279a..342472e708 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -98,7 +98,7 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" -index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.4.0"} +index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.4.1"} itertools = "0.10.1" libc = "0.2.97" libloading = "0.7.2" From fb92431636a2f4ecbc22ef5e33992c300c7e4fbc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 09:56:08 +0000 Subject: [PATCH 1768/1995] Add new Makefile build-debug target --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index bf7bb44c10..cf748992d3 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,9 @@ build-test: build-release: ANOMA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml +build-debug: + ANOMA_DEV=false $(cargo) build --package namada_apps --manifest-path Cargo.toml + install-release: ANOMA_DEV=false $(cargo) install --path ./apps --locked From a2c1c6cd765521ecc578db2eb51a8971be15d8c1 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 28 Nov 2022 10:02:32 +0000 Subject: [PATCH 1769/1995] Update apps/src/lib/node/ledger/shell/process_proposal.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/node/ledger/shell/process_proposal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 935f609e4a..4d8f0e61d5 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -477,7 +477,7 @@ where // If the public key corresponds to the MASP sentinel // transaction key, then the fee payer is effectively // the MASP, otherwise derive - // they payer from public key. + // the payer from public key. let fee_payer = if tx.pk != masp_tx_key().ref_to() { tx.fee_payer() } else { From c055bb56ad5820056ac3e9dd71bebb82876cae66 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 13:05:47 +0000 Subject: [PATCH 1770/1995] Enable sign extend wasm opcodes --- shared/Cargo.toml | 4 ++-- shared/src/vm/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index b2a361e513..f303ab9a54 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -112,13 +112,13 @@ ics23 = "0.7.0" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -parity-wasm = {version = "0.42.2", optional = true} +parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste = "1.0.9" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" -pwasm-utils = {version = "0.18.0", optional = true} +pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} diff --git a/shared/src/vm/mod.rs b/shared/src/vm/mod.rs index 2e14666f81..70fae7a3d4 100644 --- a/shared/src/vm/mod.rs +++ b/shared/src/vm/mod.rs @@ -28,7 +28,7 @@ const UNTRUSTED_WASM_FEATURES: WasmFeatures = WasmFeatures { memory64: false, mutable_global: false, saturating_float_to_int: false, - sign_extension: false, + sign_extension: true, relaxed_simd: false, extended_const: false, }; From 2e06ad140f57d76ce20dd2cea89ccddcc5f0ccd0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 13:14:23 +0000 Subject: [PATCH 1771/1995] Update Cargo.lock files --- Cargo.lock | 9 ++++----- wasm/Cargo.lock | 9 ++++----- wasm_for_tests/wasm_source/Cargo.lock | 9 ++++----- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b3f0391dd..a8a2609943 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4786,9 +4786,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" @@ -5259,9 +5259,8 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" +version = "0.20.0" +source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" dependencies = [ "byteorder", "log 0.4.17", diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index ae17eeb34d..a806119c0a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2897,9 +2897,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking_lot" @@ -3259,9 +3259,8 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" +version = "0.20.0" +source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" dependencies = [ "byteorder", "log", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 02279f7d57..cf5a6054ad 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2889,9 +2889,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking_lot" @@ -3251,9 +3251,8 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" +version = "0.20.0" +source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" dependencies = [ "byteorder", "log", From 567ff6769642beee09e966eb4a709a6705f9cd31 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 21 Oct 2022 21:26:51 +0100 Subject: [PATCH 1772/1995] Tally votes across multiple rounds --- .../transactions/ethereum_events/mod.rs | 40 +++-- .../transactions/validator_set_update/mod.rs | 20 ++- .../src/ledger/protocol/transactions/votes.rs | 148 +++++++++++++++++- 3 files changed, 172 insertions(+), 36 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 820bc66494..731b56b591 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -5,7 +5,7 @@ mod events; use std::collections::{BTreeSet, HashMap, HashSet}; -use eth_msgs::{EthMsg, EthMsgUpdate}; +use eth_msgs::EthMsgUpdate; use eyre::Result; use super::ChangedKeys; @@ -140,39 +140,37 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let mut votes = HashMap::default(); + let mut fractional_voting_powers = HashMap::default(); update.seen_by.iter().for_each(|(address, block_height)| { - let voting_power = voting_powers + let fract_voting_power = voting_powers .get(&(address.to_owned(), block_height.to_owned())) .unwrap(); - if let Some(already_present_voting_power) = - votes.insert(address.to_owned(), voting_power.to_owned()) + if let Some(already_present_fract_voting_power) = + fractional_voting_powers + .insert(address.to_owned(), fract_voting_power.to_owned()) { tracing::warn!( ?address, - ?already_present_voting_power, - new_voting_power = ?voting_power, - "Validator voted more than once, arbitrarily using later value", + ?already_present_fract_voting_power, + new_fract_voting_power = ?fract_voting_power, + "Validator voted more than once on Ethereum event, \ + arbitrarily using later value" ) } }); - let (vote_tracking, changed) = - calculate_updated(storage, ð_msg_keys, &votes)?; + let (vote_tracking, changed) = calculate_updated( + storage, + ð_msg_keys, + &fractional_voting_powers, + &update.seen_by, + )?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) }; - let eth_msg_post = EthMsg { - body: update.body, - votes: vote_tracking, - }; - tracing::debug!("writing EthMsg - {:#?}", ð_msg_post); - votes::storage::write( - storage, - ð_msg_keys, - ð_msg_post.body, - ð_msg_post.votes, - )?; + + write(storage, ð_msg_keys, &update.body, &vote_tracking)?; + Ok((changed, confirmed)) } diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 7fbc82f896..ca577f7b6f 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -110,12 +110,14 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let mut votes = HashMap::default(); - seen_by.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = - voting_powers.get(&(address.clone(), block_height)).unwrap(); + let mut fractional_voting_powers = HashMap::default(); + seen_by.iter().for_each(|(address, block_height)| { + let fract_voting_power = voting_powers + .get(&(address.clone(), block_height.to_owned())) + .unwrap(); if let Some(already_present_fract_voting_power) = - votes.insert(address.clone(), fract_voting_power.to_owned()) + fractional_voting_powers + .insert(address.clone(), fract_voting_power.to_owned()) { tracing::warn!( ?address, @@ -126,8 +128,12 @@ where ) } }); - let (tally, changed) = - votes::calculate_updated(storage, &valset_upd_keys, &votes)?; + let (tally, changed) = votes::calculate_updated( + storage, + &valset_upd_keys, + &fractional_voting_powers, + &seen_by, + )?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index baac41584e..7f5bffacc2 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -78,32 +78,164 @@ pub fn calculate_new( pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - _voters: &HashMap, + voting_powers: &HashMap, + votes: &Votes, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, T: BorshDeserialize, { - // TODO(namada#515): implement this - let _body: T = read::value(store, &keys.body())?; let seen: bool = read::value(store, &keys.seen())?; let seen_by: Votes = read::value(store, &keys.seen_by())?; let voting_power: FractionalVotingPower = read::value(store, &keys.voting_power())?; - let tally = Tally { + let tally_pre = Tally { voting_power, seen_by, seen, }; + let tally_post = calculate_update(keys, &tally_pre, voting_powers, votes); + let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; tracing::warn!( - ?tally, - "Updating events is not implemented yet, so the returned vote tally \ - will be identical to the one in storage", + ?tally_pre, + ?tally_post, + "Calculated and validated vote tracking updates", ); - Ok((tally, ChangedKeys::default())) + Ok((tally_post, changed_keys)) +} + +/// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new +/// validators which have seen it. `voting_powers` should map validators who +/// have newly seen the event to their fractional voting power at a block height +/// at which they saw the event. +fn calculate_update( + keys: &vote_tallies::Keys, + pre: &Tally, + voting_powers: &HashMap, + votes: &Votes, +) -> Tally { + let new_voters: BTreeSet
= voting_powers.keys().cloned().collect(); + + // For any event and validator, only the first vote by that validator for + // that event counts, later votes we encounter here can just be ignored. We + // can warn here when we encounter duplicate votes but these are + // reasonably likely to occur so this perhaps shouldn't be a warning unless + // it is happening a lot + let already_voted: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); + for validator in already_voted.intersection(&new_voters) { + tracing::warn!( + ?keys.prefix, + ?validator, + "Encountered duplicate vote for an event by a validator, ignoring" + ); + } + + let mut voting_power_post = pre.voting_power.clone(); + let mut seen_by_post = pre.seen_by.clone(); + for validator in new_voters.difference(&already_voted) { + tracing::info!( + ?keys.prefix, + ?validator, + "Recording validator as having voted for this event" + ); + seen_by_post.insert( + validator.to_owned(), + votes + .get(validator) + .expect("Validator must be in votes!") + .to_owned(), + ); + voting_power_post += voting_powers.get(validator).expect( + "voting powers map must have all validators from newly_seen_by", + ); + } + + let seen_post = if voting_power_post > FractionalVotingPower::TWO_THIRDS { + tracing::info!( + ?keys.prefix, + "Event has been seen by a quorum of validators", + ); + true + } else { + tracing::debug!( + ?keys.prefix, + "Event is not yet seen by a quorum of validators", + ); + false + }; + + Tally { + voting_power: voting_power_post, + seen_by: seen_by_post, + seen: seen_post, + } +} + +/// Validates that `post` is an updated version of `pre`, and returns keys which +/// changed. This function serves as a sort of validity predicate for this +/// native transaction, which is otherwise not checked by anything else. +fn validate_update( + keys: &vote_tallies::Keys, + pre: &Tally, + post: &Tally, +) -> Result { + let mut keys_changed = ChangedKeys::default(); + + let mut seen = false; + if pre.seen != post.seen { + // the only valid transition for `seen` is from `false` to `true` + if pre.seen || !post.seen { + return Err(eyre!( + "Tally seen changed from {:#?} to {:#?}", + &pre.seen, + &post.seen, + )); + } + keys_changed.insert(keys.seen()); + seen = true; + } + let pre_seen_by: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); + let post_seen_by: BTreeSet<_> = post.seen_by.keys().cloned().collect(); + + if pre_seen_by != post_seen_by { + // if seen_by changes, it must be a strict superset of the previous + // seen_by + if !post_seen_by.is_superset(&pre_seen_by) { + return Err(eyre!( + "Tally seen changed from {:#?} to {:#?}", + &pre_seen_by, + &post_seen_by, + )); + } + keys_changed.insert(keys.seen_by()); + } + + if pre.voting_power != post.voting_power { + // if voting_power changes, it must have increased + if pre.voting_power >= post.voting_power { + return Err(eyre!( + "Tally voting_power changed from {:#?} to {:#?}", + &pre.voting_power, + &post.voting_power, + )); + } + keys_changed.insert(keys.voting_power()); + } + + if post.voting_power > FractionalVotingPower::TWO_THIRDS + && !seen + && pre.voting_power >= post.voting_power + { + return Err(eyre!( + "Tally is not seen even though new voting_power is enough: {:#?}", + &post.voting_power, + )); + } + + Ok(keys_changed) } /// Deterministically constructs a [`Votes`] map from a set of validator From d65070b18c4009bb1627b0befcecd2a82e21a1ea Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 10 Nov 2022 12:40:01 +0000 Subject: [PATCH 1773/1995] Rename some parameters --- .../src/ledger/protocol/transactions/votes.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 7f5bffacc2..8d53ba6465 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -78,8 +78,8 @@ pub fn calculate_new( pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - voting_powers: &HashMap, - votes: &Votes, + vote_powers: &HashMap, + vote_heights: &BTreeMap, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -96,7 +96,8 @@ where seen_by, seen, }; - let tally_post = calculate_update(keys, &tally_pre, voting_powers, votes); + let tally_post = + calculate_update(keys, &tally_pre, vote_powers, vote_heights); let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; tracing::warn!( @@ -114,10 +115,10 @@ where fn calculate_update( keys: &vote_tallies::Keys, pre: &Tally, - voting_powers: &HashMap, - votes: &Votes, + vote_powers: &HashMap, + vote_heights: &BTreeMap, ) -> Tally { - let new_voters: BTreeSet
= voting_powers.keys().cloned().collect(); + let new_voters: BTreeSet
= vote_powers.keys().cloned().collect(); // For any event and validator, only the first vote by that validator for // that event counts, later votes we encounter here can just be ignored. We @@ -143,12 +144,12 @@ fn calculate_update( ); seen_by_post.insert( validator.to_owned(), - votes + vote_heights .get(validator) .expect("Validator must be in votes!") .to_owned(), ); - voting_power_post += voting_powers.get(validator).expect( + voting_power_post += vote_powers.get(validator).expect( "voting powers map must have all validators from newly_seen_by", ); } From 05bb9454b4591748a810f7514eccc1544fb720e1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 10 Nov 2022 13:29:28 +0000 Subject: [PATCH 1774/1995] Factor construct_fractional_voting_powers_by_address --- .../transactions/ethereum_events/mod.rs | 27 ++++++------------- .../src/ledger/protocol/transactions/utils.rs | 24 +++++++++++++++++ .../transactions/validator_set_update/mod.rs | 27 ++++++------------- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 731b56b591..f70df58eb2 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -10,7 +10,9 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils; +use crate::ledger::protocol::transactions::utils::{ + self, construct_fractional_voting_powers_by_address, +}; use crate::ledger::protocol::transactions::votes::{ self, calculate_new, calculate_updated, }; @@ -140,24 +142,11 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let mut fractional_voting_powers = HashMap::default(); - update.seen_by.iter().for_each(|(address, block_height)| { - let fract_voting_power = voting_powers - .get(&(address.to_owned(), block_height.to_owned())) - .unwrap(); - if let Some(already_present_fract_voting_power) = - fractional_voting_powers - .insert(address.to_owned(), fract_voting_power.to_owned()) - { - tracing::warn!( - ?address, - ?already_present_fract_voting_power, - new_fract_voting_power = ?fract_voting_power, - "Validator voted more than once on Ethereum event, \ - arbitrarily using later value" - ) - } - }); + let fractional_voting_powers = + construct_fractional_voting_powers_by_address( + &update.seen_by, + voting_powers, + ); let (vote_tracking, changed) = calculate_updated( storage, ð_msg_keys, diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 2ad56bd8b8..6b4c51b71d 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -18,6 +18,30 @@ pub(super) trait GetVoters { fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } +pub(super) fn construct_fractional_voting_powers_by_address( + vote_heights: &BTreeMap, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, +) -> HashMap { + let mut fractional_voting_powers = HashMap::default(); + vote_heights.iter().for_each(|(address, block_height)| { + let fract_voting_power = voting_powers + .get(&(address.clone(), block_height.to_owned())) + .unwrap(); + if let Some(already_present_fract_voting_power) = + fractional_voting_powers + .insert(address.clone(), fract_voting_power.to_owned()) + { + tracing::warn!( + ?address, + ?already_present_fract_voting_power, + new_fract_voting_power = ?fract_voting_power, + "Validator voted more than once, arbitrarily using later value" + ) + } + }); + fractional_voting_powers +} + /// Returns a map whose keys are addresses of validators and the block height at /// which they signed some arbitrary object, and whose values are the voting /// powers of these validators at the key's given block height. diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index ca577f7b6f..640ad692bd 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -6,7 +6,9 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils; +use crate::ledger::protocol::transactions::utils::{ + self, construct_fractional_voting_powers_by_address, +}; use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -110,24 +112,11 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let mut fractional_voting_powers = HashMap::default(); - seen_by.iter().for_each(|(address, block_height)| { - let fract_voting_power = voting_powers - .get(&(address.clone(), block_height.to_owned())) - .unwrap(); - if let Some(already_present_fract_voting_power) = - fractional_voting_powers - .insert(address.clone(), fract_voting_power.to_owned()) - { - tracing::warn!( - ?address, - ?already_present_fract_voting_power, - new_fract_voting_power = ?fract_voting_power, - "Validator voted more than once on validator set update, \ - arbitrarily using later value" - ) - } - }); + let fractional_voting_powers = + construct_fractional_voting_powers_by_address( + &seen_by, + &voting_powers, + ); let (tally, changed) = votes::calculate_updated( storage, &valset_upd_keys, From 4c5d19ed30711985c7e5007cddfbd5a1c875db66 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 10:29:33 +0000 Subject: [PATCH 1775/1995] Update apply_updates docstring --- .../src/ledger/protocol/transactions/ethereum_events/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index f70df58eb2..ccab422e6d 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -70,7 +70,9 @@ where }) } -/// Apply an Ethereum state update + act on any events which are confirmed +/// Apply votes to Ethereum events in storage and act on any events which are +/// confirmed. The `voting_powers` map must contain a voting power for all +/// `(Address, BlockHeight)`s that occur in any of the `updates`. pub(super) fn apply_updates( storage: &mut Storage, updates: HashSet, From 35fc5d09c0cc32c25ba105b933b7f110ef267f1b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 10:42:54 +0000 Subject: [PATCH 1776/1995] Update apply_update docstring --- shared/src/ledger/protocol/transactions/ethereum_events/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index ccab422e6d..8a3ef020da 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -116,7 +116,8 @@ where } /// Apply an [`EthMsgUpdate`] to storage. Returns any keys changed and whether -/// the event was newly seen. +/// the event was newly seen. The `voting_powers` map must contain a voting +/// power for all `(Address, BlockHeight)`s that occur in `update`. fn apply_update( storage: &mut Storage, update: EthMsgUpdate, From 88ffd77b4383168bf63110ac0ab4f6dc5cbbae67 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 11:09:12 +0000 Subject: [PATCH 1777/1995] Refactor filter_fractional_voting_powers_by_address --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 6 +++--- shared/src/ledger/protocol/transactions/utils.rs | 8 ++++---- .../protocol/transactions/validator_set_update/mod.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 8a3ef020da..2a4a42c5ab 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -11,7 +11,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{ - self, construct_fractional_voting_powers_by_address, + self, filter_fractional_voting_powers_by_address, }; use crate::ledger::protocol::transactions::votes::{ self, calculate_new, calculate_updated, @@ -146,8 +146,8 @@ where "Ethereum event already exists in storage", ); let fractional_voting_powers = - construct_fractional_voting_powers_by_address( - &update.seen_by, + filter_fractional_voting_powers_by_address( + update.seen_by.clone(), voting_powers, ); let (vote_tracking, changed) = calculate_updated( diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 6b4c51b71d..12f89e37e3 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -18,14 +18,14 @@ pub(super) trait GetVoters { fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } -pub(super) fn construct_fractional_voting_powers_by_address( - vote_heights: &BTreeMap, +pub(super) fn filter_fractional_voting_powers_by_address( + vote_heights: BTreeMap, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> HashMap { let mut fractional_voting_powers = HashMap::default(); - vote_heights.iter().for_each(|(address, block_height)| { + vote_heights.into_iter().for_each(|(address, block_height)| { let fract_voting_power = voting_powers - .get(&(address.clone(), block_height.to_owned())) + .get(&(address.clone(), block_height)) .unwrap(); if let Some(already_present_fract_voting_power) = fractional_voting_powers diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 640ad692bd..b23a1f54f7 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -7,7 +7,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{ - self, construct_fractional_voting_powers_by_address, + self, filter_fractional_voting_powers_by_address, }; use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; @@ -113,8 +113,8 @@ where "Validator set update votes already in storage", ); let fractional_voting_powers = - construct_fractional_voting_powers_by_address( - &seen_by, + filter_fractional_voting_powers_by_address( + seen_by.clone(), &voting_powers, ); let (tally, changed) = votes::calculate_updated( From 8d73afa66993ebf118d7be472850b61f3c62f9ec Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 11:11:35 +0000 Subject: [PATCH 1778/1995] refactor filter_fractional_voting_powers_by_address --- shared/src/ledger/protocol/transactions/utils.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 12f89e37e3..4013e352f0 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -3,6 +3,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; use itertools::Itertools; +use super::votes::Votes; use crate::ledger::pos::types::{VotingPower, WeightedValidator}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -18,15 +19,16 @@ pub(super) trait GetVoters { fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } +/// Constructs a map of validator [`Address`]es from `votes` to the relevant +/// [`FractionalVotingPower`] from `voting_powers` pub(super) fn filter_fractional_voting_powers_by_address( - vote_heights: BTreeMap, + votes: Votes, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, ) -> HashMap { let mut fractional_voting_powers = HashMap::default(); - vote_heights.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = voting_powers - .get(&(address.clone(), block_height)) - .unwrap(); + votes.into_iter().for_each(|(address, block_height)| { + let fract_voting_power = + voting_powers.get(&(address.clone(), block_height)).unwrap(); if let Some(already_present_fract_voting_power) = fractional_voting_powers .insert(address.clone(), fract_voting_power.to_owned()) From 9912b94266a60a6584391eb69a78c8edee9b275f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 11:24:13 +0000 Subject: [PATCH 1779/1995] Yet more refactoring of types --- .../transactions/ethereum_events/mod.rs | 18 +++---------- .../src/ledger/protocol/transactions/utils.rs | 23 +++++++++------- .../transactions/validator_set_update/mod.rs | 18 +++---------- .../src/ledger/protocol/transactions/votes.rs | 26 +++++++++++-------- 4 files changed, 36 insertions(+), 49 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 2a4a42c5ab..e6185ca905 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -10,9 +10,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils::{ - self, filter_fractional_voting_powers_by_address, -}; +use crate::ledger::protocol::transactions::utils::{self, construct_vote_info}; use crate::ledger::protocol::transactions::votes::{ self, calculate_new, calculate_updated, }; @@ -145,17 +143,9 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let fractional_voting_powers = - filter_fractional_voting_powers_by_address( - update.seen_by.clone(), - voting_powers, - ); - let (vote_tracking, changed) = calculate_updated( - storage, - ð_msg_keys, - &fractional_voting_powers, - &update.seen_by, - )?; + let voters = construct_vote_info(update.seen_by.clone(), voting_powers); + let (vote_tracking, changed) = + calculate_updated(storage, ð_msg_keys, &voters)?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 4013e352f0..68bf8598ad 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -20,30 +20,33 @@ pub(super) trait GetVoters { } /// Constructs a map of validator [`Address`]es from `votes` to the relevant -/// [`FractionalVotingPower`] from `voting_powers` -pub(super) fn filter_fractional_voting_powers_by_address( +/// [`BlockHeight`] and also [`FractionalVotingPower`] from `voting_powers` +pub(super) fn construct_vote_info( votes: Votes, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> HashMap { - let mut fractional_voting_powers = HashMap::default(); +) -> HashMap { + let mut map = HashMap::default(); votes.into_iter().for_each(|(address, block_height)| { let fract_voting_power = voting_powers.get(&(address.clone(), block_height)).unwrap(); - if let Some(already_present_fract_voting_power) = - fractional_voting_powers - .insert(address.clone(), fract_voting_power.to_owned()) - { + if let Some(( + already_present_block_height, + already_present_fract_voting_power, + )) = map.insert( + address.clone(), + (block_height, fract_voting_power.to_owned()), + ) { tracing::warn!( ?address, + ?already_present_block_height, ?already_present_fract_voting_power, new_fract_voting_power = ?fract_voting_power, "Validator voted more than once, arbitrarily using later value" ) } }); - fractional_voting_powers + map } - /// Returns a map whose keys are addresses of validators and the block height at /// which they signed some arbitrary object, and whose values are the voting /// powers of these validators at the key's given block height. diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index b23a1f54f7..8504cae277 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -6,9 +6,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils::{ - self, filter_fractional_voting_powers_by_address, -}; +use crate::ledger::protocol::transactions::utils::{self, construct_vote_info}; use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -112,17 +110,9 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let fractional_voting_powers = - filter_fractional_voting_powers_by_address( - seen_by.clone(), - &voting_powers, - ); - let (tally, changed) = votes::calculate_updated( - storage, - &valset_upd_keys, - &fractional_voting_powers, - &seen_by, - )?; + let voters = construct_vote_info(seen_by.clone(), &voting_powers); + let (tally, changed) = + votes::calculate_updated(storage, &valset_upd_keys, &voters)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 8d53ba6465..eaf972242c 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -73,13 +73,14 @@ pub fn calculate_new( }) } +type VoteInfo = HashMap; + /// Calculate an updated [`Tally`] based on one that is in storage under `keys`, /// with some new `voters`. pub fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - vote_powers: &HashMap, - vote_heights: &BTreeMap, + vote_info: &VoteInfo, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -96,8 +97,7 @@ where seen_by, seen, }; - let tally_post = - calculate_update(keys, &tally_pre, vote_powers, vote_heights); + let tally_post = calculate_update(keys, &tally_pre, &vote_info); let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; tracing::warn!( @@ -115,10 +115,9 @@ where fn calculate_update( keys: &vote_tallies::Keys, pre: &Tally, - vote_powers: &HashMap, - vote_heights: &BTreeMap, + vote_info: &VoteInfo, ) -> Tally { - let new_voters: BTreeSet
= vote_powers.keys().cloned().collect(); + let new_voters: BTreeSet
= vote_info.keys().cloned().collect(); // For any event and validator, only the first vote by that validator for // that event counts, later votes we encounter here can just be ignored. We @@ -144,14 +143,19 @@ fn calculate_update( ); seen_by_post.insert( validator.to_owned(), - vote_heights + vote_info .get(validator) .expect("Validator must be in votes!") + .0 .to_owned(), ); - voting_power_post += vote_powers.get(validator).expect( - "voting powers map must have all validators from newly_seen_by", - ); + voting_power_post += vote_info + .get(validator) + .expect( + "voting powers map must have all validators from newly_seen_by", + ) + .1 + .to_owned(); } let seen_post = if voting_power_post > FractionalVotingPower::TWO_THIRDS { From bc238c770a410ddea6ed0ad832c7a1a3d5ce7133 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 11:24:43 +0000 Subject: [PATCH 1780/1995] ... --- shared/src/ledger/protocol/transactions/votes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index eaf972242c..d51130ed2b 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -97,7 +97,7 @@ where seen_by, seen, }; - let tally_post = calculate_update(keys, &tally_pre, &vote_info); + let tally_post = calculate_update(keys, &tally_pre, vote_info); let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; tracing::warn!( From c7b558622f80b5bb6ad8bf3fae59bfb7af8a3349 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 11 Nov 2022 12:53:43 +0000 Subject: [PATCH 1781/1995] Use an explicit VoteInfo type --- .../transactions/ethereum_events/mod.rs | 6 +- .../src/ledger/protocol/transactions/utils.rs | 29 -------- .../transactions/validator_set_update/mod.rs | 6 +- .../src/ledger/protocol/transactions/votes.rs | 69 ++++++++++++++----- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index e6185ca905..be32144b75 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -10,9 +10,9 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils::{self, construct_vote_info}; +use crate::ledger::protocol::transactions::utils::{self}; use crate::ledger::protocol::transactions::votes::{ - self, calculate_new, calculate_updated, + calculate_new, calculate_updated, write, VoteInfo, }; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -143,7 +143,7 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let voters = construct_vote_info(update.seen_by.clone(), voting_powers); + let voters = VoteInfo::new(update.seen_by.clone(), voting_powers); let (vote_tracking, changed) = calculate_updated(storage, ð_msg_keys, &voters)?; let confirmed = diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 68bf8598ad..2ad56bd8b8 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -3,7 +3,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; use itertools::Itertools; -use super::votes::Votes; use crate::ledger::pos::types::{VotingPower, WeightedValidator}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -19,34 +18,6 @@ pub(super) trait GetVoters { fn get_voters(&self) -> HashSet<(Address, BlockHeight)>; } -/// Constructs a map of validator [`Address`]es from `votes` to the relevant -/// [`BlockHeight`] and also [`FractionalVotingPower`] from `voting_powers` -pub(super) fn construct_vote_info( - votes: Votes, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, -) -> HashMap { - let mut map = HashMap::default(); - votes.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = - voting_powers.get(&(address.clone(), block_height)).unwrap(); - if let Some(( - already_present_block_height, - already_present_fract_voting_power, - )) = map.insert( - address.clone(), - (block_height, fract_voting_power.to_owned()), - ) { - tracing::warn!( - ?address, - ?already_present_block_height, - ?already_present_fract_voting_power, - new_fract_voting_power = ?fract_voting_power, - "Validator voted more than once, arbitrarily using later value" - ) - } - }); - map -} /// Returns a map whose keys are addresses of validators and the block height at /// which they signed some arbitrary object, and whose values are the voting /// powers of these validators at the key's given block height. diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 8504cae277..8e8b097a25 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -6,8 +6,8 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::protocol::transactions::utils::{self, construct_vote_info}; -use crate::ledger::protocol::transactions::votes::{self, Votes}; +use crate::ledger::protocol::transactions::utils; +use crate::ledger::protocol::transactions::votes::{self, VoteInfo, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::ledger::storage_api::queries::QueriesExt; @@ -110,7 +110,7 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let voters = construct_vote_info(seen_by.clone(), &voting_powers); + let voters = VoteInfo::new(seen_by.clone(), &voting_powers); let (tally, changed) = votes::calculate_updated(storage, &valset_upd_keys, &voters)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index d51130ed2b..05ed348309 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -73,11 +73,56 @@ pub fn calculate_new( }) } -type VoteInfo = HashMap; +pub(super) struct VoteInfo { + inner: HashMap, +} + +impl VoteInfo { + pub fn new( + votes: Votes, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + ) -> Self { + let mut inner = HashMap::default(); + votes.into_iter().for_each(|(address, block_height)| { + let fract_voting_power = + voting_powers.get(&(address.clone(), block_height)).unwrap(); + if let Some(( + already_present_block_height, + already_present_fract_voting_power, + )) = inner.insert( + address.clone(), + (block_height, fract_voting_power.to_owned()), + ) { + tracing::warn!( + ?address, + ?already_present_block_height, + ?already_present_fract_voting_power, + new_fract_voting_power = ?fract_voting_power, + "Validator voted more than once, arbitrarily using later value" + ) + } + }); + Self { inner } + } + + pub fn voters(&self) -> BTreeSet
{ + self.inner.keys().cloned().collect() + } + + pub fn get_vote_height(&self, validator: &Address) -> BlockHeight { + // TODO: don't unwrap + self.inner.get(validator).unwrap().0 + } + + pub fn get_vote_power(&self, validator: &Address) -> FractionalVotingPower { + // TODO: don't unwrap + self.inner.get(validator).unwrap().1.clone() + } +} /// Calculate an updated [`Tally`] based on one that is in storage under `keys`, /// with some new `voters`. -pub fn calculate_updated( +pub(super) fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, vote_info: &VoteInfo, @@ -117,7 +162,7 @@ fn calculate_update( pre: &Tally, vote_info: &VoteInfo, ) -> Tally { - let new_voters: BTreeSet
= vote_info.keys().cloned().collect(); + let new_voters: BTreeSet
= vote_info.voters(); // For any event and validator, only the first vote by that validator for // that event counts, later votes we encounter here can just be ignored. We @@ -141,21 +186,9 @@ fn calculate_update( ?validator, "Recording validator as having voted for this event" ); - seen_by_post.insert( - validator.to_owned(), - vote_info - .get(validator) - .expect("Validator must be in votes!") - .0 - .to_owned(), - ); - voting_power_post += vote_info - .get(validator) - .expect( - "voting powers map must have all validators from newly_seen_by", - ) - .1 - .to_owned(); + seen_by_post + .insert(validator.to_owned(), vote_info.get_vote_height(validator)); + voting_power_post += vote_info.get_vote_power(validator); } let seen_post = if voting_power_post > FractionalVotingPower::TWO_THIRDS { From a25b14349748df88ea5b0993f265744160abc7e0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 11:55:50 +0000 Subject: [PATCH 1782/1995] Rename voters -> vote_info --- .../src/ledger/protocol/transactions/ethereum_events/mod.rs | 4 ++-- .../ledger/protocol/transactions/validator_set_update/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index be32144b75..2102053b83 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -143,9 +143,9 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let voters = VoteInfo::new(update.seen_by.clone(), voting_powers); + let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers); let (vote_tracking, changed) = - calculate_updated(storage, ð_msg_keys, &voters)?; + calculate_updated(storage, ð_msg_keys, &vote_info)?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 8e8b097a25..282ad60028 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -110,9 +110,9 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let voters = VoteInfo::new(seen_by.clone(), &voting_powers); + let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers); let (tally, changed) = - votes::calculate_updated(storage, &valset_upd_keys, &voters)?; + votes::calculate_updated(storage, &valset_upd_keys, &vote_info)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; From 7adf34adf267c62c0236039cd02d280a76c3894f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 11:57:16 +0000 Subject: [PATCH 1783/1995] Update calculate_updated docstring --- shared/src/ledger/protocol/transactions/votes.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 05ed348309..d437f3617d 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -154,9 +154,7 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// validators which have seen it. `voting_powers` should map validators who -/// have newly seen the event to their fractional voting power at a block height -/// at which they saw the event. +/// validators which have seen it. fn calculate_update( keys: &vote_tallies::Keys, pre: &Tally, From dad884214a9b213755e5f6f558478ddc92dd7f2b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 11:58:47 +0000 Subject: [PATCH 1784/1995] Add TODO re. needless parameter --- shared/src/ledger/protocol/transactions/votes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index d437f3617d..ccc42f9e38 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -160,6 +160,7 @@ fn calculate_update( pre: &Tally, vote_info: &VoteInfo, ) -> Tally { + // TODO: no need to accept `keys` - it is just accepted for logging, is there a better way? let new_voters: BTreeSet
= vote_info.voters(); // For any event and validator, only the first vote by that validator for From 3cc088d4d065fdb18aa8ffb96897891a2554e86c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:02:43 +0000 Subject: [PATCH 1785/1995] Remove no longer necessary check for duplicate insertion --- shared/src/ledger/protocol/transactions/votes.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index ccc42f9e38..04111f7e5d 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -86,21 +86,10 @@ impl VoteInfo { votes.into_iter().for_each(|(address, block_height)| { let fract_voting_power = voting_powers.get(&(address.clone(), block_height)).unwrap(); - if let Some(( - already_present_block_height, - already_present_fract_voting_power, - )) = inner.insert( + _ = inner.insert( address.clone(), (block_height, fract_voting_power.to_owned()), - ) { - tracing::warn!( - ?address, - ?already_present_block_height, - ?already_present_fract_voting_power, - new_fract_voting_power = ?fract_voting_power, - "Validator voted more than once, arbitrarily using later value" - ) - } + ); }); Self { inner } } From 2526c83cb30b938efca080099aed9dcc3a529d0e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:10:38 +0000 Subject: [PATCH 1786/1995] Fixing up calculate_update --- .../src/ledger/protocol/transactions/votes.rs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 04111f7e5d..c0cb278987 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -134,7 +134,19 @@ where let tally_post = calculate_update(keys, &tally_pre, vote_info); let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; - tracing::warn!( + if tally_post.seen { + tracing::info!( + ?keys.prefix, + "Event has been seen by a quorum of validators", + ); + } else { + tracing::debug!( + ?keys.prefix, + "Event is not yet seen by a quorum of validators", + ); + }; + + tracing::debug!( ?tally_pre, ?tally_post, "Calculated and validated vote tracking updates", @@ -150,15 +162,16 @@ fn calculate_update( vote_info: &VoteInfo, ) -> Tally { // TODO: no need to accept `keys` - it is just accepted for logging, is there a better way? - let new_voters: BTreeSet
= vote_info.voters(); + + let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); + let new_voters: BTreeSet<_> = previous_voters.difference(&vote_info.voters()).cloned().collect(); // For any event and validator, only the first vote by that validator for // that event counts, later votes we encounter here can just be ignored. We // can warn here when we encounter duplicate votes but these are // reasonably likely to occur so this perhaps shouldn't be a warning unless - // it is happening a lot - let already_voted: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - for validator in already_voted.intersection(&new_voters) { + // it is happening a lot. + for validator in previous_voters.intersection(&new_voters) { tracing::warn!( ?keys.prefix, ?validator, @@ -168,30 +181,18 @@ fn calculate_update( let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for validator in new_voters.difference(&already_voted) { + for validator in new_voters { tracing::info!( ?keys.prefix, ?validator, "Recording validator as having voted for this event" ); - seen_by_post - .insert(validator.to_owned(), vote_info.get_vote_height(validator)); - voting_power_post += vote_info.get_vote_power(validator); + _ = seen_by_post + .insert(validator.to_owned(), vote_info.get_vote_height(&validator)); + voting_power_post += vote_info.get_vote_power(&validator); } - let seen_post = if voting_power_post > FractionalVotingPower::TWO_THIRDS { - tracing::info!( - ?keys.prefix, - "Event has been seen by a quorum of validators", - ); - true - } else { - tracing::debug!( - ?keys.prefix, - "Event is not yet seen by a quorum of validators", - ); - false - }; + let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; Tally { voting_power: voting_power_post, From b15c1f3c1c7bca41c12a8fa7ac54cf3a56885cd6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:16:04 +0000 Subject: [PATCH 1787/1995] Fix working out of duplicate voters --- shared/src/ledger/protocol/transactions/votes.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index c0cb278987..fadc255c8a 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -165,13 +165,14 @@ fn calculate_update( let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let new_voters: BTreeSet<_> = previous_voters.difference(&vote_info.voters()).cloned().collect(); + let duplicate_voters: BTreeSet<_> = previous_voters.intersection(&vote_info.voters()).cloned().collect(); // For any event and validator, only the first vote by that validator for // that event counts, later votes we encounter here can just be ignored. We // can warn here when we encounter duplicate votes but these are // reasonably likely to occur so this perhaps shouldn't be a warning unless // it is happening a lot. - for validator in previous_voters.intersection(&new_voters) { + for validator in duplicate_voters { tracing::warn!( ?keys.prefix, ?validator, From 0f1ae98c78b4b4995bc26800dc5f3e339edd3338 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:30:46 +0000 Subject: [PATCH 1788/1995] Simplify calculate_update --- .../src/ledger/protocol/transactions/votes.rs | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index fadc255c8a..3e05d4b88b 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -131,7 +131,12 @@ where seen_by, seen, }; - let tally_post = calculate_update(keys, &tally_pre, vote_info); + tracing::info!( + ?keys.prefix, + ?vote_info.voters(), + "Recording validators as having voted for this event" + ); + let tally_post = calculate_update(&tally_pre, vote_info)?; let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; if tally_post.seen { @@ -155,39 +160,22 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// validators which have seen it. +/// voters from `vote_info`. Returns an error if any new voters have already voted previously. fn calculate_update( - keys: &vote_tallies::Keys, pre: &Tally, vote_info: &VoteInfo, -) -> Tally { - // TODO: no need to accept `keys` - it is just accepted for logging, is there a better way? - +) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let new_voters: BTreeSet<_> = previous_voters.difference(&vote_info.voters()).cloned().collect(); - let duplicate_voters: BTreeSet<_> = previous_voters.intersection(&vote_info.voters()).cloned().collect(); - - // For any event and validator, only the first vote by that validator for - // that event counts, later votes we encounter here can just be ignored. We - // can warn here when we encounter duplicate votes but these are - // reasonably likely to occur so this perhaps shouldn't be a warning unless - // it is happening a lot. - for validator in duplicate_voters { - tracing::warn!( - ?keys.prefix, - ?validator, - "Encountered duplicate vote for an event by a validator, ignoring" - ); + let new_voters = vote_info.voters(); + let duplicate_voters: BTreeSet<_> = previous_voters.intersection(&new_voters).collect(); + if !duplicate_voters.is_empty() { + // TODO: this is a programmer error and should never happen + return Err(eyre!("Duplicate voters found - {}", duplicate_voters)); } let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); for validator in new_voters { - tracing::info!( - ?keys.prefix, - ?validator, - "Recording validator as having voted for this event" - ); _ = seen_by_post .insert(validator.to_owned(), vote_info.get_vote_height(&validator)); voting_power_post += vote_info.get_vote_power(&validator); @@ -195,11 +183,11 @@ fn calculate_update( let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; - Tally { + Ok(Tally { voting_power: voting_power_post, seen_by: seen_by_post, seen: seen_post, - } + }) } /// Validates that `post` is an updated version of `pre`, and returns keys which From f1ba92dbf86b327b7c51813a98cf4cb09c65e2eb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:37:59 +0000 Subject: [PATCH 1789/1995] Split out read fn --- shared/src/ledger/protocol/transactions/votes.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 3e05d4b88b..f694f49eb9 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -121,21 +121,12 @@ where H: 'static + StorageHasher + Sync, T: BorshDeserialize, { - let seen: bool = read::value(store, &keys.seen())?; - let seen_by: Votes = read::value(store, &keys.seen_by())?; - let voting_power: FractionalVotingPower = - read::value(store, &keys.voting_power())?; - - let tally_pre = Tally { - voting_power, - seen_by, - seen, - }; tracing::info!( ?keys.prefix, ?vote_info.voters(), "Recording validators as having voted for this event" ); + let tally_pre = read(store, keys)?; let tally_post = calculate_update(&tally_pre, vote_info)?; let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; From 56cb31f1a241e15662d84e0b4f4d5f1ea7b461f5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:47:54 +0000 Subject: [PATCH 1790/1995] Replace unwraps with expects --- shared/src/ledger/protocol/transactions/votes.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index f694f49eb9..2be42dc09e 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -98,14 +98,12 @@ impl VoteInfo { self.inner.keys().cloned().collect() } - pub fn get_vote_height(&self, validator: &Address) -> BlockHeight { - // TODO: don't unwrap - self.inner.get(validator).unwrap().0 + pub fn get_vote_height(&self, validator: &Address) -> Option { + self.inner.get(validator).map(|(height, _)| *height) } - pub fn get_vote_power(&self, validator: &Address) -> FractionalVotingPower { - // TODO: don't unwrap - self.inner.get(validator).unwrap().1.clone() + pub fn get_vote_power(&self, validator: &Address) -> Option { + self.inner.get(validator).map(|(_, voting_power)| *voting_power) } } @@ -168,8 +166,8 @@ fn calculate_update( let mut seen_by_post = pre.seen_by.clone(); for validator in new_voters { _ = seen_by_post - .insert(validator.to_owned(), vote_info.get_vote_height(&validator)); - voting_power_post += vote_info.get_vote_power(&validator); + .insert(validator.to_owned(), vote_info.get_vote_height(&validator).expect("We can always get the vote height for a voter")); + voting_power_post += vote_info.get_vote_power(&validator).expect("We can always get the voting power for a voter"); } let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; From 67a4b447c3960856fb53cb2d7f7fe3b6442812cf Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 12:59:15 +0000 Subject: [PATCH 1791/1995] Fix various compile errors --- shared/src/ledger/protocol/transactions/votes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 2be42dc09e..ef2c2d04cc 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -103,7 +103,7 @@ impl VoteInfo { } pub fn get_vote_power(&self, validator: &Address) -> Option { - self.inner.get(validator).map(|(_, voting_power)| *voting_power) + self.inner.get(validator).map(|(_, voting_power)| voting_power.clone()) } } @@ -121,7 +121,7 @@ where { tracing::info!( ?keys.prefix, - ?vote_info.voters(), + validators = ?vote_info.voters(), "Recording validators as having voted for this event" ); let tally_pre = read(store, keys)?; @@ -150,7 +150,7 @@ where /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new /// voters from `vote_info`. Returns an error if any new voters have already voted previously. -fn calculate_update( +fn calculate_update( pre: &Tally, vote_info: &VoteInfo, ) -> Result { @@ -159,7 +159,7 @@ fn calculate_update( let duplicate_voters: BTreeSet<_> = previous_voters.intersection(&new_voters).collect(); if !duplicate_voters.is_empty() { // TODO: this is a programmer error and should never happen - return Err(eyre!("Duplicate voters found - {}", duplicate_voters)); + return Err(eyre!("Duplicate voters found - {:?}", duplicate_voters)); } let mut voting_power_post = pre.voting_power.clone(); From 657e4fe13ba13a8c36fd9e5bd869693755cd86ba Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 13:13:35 +0000 Subject: [PATCH 1792/1995] Use an iterator to avoid expects --- .../src/ledger/protocol/transactions/votes.rs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index ef2c2d04cc..0ff90bf4d4 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -98,12 +98,15 @@ impl VoteInfo { self.inner.keys().cloned().collect() } - pub fn get_vote_height(&self, validator: &Address) -> Option { - self.inner.get(validator).map(|(height, _)| *height) - } - - pub fn get_vote_power(&self, validator: &Address) -> Option { - self.inner.get(validator).map(|(_, voting_power)| voting_power.clone()) + pub fn iter( + &self, + ) -> BTreeSet<(Address, BlockHeight, FractionalVotingPower)> { + self.inner + .iter() + .map(|(address, (block_height, fract_voting_power))| { + (address.clone(), *block_height, fract_voting_power.clone()) + }) + .collect() } } @@ -149,14 +152,13 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// voters from `vote_info`. Returns an error if any new voters have already voted previously. -fn calculate_update( - pre: &Tally, - vote_info: &VoteInfo, -) -> Result { +/// voters from `vote_info`. Returns an error if any new voters have already +/// voted previously. +fn calculate_update(pre: &Tally, vote_info: &VoteInfo) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let new_voters = vote_info.voters(); - let duplicate_voters: BTreeSet<_> = previous_voters.intersection(&new_voters).collect(); + let duplicate_voters: BTreeSet<_> = + previous_voters.intersection(&new_voters).collect(); if !duplicate_voters.is_empty() { // TODO: this is a programmer error and should never happen return Err(eyre!("Duplicate voters found - {:?}", duplicate_voters)); @@ -164,10 +166,9 @@ fn calculate_update( let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for validator in new_voters { - _ = seen_by_post - .insert(validator.to_owned(), vote_info.get_vote_height(&validator).expect("We can always get the vote height for a voter")); - voting_power_post += vote_info.get_vote_power(&validator).expect("We can always get the voting power for a voter"); + for (validator, vote_height, voting_power) in vote_info.iter() { + _ = seen_by_post.insert(validator, vote_height); + voting_power_post += voting_power; } let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; From ec9fe71cd13d3297ed31e8d12cc0d552e608a855 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 14 Nov 2022 13:26:30 +0000 Subject: [PATCH 1793/1995] Rename calculate_update -> calculate_tally_post --- shared/src/ledger/protocol/transactions/votes.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 0ff90bf4d4..b4360961eb 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -128,7 +128,7 @@ where "Recording validators as having voted for this event" ); let tally_pre = read(store, keys)?; - let tally_post = calculate_update(&tally_pre, vote_info)?; + let tally_post = calculate_tally_post(&tally_pre, vote_info)?; let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; if tally_post.seen { @@ -154,7 +154,7 @@ where /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new /// voters from `vote_info`. Returns an error if any new voters have already /// voted previously. -fn calculate_update(pre: &Tally, vote_info: &VoteInfo) -> Result { +fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let new_voters = vote_info.voters(); let duplicate_voters: BTreeSet<_> = From 1e7ba8ed6af47f1b2633516235cb6cb038f0999e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 21 Nov 2022 11:00:56 +0000 Subject: [PATCH 1794/1995] Fix up for rebase --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 9 +++++++-- shared/src/ledger/protocol/transactions/votes.rs | 8 +++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 2102053b83..642f688bcb 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -12,7 +12,7 @@ use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{self}; use crate::ledger::protocol::transactions::votes::{ - calculate_new, calculate_updated, write, VoteInfo, + self, calculate_new, calculate_updated, VoteInfo, }; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -151,7 +151,12 @@ where (vote_tracking, changed, confirmed) }; - write(storage, ð_msg_keys, &update.body, &vote_tracking)?; + votes::storage::write( + storage, + ð_msg_keys, + &update.body, + &vote_tracking, + )?; Ok((changed, confirmed)) } diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index b4360961eb..08851ada83 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -86,10 +86,8 @@ impl VoteInfo { votes.into_iter().for_each(|(address, block_height)| { let fract_voting_power = voting_powers.get(&(address.clone(), block_height)).unwrap(); - _ = inner.insert( - address.clone(), - (block_height, fract_voting_power.to_owned()), - ); + _ = inner + .insert(address, (block_height, fract_voting_power.to_owned())); }); Self { inner } } @@ -127,7 +125,7 @@ where validators = ?vote_info.voters(), "Recording validators as having voted for this event" ); - let tally_pre = read(store, keys)?; + let tally_pre = storage::read(store, keys)?; let tally_post = calculate_tally_post(&tally_pre, vote_info)?; let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; From 4a4864a9d9841a92ce15724c2730114769f47c57 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 22 Nov 2022 14:46:12 +0000 Subject: [PATCH 1795/1995] Move update logic to own module --- .../transactions/ethereum_events/mod.rs | 5 +- .../transactions/validator_set_update/mod.rs | 7 +- .../src/ledger/protocol/transactions/votes.rs | 173 +---------------- .../protocol/transactions/votes/update.rs | 181 ++++++++++++++++++ 4 files changed, 190 insertions(+), 176 deletions(-) create mode 100644 shared/src/ledger/protocol/transactions/votes/update.rs diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 642f688bcb..605bb16db9 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -11,9 +11,10 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{self}; -use crate::ledger::protocol::transactions::votes::{ - self, calculate_new, calculate_updated, VoteInfo, +use crate::ledger::protocol::transactions::votes::update::{ + calculate_updated, VoteInfo, }; +use crate::ledger::protocol::transactions::votes::{self, calculate_new}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 282ad60028..4198f074ac 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -7,7 +7,10 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; -use crate::ledger::protocol::transactions::votes::{self, VoteInfo, Votes}; +use crate::ledger::protocol::transactions::votes::update::{ + calculate_updated, VoteInfo, +}; +use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; use crate::ledger::storage_api::queries::QueriesExt; @@ -112,7 +115,7 @@ where ); let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers); let (tally, changed) = - votes::calculate_updated(storage, &valset_upd_keys, &vote_info)?; + calculate_updated(storage, &valset_upd_keys, &vote_info)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/shared/src/ledger/protocol/transactions/votes.rs index 08851ada83..98dd689cee 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/shared/src/ledger/protocol/transactions/votes.rs @@ -7,15 +7,13 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; use super::ChangedKeys; -use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::read; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; pub(super) mod storage; +pub(super) mod update; /// The addresses of validators that voted for something, and the block /// heights at which they voted. We use a [`BTreeMap`] to enforce that a @@ -73,175 +71,6 @@ pub fn calculate_new( }) } -pub(super) struct VoteInfo { - inner: HashMap, -} - -impl VoteInfo { - pub fn new( - votes: Votes, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, - ) -> Self { - let mut inner = HashMap::default(); - votes.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = - voting_powers.get(&(address.clone(), block_height)).unwrap(); - _ = inner - .insert(address, (block_height, fract_voting_power.to_owned())); - }); - Self { inner } - } - - pub fn voters(&self) -> BTreeSet
{ - self.inner.keys().cloned().collect() - } - - pub fn iter( - &self, - ) -> BTreeSet<(Address, BlockHeight, FractionalVotingPower)> { - self.inner - .iter() - .map(|(address, (block_height, fract_voting_power))| { - (address.clone(), *block_height, fract_voting_power.clone()) - }) - .collect() - } -} - -/// Calculate an updated [`Tally`] based on one that is in storage under `keys`, -/// with some new `voters`. -pub(super) fn calculate_updated( - store: &mut Storage, - keys: &vote_tallies::Keys, - vote_info: &VoteInfo, -) -> Result<(Tally, ChangedKeys)> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - T: BorshDeserialize, -{ - tracing::info!( - ?keys.prefix, - validators = ?vote_info.voters(), - "Recording validators as having voted for this event" - ); - let tally_pre = storage::read(store, keys)?; - let tally_post = calculate_tally_post(&tally_pre, vote_info)?; - let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; - - if tally_post.seen { - tracing::info!( - ?keys.prefix, - "Event has been seen by a quorum of validators", - ); - } else { - tracing::debug!( - ?keys.prefix, - "Event is not yet seen by a quorum of validators", - ); - }; - - tracing::debug!( - ?tally_pre, - ?tally_post, - "Calculated and validated vote tracking updates", - ); - Ok((tally_post, changed_keys)) -} - -/// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// voters from `vote_info`. Returns an error if any new voters have already -/// voted previously. -fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { - let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let new_voters = vote_info.voters(); - let duplicate_voters: BTreeSet<_> = - previous_voters.intersection(&new_voters).collect(); - if !duplicate_voters.is_empty() { - // TODO: this is a programmer error and should never happen - return Err(eyre!("Duplicate voters found - {:?}", duplicate_voters)); - } - - let mut voting_power_post = pre.voting_power.clone(); - let mut seen_by_post = pre.seen_by.clone(); - for (validator, vote_height, voting_power) in vote_info.iter() { - _ = seen_by_post.insert(validator, vote_height); - voting_power_post += voting_power; - } - - let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; - - Ok(Tally { - voting_power: voting_power_post, - seen_by: seen_by_post, - seen: seen_post, - }) -} - -/// Validates that `post` is an updated version of `pre`, and returns keys which -/// changed. This function serves as a sort of validity predicate for this -/// native transaction, which is otherwise not checked by anything else. -fn validate_update( - keys: &vote_tallies::Keys, - pre: &Tally, - post: &Tally, -) -> Result { - let mut keys_changed = ChangedKeys::default(); - - let mut seen = false; - if pre.seen != post.seen { - // the only valid transition for `seen` is from `false` to `true` - if pre.seen || !post.seen { - return Err(eyre!( - "Tally seen changed from {:#?} to {:#?}", - &pre.seen, - &post.seen, - )); - } - keys_changed.insert(keys.seen()); - seen = true; - } - let pre_seen_by: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let post_seen_by: BTreeSet<_> = post.seen_by.keys().cloned().collect(); - - if pre_seen_by != post_seen_by { - // if seen_by changes, it must be a strict superset of the previous - // seen_by - if !post_seen_by.is_superset(&pre_seen_by) { - return Err(eyre!( - "Tally seen changed from {:#?} to {:#?}", - &pre_seen_by, - &post_seen_by, - )); - } - keys_changed.insert(keys.seen_by()); - } - - if pre.voting_power != post.voting_power { - // if voting_power changes, it must have increased - if pre.voting_power >= post.voting_power { - return Err(eyre!( - "Tally voting_power changed from {:#?} to {:#?}", - &pre.voting_power, - &post.voting_power, - )); - } - keys_changed.insert(keys.voting_power()); - } - - if post.voting_power > FractionalVotingPower::TWO_THIRDS - && !seen - && pre.voting_power >= post.voting_power - { - return Err(eyre!( - "Tally is not seen even though new voting_power is enough: {:#?}", - &post.voting_power, - )); - } - - Ok(keys_changed) -} - /// Deterministically constructs a [`Votes`] map from a set of validator /// addresses and the block heights they signed something at. We arbitrarily /// take the earliest block height for each validator address encountered. diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs new file mode 100644 index 0000000000..1982247a1c --- /dev/null +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -0,0 +1,181 @@ +use std::collections::{BTreeSet, HashMap}; + +use borsh::BorshDeserialize; +use eyre::{eyre, Result}; + +use super::{ChangedKeys, Tally, Votes}; +use crate::ledger::eth_bridge::storage::vote_tallies; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, Storage, DB}; +use crate::types::address::Address; +use crate::types::storage::BlockHeight; +use crate::types::voting_power::FractionalVotingPower; + +pub(in super::super) struct VoteInfo { + inner: HashMap, +} + +impl VoteInfo { + pub fn new( + votes: Votes, + voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + ) -> Self { + let mut inner = HashMap::default(); + votes.into_iter().for_each(|(address, block_height)| { + let fract_voting_power = + voting_powers.get(&(address.clone(), block_height)).unwrap(); + _ = inner + .insert(address, (block_height, fract_voting_power.to_owned())); + }); + Self { inner } + } + + pub fn voters(&self) -> BTreeSet
{ + self.inner.keys().cloned().collect() + } + + pub fn iter( + &self, + ) -> BTreeSet<(Address, BlockHeight, FractionalVotingPower)> { + self.inner + .iter() + .map(|(address, (block_height, fract_voting_power))| { + (address.clone(), *block_height, fract_voting_power.clone()) + }) + .collect() + } +} + +/// Calculate an updated [`Tally`] based on one that is in storage under `keys`, +/// with some new `voters`. +pub(in super::super) fn calculate_updated( + store: &mut Storage, + keys: &vote_tallies::Keys, + vote_info: &VoteInfo, +) -> Result<(Tally, ChangedKeys)> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, + T: BorshDeserialize, +{ + tracing::info!( + ?keys.prefix, + validators = ?vote_info.voters(), + "Recording validators as having voted for this event" + ); + let tally_pre = super::storage::read(store, keys)?; + let tally_post = calculate_tally_post(&tally_pre, vote_info)?; + let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; + + if tally_post.seen { + tracing::info!( + ?keys.prefix, + "Event has been seen by a quorum of validators", + ); + } else { + tracing::debug!( + ?keys.prefix, + "Event is not yet seen by a quorum of validators", + ); + }; + + tracing::debug!( + ?tally_pre, + ?tally_post, + "Calculated and validated vote tracking updates", + ); + Ok((tally_post, changed_keys)) +} + +/// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new +/// voters from `vote_info`. Returns an error if any new voters have already +/// voted previously. +fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { + let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); + let new_voters = vote_info.voters(); + let duplicate_voters: BTreeSet<_> = + previous_voters.intersection(&new_voters).collect(); + if !duplicate_voters.is_empty() { + // TODO: this is a programmer error and should never happen + return Err(eyre!("Duplicate voters found - {:?}", duplicate_voters)); + } + + let mut voting_power_post = pre.voting_power.clone(); + let mut seen_by_post = pre.seen_by.clone(); + for (validator, vote_height, voting_power) in vote_info.iter() { + _ = seen_by_post.insert(validator, vote_height); + voting_power_post += voting_power; + } + + let seen_post = voting_power_post > FractionalVotingPower::TWO_THIRDS; + + Ok(Tally { + voting_power: voting_power_post, + seen_by: seen_by_post, + seen: seen_post, + }) +} + +/// Validates that `post` is an updated version of `pre`, and returns keys which +/// changed. This function serves as a sort of validity predicate for this +/// native transaction, which is otherwise not checked by anything else. +fn validate_update( + keys: &vote_tallies::Keys, + pre: &Tally, + post: &Tally, +) -> Result { + let mut keys_changed = ChangedKeys::default(); + + let mut seen = false; + if pre.seen != post.seen { + // the only valid transition for `seen` is from `false` to `true` + if pre.seen || !post.seen { + return Err(eyre!( + "Tally seen changed from {:#?} to {:#?}", + &pre.seen, + &post.seen, + )); + } + keys_changed.insert(keys.seen()); + seen = true; + } + let pre_seen_by: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); + let post_seen_by: BTreeSet<_> = post.seen_by.keys().cloned().collect(); + + if pre_seen_by != post_seen_by { + // if seen_by changes, it must be a strict superset of the previous + // seen_by + if !post_seen_by.is_superset(&pre_seen_by) { + return Err(eyre!( + "Tally seen changed from {:#?} to {:#?}", + &pre_seen_by, + &post_seen_by, + )); + } + keys_changed.insert(keys.seen_by()); + } + + if pre.voting_power != post.voting_power { + // if voting_power changes, it must have increased + if pre.voting_power >= post.voting_power { + return Err(eyre!( + "Tally voting_power changed from {:#?} to {:#?}", + &pre.voting_power, + &post.voting_power, + )); + } + keys_changed.insert(keys.voting_power()); + } + + if post.voting_power > FractionalVotingPower::TWO_THIRDS + && !seen + && pre.voting_power >= post.voting_power + { + return Err(eyre!( + "Tally is not seen even though new voting_power is enough: {:#?}", + &post.voting_power, + )); + } + + Ok(keys_changed) +} From d22540a2a334f1c906f9f3e40464af86e95ceda4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 22 Nov 2022 15:17:27 +0000 Subject: [PATCH 1796/1995] VoteInfo::new may return an error --- .../transactions/ethereum_events/mod.rs | 2 +- .../transactions/validator_set_update/mod.rs | 2 +- .../protocol/transactions/votes/update.rs | 26 ++++++++++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 605bb16db9..ba887ba2a8 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -144,7 +144,7 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers); + let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers)?; let (vote_tracking, changed) = calculate_updated(storage, ð_msg_keys, &vote_info)?; let confirmed = diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 4198f074ac..78a2527fa7 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -113,7 +113,7 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers); + let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers)?; let (tally, changed) = calculate_updated(storage, &valset_upd_keys, &vote_info)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 1982247a1c..cac39f46ec 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -11,23 +11,37 @@ use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; +/// Wraps all the information about votes needed for updating some existing +/// tally in storage. pub(in super::super) struct VoteInfo { inner: HashMap, } impl VoteInfo { + /// Constructs a new [`VoteInfo`]. For all `votes` provided, a corresponding + /// [`FractionalVotingPower`] must be provided in `voting_powers` also, + /// otherwise an error will be returned. pub fn new( votes: Votes, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, - ) -> Self { + ) -> Result { let mut inner = HashMap::default(); - votes.into_iter().for_each(|(address, block_height)| { - let fract_voting_power = - voting_powers.get(&(address.clone(), block_height)).unwrap(); + for (address, block_height) in votes { + let fract_voting_power = match voting_powers + .get(&(address.clone(), block_height)) + { + Some(fract_voting_power) => fract_voting_power, + None => { + return Err(eyre!( + "No fractional voting power provided for vote by \ + validator {address} at block height {block_height}" + )); + } + }; _ = inner .insert(address, (block_height, fract_voting_power.to_owned())); - }); - Self { inner } + } + Ok(Self { inner }) } pub fn voters(&self) -> BTreeSet
{ From ab31c4494328f4eda922ebb06f9029d52207c57f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 22 Nov 2022 15:25:36 +0000 Subject: [PATCH 1797/1995] VoteInfo::iterate should return an Iterator --- .../ledger/protocol/transactions/votes/update.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index cac39f46ec..e755ab9a1f 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -48,15 +48,15 @@ impl VoteInfo { self.inner.keys().cloned().collect() } - pub fn iter( + pub fn iterate( &self, - ) -> BTreeSet<(Address, BlockHeight, FractionalVotingPower)> { - self.inner - .iter() - .map(|(address, (block_height, fract_voting_power))| { + ) -> impl Iterator + '_ + { + self.inner.iter().map( + |(address, (block_height, fract_voting_power))| { (address.clone(), *block_height, fract_voting_power.clone()) - }) - .collect() + }, + ) } } @@ -116,7 +116,7 @@ fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for (validator, vote_height, voting_power) in vote_info.iter() { + for (validator, vote_height, voting_power) in vote_info.iterate() { _ = seen_by_post.insert(validator, vote_height); voting_power_post += voting_power; } From dd217408c025ad8cccd6086cde89d3cc57395a1d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 22 Nov 2022 16:02:41 +0000 Subject: [PATCH 1798/1995] impl IntoIterator for VoteInfo --- .../transactions/ethereum_events/mod.rs | 2 +- .../transactions/validator_set_update/mod.rs | 2 +- .../protocol/transactions/votes/update.rs | 31 +++++++++++-------- shared/src/types/voting_power.rs | 2 +- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index ba887ba2a8..609085fc89 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -146,7 +146,7 @@ where ); let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers)?; let (vote_tracking, changed) = - calculate_updated(storage, ð_msg_keys, &vote_info)?; + calculate_updated(storage, ð_msg_keys, vote_info)?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 78a2527fa7..e59674a1b3 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -115,7 +115,7 @@ where ); let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers)?; let (tally, changed) = - calculate_updated(storage, &valset_upd_keys, &vote_info)?; + calculate_updated(storage, &valset_upd_keys, vote_info)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index e755ab9a1f..8cfc5401a8 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; use eyre::{eyre, Result}; @@ -47,16 +47,21 @@ impl VoteInfo { pub fn voters(&self) -> BTreeSet
{ self.inner.keys().cloned().collect() } +} - pub fn iterate( - &self, - ) -> impl Iterator + '_ - { - self.inner.iter().map( - |(address, (block_height, fract_voting_power))| { - (address.clone(), *block_height, fract_voting_power.clone()) - }, - ) +impl IntoIterator for VoteInfo { + type IntoIter = std::collections::hash_set::IntoIter; + type Item = (Address, BlockHeight, FractionalVotingPower); + + fn into_iter(self) -> Self::IntoIter { + let items: HashSet<_> = self + .inner + .into_iter() + .map(|(address, (block_height, fract_voting_power))| { + (address, block_height, fract_voting_power) + }) + .collect(); + items.into_iter() } } @@ -65,7 +70,7 @@ impl VoteInfo { pub(in super::super) fn calculate_updated( store: &mut Storage, keys: &vote_tallies::Keys, - vote_info: &VoteInfo, + vote_info: VoteInfo, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -104,7 +109,7 @@ where /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new /// voters from `vote_info`. Returns an error if any new voters have already /// voted previously. -fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { +fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let new_voters = vote_info.voters(); let duplicate_voters: BTreeSet<_> = @@ -116,7 +121,7 @@ fn calculate_tally_post(pre: &Tally, vote_info: &VoteInfo) -> Result { let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for (validator, vote_height, voting_power) in vote_info.iterate() { + for (validator, vote_height, voting_power) in vote_info { _ = seen_by_post.insert(validator, vote_height); voting_power_post += voting_power; } diff --git a/shared/src/types/voting_power.rs b/shared/src/types/voting_power.rs index 6cbc3895e2..3482b9fc85 100644 --- a/shared/src/types/voting_power.rs +++ b/shared/src/types/voting_power.rs @@ -8,7 +8,7 @@ use num_rational::Ratio; /// A fraction of the total voting power. This should always be a reduced /// fraction that is between zero and one inclusive. -#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug)] pub struct FractionalVotingPower(Ratio); impl FractionalVotingPower { From 6a59ae8ee6632db1afbbebb4525a74be07dce554 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 12:47:27 +0000 Subject: [PATCH 1799/1995] Add tests for VoteInfo::new --- .../protocol/transactions/votes/update.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 8cfc5401a8..be7af0879a 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -198,3 +198,40 @@ fn validate_update( Ok(keys_changed) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::address; + + #[test] + fn test_vote_info_new_empty() -> Result<()> { + let voting_powers = HashMap::default(); + + let vote_info = VoteInfo::new(Votes::default(), &voting_powers)?; + + assert!(vote_info.voters().is_empty()); + assert_eq!(vote_info.into_iter().count(), 0); + Ok(()) + } + + #[test] + fn test_vote_info_new_single_voter() -> Result<()> { + let validator = address::testing::established_address_1; + let vote_height = || BlockHeight(100); + let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + let voting_powers = HashMap::from([(vote(), voting_power())]); + + let vote_info = VoteInfo::new(votes, &voting_powers)?; + + assert_eq!(vote_info.voters(), BTreeSet::from([validator()])); + let votes: BTreeSet<_> = vote_info.into_iter().collect(); + assert_eq!( + votes, + BTreeSet::from([(validator(), vote_height(), voting_power())]), + ); + Ok(()) + } +} From 1bdda83f706075c713985ed07f53a79d62885e20 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 13:28:27 +0000 Subject: [PATCH 1800/1995] Add test_calculate_updated_empty --- .../protocol/transactions/votes/update.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index be7af0879a..6ddf805a94 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -201,8 +201,13 @@ fn validate_update( #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use super::*; + use crate::ledger::protocol::transactions::votes; + use crate::ledger::storage::testing::TestStorage; use crate::types::address; + use crate::types::ethereum_events::EthereumEvent; #[test] fn test_vote_info_new_empty() -> Result<()> { @@ -234,4 +239,31 @@ mod tests { ); Ok(()) } + + #[test] + fn test_calculate_updated_empty() -> Result<()> { + let mut storage = TestStorage::default(); + let event = EthereumEvent::TransfersToNamada { + nonce: 0.into(), + transfers: vec![], + }; + let keys = vote_tallies::Keys::from(&event); + let tally_pre = Tally { + voting_power: FractionalVotingPower::new(1, 3).unwrap(), + seen_by: BTreeMap::from([( + address::testing::established_address_1(), + 10.into(), + )]), + seen: false, + }; + votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; + let vote_info = VoteInfo::new(Votes::default(), &HashMap::default())?; + + let (tally_post, changed_keys) = + calculate_updated(&mut storage, &keys, vote_info)?; + + assert_eq!(tally_post, tally_pre); + assert!(changed_keys.is_empty()); + Ok(()) + } } From aca49f142c3b67301a9e2f072ecf6a825be7a920 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 13:34:50 +0000 Subject: [PATCH 1801/1995] Add test_calculate_updated_one_vote_not_seen --- .../protocol/transactions/votes/update.rs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 6ddf805a94..1405493367 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -266,4 +266,51 @@ mod tests { assert!(changed_keys.is_empty()); Ok(()) } + + #[test] + fn test_calculate_updated_one_vote_not_seen() -> Result<()> { + let mut storage = TestStorage::default(); + let event = EthereumEvent::TransfersToNamada { + nonce: 0.into(), + transfers: vec![], + }; + let keys = vote_tallies::Keys::from(&event); + let tally_pre = Tally { + voting_power: FractionalVotingPower::new(1, 3).unwrap(), + seen_by: BTreeMap::from([( + address::testing::established_address_1(), + 10.into(), + )]), + seen: false, + }; + votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; + + let validator = address::testing::established_address_2; + let vote_height = || BlockHeight(100); + let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + let voting_powers = HashMap::from([(vote(), voting_power())]); + let vote_info = VoteInfo::new(votes, &voting_powers)?; + + let (tally_post, changed_keys) = + calculate_updated(&mut storage, &keys, vote_info)?; + + assert_eq!( + tally_post, + Tally { + voting_power: FractionalVotingPower::new(2, 3).unwrap(), + seen_by: BTreeMap::from([ + (address::testing::established_address_1(), 10.into()), + vote(), + ]), + seen: false, + } + ); + assert_eq!( + changed_keys, + BTreeSet::from([keys.voting_power(), keys.seen_by()]) + ); + Ok(()) + } } From 1e5d304cb46d2f529b4492bc55e85933a185d147 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 13:53:08 +0000 Subject: [PATCH 1802/1995] Factor out arbitrary_event --- .../protocol/transactions/votes/update.rs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 1405493367..9b399cf5d6 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -205,10 +205,27 @@ mod tests { use super::*; use crate::ledger::protocol::transactions::votes; + use crate::ledger::protocol::transactions::votes::update::tests::helpers::arbitrary_event; use crate::ledger::storage::testing::TestStorage; use crate::types::address; use crate::types::ethereum_events::EthereumEvent; + mod helpers { + use super::*; + + /// Returns an arbitrary piece of data that can be tallied, and the keys + /// for it. + pub(super) fn arbitrary_event() + -> (EthereumEvent, vote_tallies::Keys) { + let event = EthereumEvent::TransfersToNamada { + nonce: 0.into(), + transfers: vec![], + }; + let keys = vote_tallies::Keys::from(&event); + (event, keys) + } + } + #[test] fn test_vote_info_new_empty() -> Result<()> { let voting_powers = HashMap::default(); @@ -243,11 +260,7 @@ mod tests { #[test] fn test_calculate_updated_empty() -> Result<()> { let mut storage = TestStorage::default(); - let event = EthereumEvent::TransfersToNamada { - nonce: 0.into(), - transfers: vec![], - }; - let keys = vote_tallies::Keys::from(&event); + let (event, keys) = arbitrary_event(); let tally_pre = Tally { voting_power: FractionalVotingPower::new(1, 3).unwrap(), seen_by: BTreeMap::from([( @@ -270,11 +283,8 @@ mod tests { #[test] fn test_calculate_updated_one_vote_not_seen() -> Result<()> { let mut storage = TestStorage::default(); - let event = EthereumEvent::TransfersToNamada { - nonce: 0.into(), - transfers: vec![], - }; - let keys = vote_tallies::Keys::from(&event); + + let (event, keys) = arbitrary_event(); let tally_pre = Tally { voting_power: FractionalVotingPower::new(1, 3).unwrap(), seen_by: BTreeMap::from([( From ca3dd7052f28d13a02b6a64299acca9f40d05584 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 14:11:04 +0000 Subject: [PATCH 1803/1995] Add setup_tally test helper --- .../protocol/transactions/votes/update.rs | 49 ++++++++++++++----- shared/src/types/voting_power.rs | 7 +++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 9b399cf5d6..b68141b24b 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -205,7 +205,7 @@ mod tests { use super::*; use crate::ledger::protocol::transactions::votes; - use crate::ledger::protocol::transactions::votes::update::tests::helpers::arbitrary_event; + use crate::ledger::protocol::transactions::votes::update::tests::helpers::{arbitrary_event, setup_tally}; use crate::ledger::storage::testing::TestStorage; use crate::types::address; use crate::types::ethereum_events::EthereumEvent; @@ -224,6 +224,24 @@ mod tests { let keys = vote_tallies::Keys::from(&event); (event, keys) } + + /// Writes an initial [`Tally`] to storage, based on the passed `votes`. + pub(super) fn setup_tally( + storage: &mut TestStorage, + event: &EthereumEvent, + keys: &vote_tallies::Keys, + votes: HashSet<(Address, BlockHeight, FractionalVotingPower)>, + ) -> Result { + let voting_power: FractionalVotingPower = + votes.iter().cloned().map(|(_, _, v)| v).sum(); + let tally = Tally { + voting_power: voting_power.to_owned(), + seen_by: votes.into_iter().map(|(a, h, _)| (a, h)).collect(), + seen: voting_power > FractionalVotingPower::TWO_THIRDS, + }; + votes::storage::write(storage, keys, event, &tally)?; + Ok(tally) + } } #[test] @@ -261,14 +279,16 @@ mod tests { fn test_calculate_updated_empty() -> Result<()> { let mut storage = TestStorage::default(); let (event, keys) = arbitrary_event(); - let tally_pre = Tally { - voting_power: FractionalVotingPower::new(1, 3).unwrap(), - seen_by: BTreeMap::from([( + let tally_pre = setup_tally( + &mut storage, + &event, + &keys, + HashSet::from([( address::testing::established_address_1(), - 10.into(), + BlockHeight(100), + FractionalVotingPower::new(1, 3).unwrap(), )]), - seen: false, - }; + )?; votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; let vote_info = VoteInfo::new(Votes::default(), &HashMap::default())?; @@ -285,14 +305,17 @@ mod tests { let mut storage = TestStorage::default(); let (event, keys) = arbitrary_event(); - let tally_pre = Tally { - voting_power: FractionalVotingPower::new(1, 3).unwrap(), - seen_by: BTreeMap::from([( + let (event, keys) = arbitrary_event(); + let tally_pre = setup_tally( + &mut storage, + &event, + &keys, + HashSet::from([( address::testing::established_address_1(), - 10.into(), + BlockHeight(100), + FractionalVotingPower::new(1, 3).unwrap(), )]), - seen: false, - }; + )?; votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; let validator = address::testing::established_address_2; diff --git a/shared/src/types/voting_power.rs b/shared/src/types/voting_power.rs index 3482b9fc85..126554e56e 100644 --- a/shared/src/types/voting_power.rs +++ b/shared/src/types/voting_power.rs @@ -1,5 +1,6 @@ //! This module contains types related with validator voting power calculations. +use std::iter::Sum; use std::ops::{Add, AddAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -44,6 +45,12 @@ impl From<&FractionalVotingPower> for (u64, u64) { } } +impl Sum for FractionalVotingPower { + fn sum>(iter: I) -> Self { + iter.fold(Self::default(), Add::add) + } +} + impl Add for FractionalVotingPower { type Output = Self; From d64a9f87b81b5a08bb7ebb2769ca803cab1b60ff Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 14:11:21 +0000 Subject: [PATCH 1804/1995] Remove duplicate line --- shared/src/ledger/protocol/transactions/votes/update.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index b68141b24b..2ff9bfaa24 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -304,7 +304,6 @@ mod tests { fn test_calculate_updated_one_vote_not_seen() -> Result<()> { let mut storage = TestStorage::default(); - let (event, keys) = arbitrary_event(); let (event, keys) = arbitrary_event(); let tally_pre = setup_tally( &mut storage, From 561c9e34755f1c515a61148f1739a2a0c63fd80c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 14:22:36 +0000 Subject: [PATCH 1805/1995] Fix tests --- shared/src/ledger/protocol/transactions/votes/update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 2ff9bfaa24..58081ba50f 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -285,7 +285,7 @@ mod tests { &keys, HashSet::from([( address::testing::established_address_1(), - BlockHeight(100), + BlockHeight(10), FractionalVotingPower::new(1, 3).unwrap(), )]), )?; @@ -311,7 +311,7 @@ mod tests { &keys, HashSet::from([( address::testing::established_address_1(), - BlockHeight(100), + BlockHeight(10), FractionalVotingPower::new(1, 3).unwrap(), )]), )?; From 70ee1f483c23ed0bb69362ee8bae896e4c346524 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 14:23:36 +0000 Subject: [PATCH 1806/1995] Add test_calculate_updated_one_vote_seen --- .../protocol/transactions/votes/update.rs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 58081ba50f..2a66d6f365 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -345,4 +345,50 @@ mod tests { ); Ok(()) } + + #[test] + fn test_calculate_updated_one_vote_seen() { + let mut storage = TestStorage::default(); + + let (event, keys) = arbitrary_event(); + let tally_pre = setup_tally( + &mut storage, + &event, + &keys, + HashSet::from([( + address::testing::established_address_1(), + BlockHeight(10), + FractionalVotingPower::new(1, 3).unwrap(), + )]), + ) + .unwrap(); + votes::storage::write(&mut storage, &keys, &event, &tally_pre).unwrap(); + + let validator = address::testing::established_address_2; + let vote_height = || BlockHeight(100); + let voting_power = || FractionalVotingPower::new(2, 3).unwrap(); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + let voting_powers = HashMap::from([(vote(), voting_power())]); + let vote_info = VoteInfo::new(votes, &voting_powers).unwrap(); + + let (tally_post, changed_keys) = + calculate_updated(&mut storage, &keys, vote_info).unwrap(); + + assert_eq!( + tally_post, + Tally { + voting_power: FractionalVotingPower::new(1, 1).unwrap(), + seen_by: BTreeMap::from([ + (address::testing::established_address_1(), 10.into()), + vote(), + ]), + seen: true, + } + ); + assert_eq!( + changed_keys, + BTreeSet::from([keys.voting_power(), keys.seen_by(), keys.seen()]) + ); + } } From a111fe1642d766704883756ac8c94c287cda54da Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 15:02:30 +0000 Subject: [PATCH 1807/1995] Add test_vote_info_new_error --- .../ledger/protocol/transactions/votes/update.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 2a66d6f365..118023c5c4 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -275,6 +275,21 @@ mod tests { Ok(()) } + #[test] + fn test_vote_info_new_error() -> Result<()> { + let validator = address::testing::established_address_1; + let vote_height = || BlockHeight(100); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + // voting powers map is missing vote + let voting_powers = HashMap::default(); + + let result = VoteInfo::new(votes, &voting_powers); + + assert!(result.is_err()); + Ok(()) + } + #[test] fn test_calculate_updated_empty() -> Result<()> { let mut storage = TestStorage::default(); From 8524234832047fde9ad84779bb5b590c4ba6abb7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 15:16:14 +0000 Subject: [PATCH 1808/1995] Rename calculate_updated -> calculate --- .../protocol/transactions/ethereum_events/mod.rs | 6 ++---- .../protocol/transactions/validator_set_update/mod.rs | 6 ++---- .../src/ledger/protocol/transactions/votes/update.rs | 11 +++++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 609085fc89..9d3fe24e0e 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -11,9 +11,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{self}; -use crate::ledger::protocol::transactions::votes::update::{ - calculate_updated, VoteInfo, -}; +use crate::ledger::protocol::transactions::votes::update::VoteInfo; use crate::ledger::protocol::transactions::votes::{self, calculate_new}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -146,7 +144,7 @@ where ); let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers)?; let (vote_tracking, changed) = - calculate_updated(storage, ð_msg_keys, vote_info)?; + votes::update::calculate(storage, ð_msg_keys, vote_info)?; let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index e59674a1b3..d628b0869d 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -7,9 +7,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; -use crate::ledger::protocol::transactions::votes::update::{ - calculate_updated, VoteInfo, -}; +use crate::ledger::protocol::transactions::votes::update::VoteInfo; use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -115,7 +113,7 @@ where ); let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers)?; let (tally, changed) = - calculate_updated(storage, &valset_upd_keys, vote_info)?; + votes::update::calculate(storage, &valset_upd_keys, vote_info)?; let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 118023c5c4..a6affb5d4c 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -67,7 +67,7 @@ impl IntoIterator for VoteInfo { /// Calculate an updated [`Tally`] based on one that is in storage under `keys`, /// with some new `voters`. -pub(in super::super) fn calculate_updated( +pub(in super::super) fn calculate( store: &mut Storage, keys: &vote_tallies::Keys, vote_info: VoteInfo, @@ -107,8 +107,7 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// voters from `vote_info`. Returns an error if any new voters have already -/// voted previously. +/// voters from `vote_info`. fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let new_voters = vote_info.voters(); @@ -308,7 +307,7 @@ mod tests { let vote_info = VoteInfo::new(Votes::default(), &HashMap::default())?; let (tally_post, changed_keys) = - calculate_updated(&mut storage, &keys, vote_info)?; + calculate(&mut storage, &keys, vote_info)?; assert_eq!(tally_post, tally_pre); assert!(changed_keys.is_empty()); @@ -341,7 +340,7 @@ mod tests { let vote_info = VoteInfo::new(votes, &voting_powers)?; let (tally_post, changed_keys) = - calculate_updated(&mut storage, &keys, vote_info)?; + calculate(&mut storage, &keys, vote_info)?; assert_eq!( tally_post, @@ -388,7 +387,7 @@ mod tests { let vote_info = VoteInfo::new(votes, &voting_powers).unwrap(); let (tally_post, changed_keys) = - calculate_updated(&mut storage, &keys, vote_info).unwrap(); + calculate(&mut storage, &keys, vote_info).unwrap(); assert_eq!( tally_post, From aae0a6ef8deedcf03df80221345db7771028124d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 15:16:38 +0000 Subject: [PATCH 1809/1995] rename validate_update -> validate --- shared/src/ledger/protocol/transactions/votes/update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index a6affb5d4c..b25b71b5c7 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -84,7 +84,7 @@ where ); let tally_pre = super::storage::read(store, keys)?; let tally_post = calculate_tally_post(&tally_pre, vote_info)?; - let changed_keys = validate_update(keys, &tally_pre, &tally_post)?; + let changed_keys = validate(keys, &tally_pre, &tally_post)?; if tally_post.seen { tracing::info!( @@ -137,7 +137,7 @@ fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { /// Validates that `post` is an updated version of `pre`, and returns keys which /// changed. This function serves as a sort of validity predicate for this /// native transaction, which is otherwise not checked by anything else. -fn validate_update( +fn validate( keys: &vote_tallies::Keys, pre: &Tally, post: &Tally, From d04a91b6f378e65b3d98adf12f815cc784538de9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 15:30:22 +0000 Subject: [PATCH 1810/1995] Implement VoteInfo::without_voters --- .../protocol/transactions/votes/update.rs | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index b25b71b5c7..59d5001a25 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -47,6 +47,23 @@ impl VoteInfo { pub fn voters(&self) -> BTreeSet
{ self.inner.keys().cloned().collect() } + + /// Consumes `self` and returns a `VoteInfo` with any addresses from + /// `voters` removed, as well as the set of addresses that were actually + /// removed. Useful for removing voters who have already voted for + /// something. + pub fn without_voters( + self, + voters: impl IntoIterator, + ) -> (Self, HashSet
) { + let mut inner = self.inner; + let mut removed = HashSet::default(); + for voter in voters { + inner.remove(&voter); + removed.insert(voter); + } + (Self { inner }, removed) + } } impl IntoIterator for VoteInfo { @@ -107,20 +124,19 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// voters from `vote_info`. +/// voters from `vote_info`. Voters in `vote_info` who voted previously are +/// ignored. fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let new_voters = vote_info.voters(); - let duplicate_voters: BTreeSet<_> = - previous_voters.intersection(&new_voters).collect(); - if !duplicate_voters.is_empty() { - // TODO: this is a programmer error and should never happen - return Err(eyre!("Duplicate voters found - {:?}", duplicate_voters)); + let (new_voters, duplicate_voters) = + vote_info.without_voters(previous_voters); + for voter in duplicate_voters { + tracing::info!(?voter, "Ignoring duplicate voter"); } let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for (validator, vote_height, voting_power) in vote_info { + for (validator, vote_height, voting_power) in new_voters { _ = seen_by_post.insert(validator, vote_height); voting_power_post += voting_power; } @@ -289,6 +305,24 @@ mod tests { Ok(()) } + #[test] + fn test_vote_info_without_voters() -> Result<()> { + let validator = address::testing::established_address_1; + let vote_height = || BlockHeight(100); + let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + let voting_powers = HashMap::from([(vote(), voting_power())]); + + let vote_info = VoteInfo::new(votes, &voting_powers)?; + + let (vote_info, removed) = vote_info.without_voters(vec![validator()]); + + assert!(vote_info.voters().is_empty()); + assert_eq!(removed, HashSet::from([validator()])); + Ok(()) + } + #[test] fn test_calculate_updated_empty() -> Result<()> { let mut storage = TestStorage::default(); From 2f93c03c7d6bfcd987c51e8192a3ab8cb87cdf44 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:08:08 +0000 Subject: [PATCH 1811/1995] Some refactoring around duplicate votes --- .../protocol/transactions/votes/update.rs | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 59d5001a25..90a06e91f0 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -52,14 +52,14 @@ impl VoteInfo { /// `voters` removed, as well as the set of addresses that were actually /// removed. Useful for removing voters who have already voted for /// something. - pub fn without_voters( + pub fn without_voters<'a>( self, - voters: impl IntoIterator, - ) -> (Self, HashSet
) { + voters: impl IntoIterator, + ) -> (Self, HashSet<&'a Address>) { let mut inner = self.inner; let mut removed = HashSet::default(); for voter in voters { - inner.remove(&voter); + inner.remove(voter); removed.insert(voter); } (Self { inner }, removed) @@ -100,7 +100,19 @@ where "Recording validators as having voted for this event" ); let tally_pre = super::storage::read(store, keys)?; - let tally_post = calculate_tally_post(&tally_pre, vote_info)?; + + let (vote_info, duplicate_voters) = + vote_info.without_voters(tally_pre.seen_by.keys()); + for voter in duplicate_voters { + tracing::info!( + ?keys.prefix, + ?voter, + "Ignoring duplicate voter" + ); + } + let tally_post = calculate_tally_post(&tally_pre, vote_info) + .expect("We deduplicated voters already, so this should never error"); + let changed_keys = validate(keys, &tally_pre, &tally_post)?; if tally_post.seen { @@ -124,20 +136,21 @@ where } /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new -/// voters from `vote_info`. Voters in `vote_info` who voted previously are -/// ignored. +/// voters from `vote_info`. An error is returned if any validator which +/// previously voted is present in `vote_info`. fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { - let previous_voters: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let (new_voters, duplicate_voters) = - vote_info.without_voters(previous_voters); - for voter in duplicate_voters { - tracing::info!(?voter, "Ignoring duplicate voter"); - } - let mut voting_power_post = pre.voting_power.clone(); let mut seen_by_post = pre.seen_by.clone(); - for (validator, vote_height, voting_power) in new_voters { - _ = seen_by_post.insert(validator, vote_height); + for (validator, vote_height, voting_power) in vote_info { + if let Some(already_voted_height) = + seen_by_post.insert(validator.clone(), vote_height) + { + return Err(eyre!( + "Validator {} had already voted at height {}", + validator, + already_voted_height, + )); + }; voting_power_post += voting_power; } @@ -313,13 +326,13 @@ mod tests { let vote = || (validator(), vote_height()); let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); - + let validator = validator(); let vote_info = VoteInfo::new(votes, &voting_powers)?; - let (vote_info, removed) = vote_info.without_voters(vec![validator()]); + let (vote_info, removed) = vote_info.without_voters(vec![&validator]); assert!(vote_info.voters().is_empty()); - assert_eq!(removed, HashSet::from([validator()])); + assert_eq!(removed, HashSet::from([&validator])); Ok(()) } From 56a690a58d96fb06bb7d1fe072d4b9b4c3694bcd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:10:22 +0000 Subject: [PATCH 1812/1995] Rename seen -> newly_seen --- shared/src/ledger/protocol/transactions/votes/update.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 90a06e91f0..768c381760 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -173,7 +173,7 @@ fn validate( ) -> Result { let mut keys_changed = ChangedKeys::default(); - let mut seen = false; + let mut newly_seen = false; if pre.seen != post.seen { // the only valid transition for `seen` is from `false` to `true` if pre.seen || !post.seen { @@ -184,7 +184,7 @@ fn validate( )); } keys_changed.insert(keys.seen()); - seen = true; + newly_seen = true; } let pre_seen_by: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); let post_seen_by: BTreeSet<_> = post.seen_by.keys().cloned().collect(); @@ -215,7 +215,7 @@ fn validate( } if post.voting_power > FractionalVotingPower::TWO_THIRDS - && !seen + && !newly_seen && pre.voting_power >= post.voting_power { return Err(eyre!( From a770685f7899387cf62d68565f24bb578cac3499 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:17:37 +0000 Subject: [PATCH 1813/1995] Simplify validate to keys_changed --- .../protocol/transactions/votes/update.rs | 71 ++++--------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 768c381760..14fc389531 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -113,7 +113,7 @@ where let tally_post = calculate_tally_post(&tally_pre, vote_info) .expect("We deduplicated voters already, so this should never error"); - let changed_keys = validate(keys, &tally_pre, &tally_post)?; + let changed_keys = keys_changed(keys, &tally_pre, &tally_post); if tally_post.seen { tracing::info!( @@ -163,68 +163,23 @@ fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { }) } -/// Validates that `post` is an updated version of `pre`, and returns keys which -/// changed. This function serves as a sort of validity predicate for this -/// native transaction, which is otherwise not checked by anything else. -fn validate( +/// Straightforwardly calculates the keys that changed between `pre` and `post`. +fn keys_changed( keys: &vote_tallies::Keys, pre: &Tally, post: &Tally, -) -> Result { - let mut keys_changed = ChangedKeys::default(); - - let mut newly_seen = false; +) -> ChangedKeys { + let mut changed_keys = ChangedKeys::default(); if pre.seen != post.seen { - // the only valid transition for `seen` is from `false` to `true` - if pre.seen || !post.seen { - return Err(eyre!( - "Tally seen changed from {:#?} to {:#?}", - &pre.seen, - &post.seen, - )); - } - keys_changed.insert(keys.seen()); - newly_seen = true; - } - let pre_seen_by: BTreeSet<_> = pre.seen_by.keys().cloned().collect(); - let post_seen_by: BTreeSet<_> = post.seen_by.keys().cloned().collect(); - - if pre_seen_by != post_seen_by { - // if seen_by changes, it must be a strict superset of the previous - // seen_by - if !post_seen_by.is_superset(&pre_seen_by) { - return Err(eyre!( - "Tally seen changed from {:#?} to {:#?}", - &pre_seen_by, - &post_seen_by, - )); - } - keys_changed.insert(keys.seen_by()); - } - + changed_keys.insert(keys.seen()); + }; if pre.voting_power != post.voting_power { - // if voting_power changes, it must have increased - if pre.voting_power >= post.voting_power { - return Err(eyre!( - "Tally voting_power changed from {:#?} to {:#?}", - &pre.voting_power, - &post.voting_power, - )); - } - keys_changed.insert(keys.voting_power()); - } - - if post.voting_power > FractionalVotingPower::TWO_THIRDS - && !newly_seen - && pre.voting_power >= post.voting_power - { - return Err(eyre!( - "Tally is not seen even though new voting_power is enough: {:#?}", - &post.voting_power, - )); - } - - Ok(keys_changed) + changed_keys.insert(keys.voting_power()); + }; + if pre.seen_by != post.seen_by { + changed_keys.insert(keys.seen_by()); + }; + changed_keys } #[cfg(test)] From af1b5ff4855b70dbe39b6694f9af3dafac28ddd7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:27:34 +0000 Subject: [PATCH 1814/1995] Don't apply votes for already seen data --- .../ledger/protocol/transactions/ethereum_events/mod.rs | 3 +++ .../protocol/transactions/validator_set_update/mod.rs | 3 +++ shared/src/ledger/protocol/transactions/votes/update.rs | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 9d3fe24e0e..05557cd687 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -145,6 +145,9 @@ where let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers)?; let (vote_tracking, changed) = votes::update::calculate(storage, ð_msg_keys, vote_info)?; + if changed.is_empty() { + return Ok((changed, false)); + } let confirmed = vote_tracking.seen && changed.contains(ð_msg_keys.seen()); (vote_tracking, changed, confirmed) diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index d628b0869d..31762d406e 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -114,6 +114,9 @@ where let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers)?; let (tally, changed) = votes::update::calculate(storage, &valset_upd_keys, vote_info)?; + if changed.is_empty() { + return Ok(changed); + } let confirmed = tally.seen && changed.contains(&valset_upd_keys.seen()); (tally, changed, confirmed) }; diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 14fc389531..20267d73b0 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -83,7 +83,10 @@ impl IntoIterator for VoteInfo { } /// Calculate an updated [`Tally`] based on one that is in storage under `keys`, -/// with some new `voters`. +/// with new votes from `vote_info` applied, as well as the storage keys that +/// would change. If [`Tally`] is already `seen = true` in storage, then no +/// votes from `vote_info` should be applied, and the returned changed keys will +/// be empty. pub(in super::super) fn calculate( store: &mut Storage, keys: &vote_tallies::Keys, @@ -100,6 +103,9 @@ where "Recording validators as having voted for this event" ); let tally_pre = super::storage::read(store, keys)?; + if tally_pre.seen { + return Ok((tally_pre, ChangedKeys::default())); + } let (vote_info, duplicate_voters) = vote_info.without_voters(tally_pre.seen_by.keys()); From 07882c7d467da961068cd72bb3915d5ec29a0533 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:47:33 +0000 Subject: [PATCH 1815/1995] Add test_keys_changed_all --- .../protocol/transactions/votes/update.rs | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 20267d73b0..13fa02ccae 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -204,14 +204,11 @@ mod tests { /// Returns an arbitrary piece of data that can be tallied, and the keys /// for it. - pub(super) fn arbitrary_event() - -> (EthereumEvent, vote_tallies::Keys) { - let event = EthereumEvent::TransfersToNamada { + pub(super) fn arbitrary_event() -> EthereumEvent { + EthereumEvent::TransfersToNamada { nonce: 0.into(), transfers: vec![], - }; - let keys = vote_tallies::Keys::from(&event); - (event, keys) + } } /// Writes an initial [`Tally`] to storage, based on the passed `votes`. @@ -300,7 +297,8 @@ mod tests { #[test] fn test_calculate_updated_empty() -> Result<()> { let mut storage = TestStorage::default(); - let (event, keys) = arbitrary_event(); + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); let tally_pre = setup_tally( &mut storage, &event, @@ -326,7 +324,8 @@ mod tests { fn test_calculate_updated_one_vote_not_seen() -> Result<()> { let mut storage = TestStorage::default(); - let (event, keys) = arbitrary_event(); + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); let tally_pre = setup_tally( &mut storage, &event, @@ -372,7 +371,8 @@ mod tests { fn test_calculate_updated_one_vote_seen() { let mut storage = TestStorage::default(); - let (event, keys) = arbitrary_event(); + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); let tally_pre = setup_tally( &mut storage, &event, @@ -413,4 +413,41 @@ mod tests { BTreeSet::from([keys.voting_power(), keys.seen_by(), keys.seen()]) ); } + + #[test] + fn test_keys_changed_all() { + let voting_power_a = FractionalVotingPower::new(1, 3).unwrap(); + let voting_power_b = FractionalVotingPower::new(2, 3).unwrap(); + + let seen_a = false; + let seen_b = true; + + let seen_by_a = BTreeMap::from([( + address::testing::established_address_1(), + BlockHeight(10), + )]); + let seen_by_b = BTreeMap::from([( + address::testing::established_address_2(), + BlockHeight(20), + )]); + + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); + let pre = Tally { + voting_power: voting_power_a, + seen: seen_a, + seen_by: seen_by_a, + }; + let post = Tally { + voting_power: voting_power_b, + seen: seen_b, + seen_by: seen_by_b, + }; + let changed_keys = keys_changed(&keys, &pre, &post); + + assert_eq!( + changed_keys, + BTreeSet::from([keys.seen(), keys.seen_by(), keys.voting_power()]) + ); + } } From 6c9fede304e96c15a73b2b43c3aa9efb5deef009 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:48:07 +0000 Subject: [PATCH 1816/1995] Add test_keys_changed_none --- .../protocol/transactions/votes/update.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 13fa02ccae..0a428ff65d 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -450,4 +450,26 @@ mod tests { BTreeSet::from([keys.seen(), keys.seen_by(), keys.voting_power()]) ); } + + #[test] + fn test_keys_changed_none() { + let voting_power = FractionalVotingPower::new(1, 3).unwrap(); + let seen = false; + let seen_by = BTreeMap::from([( + address::testing::established_address_1(), + BlockHeight(10), + )]); + + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); + let pre = Tally { + voting_power, + seen, + seen_by, + }; + let post = pre.clone(); + let changed_keys = keys_changed(&keys, &pre, &post); + + assert!(changed_keys.is_empty()); + } } From 1559bbf9dd621567dd3af6981d08e247192fc697 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:48:33 +0000 Subject: [PATCH 1817/1995] Rename calculate_tally_post -> apply --- shared/src/ledger/protocol/transactions/votes/update.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 0a428ff65d..ac3958c101 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -116,7 +116,7 @@ where "Ignoring duplicate voter" ); } - let tally_post = calculate_tally_post(&tally_pre, vote_info) + let tally_post = apply(&tally_pre, vote_info) .expect("We deduplicated voters already, so this should never error"); let changed_keys = keys_changed(keys, &tally_pre, &tally_post); @@ -144,9 +144,9 @@ where /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new /// voters from `vote_info`. An error is returned if any validator which /// previously voted is present in `vote_info`. -fn calculate_tally_post(pre: &Tally, vote_info: VoteInfo) -> Result { - let mut voting_power_post = pre.voting_power.clone(); - let mut seen_by_post = pre.seen_by.clone(); +fn apply(tally: &Tally, vote_info: VoteInfo) -> Result { + let mut voting_power_post = tally.voting_power.clone(); + let mut seen_by_post = tally.seen_by.clone(); for (validator, vote_height, voting_power) in vote_info { if let Some(already_voted_height) = seen_by_post.insert(validator.clone(), vote_height) From ea8d91feddfa4dac94c63f6829c6fc9f3bd85ec4 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:49:17 +0000 Subject: [PATCH 1818/1995] Fix test names --- shared/src/ledger/protocol/transactions/votes/update.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index ac3958c101..2d20b36d90 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -295,7 +295,7 @@ mod tests { } #[test] - fn test_calculate_updated_empty() -> Result<()> { + fn test_calculate_empty() -> Result<()> { let mut storage = TestStorage::default(); let event = arbitrary_event(); let keys = vote_tallies::Keys::from(&event); @@ -321,7 +321,7 @@ mod tests { } #[test] - fn test_calculate_updated_one_vote_not_seen() -> Result<()> { + fn test_calculate_one_vote_not_seen() -> Result<()> { let mut storage = TestStorage::default(); let event = arbitrary_event(); @@ -368,7 +368,7 @@ mod tests { } #[test] - fn test_calculate_updated_one_vote_seen() { + fn test_calculate_one_vote_seen() { let mut storage = TestStorage::default(); let event = arbitrary_event(); From ffe4a0ea0ccdd97442a452705a86a92a6d4fa105 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 16:54:46 +0000 Subject: [PATCH 1819/1995] Add test_calculate_already_seen --- .../protocol/transactions/votes/update.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 2d20b36d90..151e7863ce 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -294,6 +294,41 @@ mod tests { Ok(()) } + /// Tests that an unchanged tally is returned if the tally as in storage is + /// already recorded as having been seen. + #[test] + fn test_calculate_already_seen() -> Result<()> { + let mut storage = TestStorage::default(); + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); + let tally_pre = setup_tally( + &mut storage, + &event, + &keys, + HashSet::from([( + address::testing::established_address_1(), + BlockHeight(10), + FractionalVotingPower::new(3, 4).unwrap(), // this is > 2/3 + )]), + )?; + + let validator = address::testing::established_address_2; + let vote_height = || BlockHeight(100); + let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); + let vote = || (validator(), vote_height()); + let votes = Votes::from([vote()]); + let voting_powers = HashMap::from([(vote(), voting_power())]); + let vote_info = VoteInfo::new(votes, &voting_powers)?; + + let (tally_post, changed_keys) = + calculate(&mut storage, &keys, vote_info)?; + + assert_eq!(tally_post, tally_pre); + assert!(changed_keys.is_empty()); + Ok(()) + } + + /// Tests that an unchanged tally is returned if no votes are passed. #[test] fn test_calculate_empty() -> Result<()> { let mut storage = TestStorage::default(); @@ -320,6 +355,8 @@ mod tests { Ok(()) } + /// Tests the case where a single vote is applied, and the tally is still + /// not yet seen. #[test] fn test_calculate_one_vote_not_seen() -> Result<()> { let mut storage = TestStorage::default(); @@ -367,6 +404,8 @@ mod tests { Ok(()) } + /// Tests the case where a single vote is applied, and the tally is now + /// seen. #[test] fn test_calculate_one_vote_seen() { let mut storage = TestStorage::default(); From 4ee52a6502165e13e57d0321e31c87191a9e209a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 17:20:51 +0000 Subject: [PATCH 1820/1995] Rename VoteInfo -> NewVotes --- .../transactions/ethereum_events/mod.rs | 6 ++-- .../transactions/validator_set_update/mod.rs | 6 ++-- .../protocol/transactions/votes/update.rs | 28 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 05557cd687..89454f0666 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -11,7 +11,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{self}; -use crate::ledger::protocol::transactions::votes::update::VoteInfo; +use crate::ledger::protocol::transactions::votes::update::NewVotes; use crate::ledger::protocol::transactions::votes::{self, calculate_new}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -142,9 +142,9 @@ where %eth_msg_keys.prefix, "Ethereum event already exists in storage", ); - let vote_info = VoteInfo::new(update.seen_by.clone(), voting_powers)?; + let new_votes = NewVotes::new(update.seen_by.clone(), voting_powers)?; let (vote_tracking, changed) = - votes::update::calculate(storage, ð_msg_keys, vote_info)?; + votes::update::calculate(storage, ð_msg_keys, new_votes)?; if changed.is_empty() { return Ok((changed, false)); } diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 31762d406e..e30d22b0bd 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -7,7 +7,7 @@ use eyre::Result; use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; -use crate::ledger::protocol::transactions::votes::update::VoteInfo; +use crate::ledger::protocol::transactions::votes::update::NewVotes; use crate::ledger::protocol::transactions::votes::{self, Votes}; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; @@ -111,9 +111,9 @@ where %valset_upd_keys.prefix, "Validator set update votes already in storage", ); - let vote_info = VoteInfo::new(seen_by.clone(), &voting_powers)?; + let new_votes = NewVotes::new(seen_by.clone(), &voting_powers)?; let (tally, changed) = - votes::update::calculate(storage, &valset_upd_keys, vote_info)?; + votes::update::calculate(storage, &valset_upd_keys, new_votes)?; if changed.is_empty() { return Ok(changed); } diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 151e7863ce..37778acc16 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -11,13 +11,13 @@ use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; -/// Wraps all the information about votes needed for updating some existing +/// Wraps all the information about new votes to be applied to some existing /// tally in storage. -pub(in super::super) struct VoteInfo { +pub(in super::super) struct NewVotes { inner: HashMap, } -impl VoteInfo { +impl NewVotes { /// Constructs a new [`VoteInfo`]. For all `votes` provided, a corresponding /// [`FractionalVotingPower`] must be provided in `voting_powers` also, /// otherwise an error will be returned. @@ -66,7 +66,7 @@ impl VoteInfo { } } -impl IntoIterator for VoteInfo { +impl IntoIterator for NewVotes { type IntoIter = std::collections::hash_set::IntoIter; type Item = (Address, BlockHeight, FractionalVotingPower); @@ -90,7 +90,7 @@ impl IntoIterator for VoteInfo { pub(in super::super) fn calculate( store: &mut Storage, keys: &vote_tallies::Keys, - vote_info: VoteInfo, + vote_info: NewVotes, ) -> Result<(Tally, ChangedKeys)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -144,7 +144,7 @@ where /// Takes an existing [`Tally`] and calculates the new [`Tally`] based on new /// voters from `vote_info`. An error is returned if any validator which /// previously voted is present in `vote_info`. -fn apply(tally: &Tally, vote_info: VoteInfo) -> Result { +fn apply(tally: &Tally, vote_info: NewVotes) -> Result { let mut voting_power_post = tally.voting_power.clone(); let mut seen_by_post = tally.seen_by.clone(); for (validator, vote_height, voting_power) in vote_info { @@ -234,7 +234,7 @@ mod tests { fn test_vote_info_new_empty() -> Result<()> { let voting_powers = HashMap::default(); - let vote_info = VoteInfo::new(Votes::default(), &voting_powers)?; + let vote_info = NewVotes::new(Votes::default(), &voting_powers)?; assert!(vote_info.voters().is_empty()); assert_eq!(vote_info.into_iter().count(), 0); @@ -250,7 +250,7 @@ mod tests { let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); - let vote_info = VoteInfo::new(votes, &voting_powers)?; + let vote_info = NewVotes::new(votes, &voting_powers)?; assert_eq!(vote_info.voters(), BTreeSet::from([validator()])); let votes: BTreeSet<_> = vote_info.into_iter().collect(); @@ -270,7 +270,7 @@ mod tests { // voting powers map is missing vote let voting_powers = HashMap::default(); - let result = VoteInfo::new(votes, &voting_powers); + let result = NewVotes::new(votes, &voting_powers); assert!(result.is_err()); Ok(()) @@ -285,7 +285,7 @@ mod tests { let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); let validator = validator(); - let vote_info = VoteInfo::new(votes, &voting_powers)?; + let vote_info = NewVotes::new(votes, &voting_powers)?; let (vote_info, removed) = vote_info.without_voters(vec![&validator]); @@ -318,7 +318,7 @@ mod tests { let vote = || (validator(), vote_height()); let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); - let vote_info = VoteInfo::new(votes, &voting_powers)?; + let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = calculate(&mut storage, &keys, vote_info)?; @@ -345,7 +345,7 @@ mod tests { )]), )?; votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; - let vote_info = VoteInfo::new(Votes::default(), &HashMap::default())?; + let vote_info = NewVotes::new(Votes::default(), &HashMap::default())?; let (tally_post, changed_keys) = calculate(&mut storage, &keys, vote_info)?; @@ -381,7 +381,7 @@ mod tests { let vote = || (validator(), vote_height()); let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); - let vote_info = VoteInfo::new(votes, &voting_powers)?; + let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = calculate(&mut storage, &keys, vote_info)?; @@ -431,7 +431,7 @@ mod tests { let vote = || (validator(), vote_height()); let votes = Votes::from([vote()]); let voting_powers = HashMap::from([(vote(), voting_power())]); - let vote_info = VoteInfo::new(votes, &voting_powers).unwrap(); + let vote_info = NewVotes::new(votes, &voting_powers).unwrap(); let (tally_post, changed_keys) = calculate(&mut storage, &keys, vote_info).unwrap(); From 1ff31d0da90ec23644569e9d70507a44630b9fd8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 23 Nov 2022 17:46:24 +0000 Subject: [PATCH 1821/1995] Add test_apply_duplicate_votes --- .../protocol/transactions/votes/update.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 37778acc16..e96f1a71ce 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -294,6 +294,39 @@ mod tests { Ok(()) } + #[test] + fn test_apply_duplicate_votes() -> Result<()> { + let mut storage = TestStorage::default(); + + let validator = address::testing::established_address_1(); + let already_voted_height = BlockHeight(100); + + let event = arbitrary_event(); + let keys = vote_tallies::Keys::from(&event); + let tally_pre = setup_tally( + &mut storage, + &event, + &keys, + HashSet::from([( + validator.clone(), + already_voted_height, + FractionalVotingPower::new(1, 3).unwrap(), + )]), + )?; + + let votes = Votes::from([(validator.clone(), BlockHeight(1000))]); + let voting_powers = HashMap::from([( + (validator, BlockHeight(1000)), + FractionalVotingPower::new(1, 3).unwrap(), + )]); + let vote_info = NewVotes::new(votes, &voting_powers)?; + + let result = apply(&tally_pre, vote_info); + + assert!(result.is_err()); + Ok(()) + } + /// Tests that an unchanged tally is returned if the tally as in storage is /// already recorded as having been seen. #[test] From 94f6d239c463784758754b5a28e123858c3bd008 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 15:31:40 +0000 Subject: [PATCH 1822/1995] Don't use anonymous functions in tests; make tests more consistent --- .../protocol/transactions/votes/update.rs | 120 +++++++++--------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index e96f1a71ce..9bf5a7ed74 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -243,31 +243,30 @@ mod tests { #[test] fn test_vote_info_new_single_voter() -> Result<()> { - let validator = address::testing::established_address_1; - let vote_height = || BlockHeight(100); - let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - let voting_powers = HashMap::from([(vote(), voting_power())]); + let validator = address::testing::established_address_1(); + let vote_height = BlockHeight(100); + let voting_power = FractionalVotingPower::new(1, 3)?; + let vote = (validator.clone(), vote_height); + let votes = Votes::from([vote.clone()]); + let voting_powers = HashMap::from([(vote, voting_power.clone())]); let vote_info = NewVotes::new(votes, &voting_powers)?; - assert_eq!(vote_info.voters(), BTreeSet::from([validator()])); + assert_eq!(vote_info.voters(), BTreeSet::from([validator.clone()])); let votes: BTreeSet<_> = vote_info.into_iter().collect(); assert_eq!( votes, - BTreeSet::from([(validator(), vote_height(), voting_power())]), + BTreeSet::from([(validator, vote_height, voting_power,)]), ); Ok(()) } #[test] fn test_vote_info_new_error() -> Result<()> { - let validator = address::testing::established_address_1; - let vote_height = || BlockHeight(100); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - // voting powers map is missing vote + let votes = Votes::from([( + address::testing::established_address_1(), + BlockHeight(100), + )]); let voting_powers = HashMap::default(); let result = NewVotes::new(votes, &voting_powers); @@ -278,13 +277,12 @@ mod tests { #[test] fn test_vote_info_without_voters() -> Result<()> { - let validator = address::testing::established_address_1; - let vote_height = || BlockHeight(100); - let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - let voting_powers = HashMap::from([(vote(), voting_power())]); - let validator = validator(); + let validator = address::testing::established_address_1(); + let vote_height = BlockHeight(100); + let voting_power = FractionalVotingPower::new(1, 3)?; + let vote = (validator.clone(), vote_height); + let votes = Votes::from([vote.clone()]); + let voting_powers = HashMap::from([(vote, voting_power)]); let vote_info = NewVotes::new(votes, &voting_powers)?; let (vote_info, removed) = vote_info.without_voters(vec![&validator]); @@ -310,14 +308,14 @@ mod tests { HashSet::from([( validator.clone(), already_voted_height, - FractionalVotingPower::new(1, 3).unwrap(), + FractionalVotingPower::new(1, 3)?, )]), )?; let votes = Votes::from([(validator.clone(), BlockHeight(1000))]); let voting_powers = HashMap::from([( (validator, BlockHeight(1000)), - FractionalVotingPower::new(1, 3).unwrap(), + FractionalVotingPower::new(1, 3)?, )]); let vote_info = NewVotes::new(votes, &voting_powers)?; @@ -341,16 +339,16 @@ mod tests { HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::new(3, 4).unwrap(), // this is > 2/3 + FractionalVotingPower::new(3, 4)?, // this is > 2/3 )]), )?; - let validator = address::testing::established_address_2; - let vote_height = || BlockHeight(100); - let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - let voting_powers = HashMap::from([(vote(), voting_power())]); + let validator = address::testing::established_address_2(); + let vote_height = BlockHeight(100); + let voting_power = FractionalVotingPower::new(1, 3)?; + let vote = (validator, vote_height); + let votes = Votes::from([vote.clone()]); + let voting_powers = HashMap::from([(vote, voting_power)]); let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = @@ -374,7 +372,7 @@ mod tests { HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::new(1, 3).unwrap(), + FractionalVotingPower::new(1, 3)?, )]), )?; votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; @@ -403,17 +401,17 @@ mod tests { HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::new(1, 3).unwrap(), + FractionalVotingPower::new(1, 3)?, )]), )?; votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; - let validator = address::testing::established_address_2; - let vote_height = || BlockHeight(100); - let voting_power = || FractionalVotingPower::new(1, 3).unwrap(); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - let voting_powers = HashMap::from([(vote(), voting_power())]); + let validator = address::testing::established_address_2(); + let vote_height = BlockHeight(100); + let voting_power = FractionalVotingPower::new(1, 3)?; + let vote = (validator, vote_height); + let votes = Votes::from([vote.clone()]); + let voting_powers = HashMap::from([(vote.clone(), voting_power)]); let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = @@ -422,10 +420,10 @@ mod tests { assert_eq!( tally_post, Tally { - voting_power: FractionalVotingPower::new(2, 3).unwrap(), + voting_power: FractionalVotingPower::new(2, 3)?, seen_by: BTreeMap::from([ (address::testing::established_address_1(), 10.into()), - vote(), + vote, ]), seen: false, } @@ -440,7 +438,7 @@ mod tests { /// Tests the case where a single vote is applied, and the tally is now /// seen. #[test] - fn test_calculate_one_vote_seen() { + fn test_calculate_one_vote_seen() -> Result<()> { let mut storage = TestStorage::default(); let event = arbitrary_event(); @@ -452,30 +450,29 @@ mod tests { HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::new(1, 3).unwrap(), + FractionalVotingPower::new(1, 3)?, )]), - ) - .unwrap(); - votes::storage::write(&mut storage, &keys, &event, &tally_pre).unwrap(); - - let validator = address::testing::established_address_2; - let vote_height = || BlockHeight(100); - let voting_power = || FractionalVotingPower::new(2, 3).unwrap(); - let vote = || (validator(), vote_height()); - let votes = Votes::from([vote()]); - let voting_powers = HashMap::from([(vote(), voting_power())]); - let vote_info = NewVotes::new(votes, &voting_powers).unwrap(); + )?; + votes::storage::write(&mut storage, &keys, &event, &tally_pre)?; + + let validator = address::testing::established_address_2(); + let vote_height = BlockHeight(100); + let voting_power = FractionalVotingPower::new(2, 3)?; + let vote = (validator, vote_height); + let votes = Votes::from([vote.clone()]); + let voting_powers = HashMap::from([(vote.clone(), voting_power)]); + let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = - calculate(&mut storage, &keys, vote_info).unwrap(); + calculate(&mut storage, &keys, vote_info)?; assert_eq!( tally_post, Tally { - voting_power: FractionalVotingPower::new(1, 1).unwrap(), + voting_power: FractionalVotingPower::new(1, 1)?, seen_by: BTreeMap::from([ (address::testing::established_address_1(), 10.into()), - vote(), + vote, ]), seen: true, } @@ -484,12 +481,13 @@ mod tests { changed_keys, BTreeSet::from([keys.voting_power(), keys.seen_by(), keys.seen()]) ); + Ok(()) } #[test] - fn test_keys_changed_all() { - let voting_power_a = FractionalVotingPower::new(1, 3).unwrap(); - let voting_power_b = FractionalVotingPower::new(2, 3).unwrap(); + fn test_keys_changed_all() -> Result<()> { + let voting_power_a = FractionalVotingPower::new(1, 3)?; + let voting_power_b = FractionalVotingPower::new(2, 3)?; let seen_a = false; let seen_b = true; @@ -521,11 +519,12 @@ mod tests { changed_keys, BTreeSet::from([keys.seen(), keys.seen_by(), keys.voting_power()]) ); + Ok(()) } #[test] - fn test_keys_changed_none() { - let voting_power = FractionalVotingPower::new(1, 3).unwrap(); + fn test_keys_changed_none() -> Result<()> { + let voting_power = FractionalVotingPower::new(1, 3)?; let seen = false; let seen_by = BTreeMap::from([( address::testing::established_address_1(), @@ -543,5 +542,6 @@ mod tests { let changed_keys = keys_changed(&keys, &pre, &post); assert!(changed_keys.is_empty()); + Ok(()) } } From 027accbb9781b7d681095025fc71febefa4e1d40 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 15:49:55 +0000 Subject: [PATCH 1823/1995] Split docstrings --- .../protocol/transactions/ethereum_events/mod.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 89454f0666..6f8c8b013b 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -68,7 +68,9 @@ where } /// Apply votes to Ethereum events in storage and act on any events which are -/// confirmed. The `voting_powers` map must contain a voting power for all +/// confirmed. +/// +/// The `voting_powers` map must contain a voting power for all /// `(Address, BlockHeight)`s that occur in any of the `updates`. pub(super) fn apply_updates( storage: &mut Storage, @@ -113,8 +115,10 @@ where } /// Apply an [`EthMsgUpdate`] to storage. Returns any keys changed and whether -/// the event was newly seen. The `voting_powers` map must contain a voting -/// power for all `(Address, BlockHeight)`s that occur in `update`. +/// the event was newly seen. +/// +/// The `voting_powers` map must contain a voting power for all +/// `(Address, BlockHeight)`s that occur in `update`. fn apply_update( storage: &mut Storage, update: EthMsgUpdate, From ab69389a7dfd519a4cb0be5a2b857fffffff40b7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 15:50:34 +0000 Subject: [PATCH 1824/1995] Replace VoteInfo -> NewVotes --- shared/src/ledger/protocol/transactions/votes/update.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 9bf5a7ed74..7e4323c7f6 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -18,7 +18,7 @@ pub(in super::super) struct NewVotes { } impl NewVotes { - /// Constructs a new [`VoteInfo`]. For all `votes` provided, a corresponding + /// Constructs a new [`NewVotes`]. For all `votes` provided, a corresponding /// [`FractionalVotingPower`] must be provided in `voting_powers` also, /// otherwise an error will be returned. pub fn new( @@ -48,7 +48,7 @@ impl NewVotes { self.inner.keys().cloned().collect() } - /// Consumes `self` and returns a `VoteInfo` with any addresses from + /// Consumes `self` and returns a [`NewVotes`] with any addresses from /// `voters` removed, as well as the set of addresses that were actually /// removed. Useful for removing voters who have already voted for /// something. From 1cd9bba008c35cdcb3dc9ad520abc8b1bcf94985 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 15:55:32 +0000 Subject: [PATCH 1825/1995] Remove refences to "event" from update.rs --- shared/src/ledger/protocol/transactions/votes/update.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 7e4323c7f6..70e5f94b5b 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -100,7 +100,7 @@ where tracing::info!( ?keys.prefix, validators = ?vote_info.voters(), - "Recording validators as having voted for this event" + "Calculating validators' votes applied to an existing tally" ); let tally_pre = super::storage::read(store, keys)?; if tally_pre.seen { @@ -124,12 +124,12 @@ where if tally_post.seen { tracing::info!( ?keys.prefix, - "Event has been seen by a quorum of validators", + "Tally has been seen by a quorum of validators", ); } else { tracing::debug!( ?keys.prefix, - "Event is not yet seen by a quorum of validators", + "Tally is not yet seen by a quorum of validators", ); }; @@ -202,8 +202,7 @@ mod tests { mod helpers { use super::*; - /// Returns an arbitrary piece of data that can be tallied, and the keys - /// for it. + /// Returns an arbitrary piece of data that can have votes tallied against it. pub(super) fn arbitrary_event() -> EthereumEvent { EthereumEvent::TransfersToNamada { nonce: 0.into(), From 2aa1991cbcb548e0cc626069b1451d2a9167113e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 16:12:09 +0000 Subject: [PATCH 1826/1995] Interpolate variables in error string --- shared/src/ledger/protocol/transactions/votes/update.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 70e5f94b5b..47bdb54cb7 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -152,9 +152,8 @@ fn apply(tally: &Tally, vote_info: NewVotes) -> Result { seen_by_post.insert(validator.clone(), vote_height) { return Err(eyre!( - "Validator {} had already voted at height {}", - validator, - already_voted_height, + "Validator {validator} had already voted at height \ + {already_voted_height}", )); }; voting_power_post += voting_power; @@ -202,7 +201,8 @@ mod tests { mod helpers { use super::*; - /// Returns an arbitrary piece of data that can have votes tallied against it. + /// Returns an arbitrary piece of data that can have votes tallied + /// against it. pub(super) fn arbitrary_event() -> EthereumEvent { EthereumEvent::TransfersToNamada { nonce: 0.into(), From c0654d871f5360de686d9d8f490a2586e98f08bd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 24 Nov 2022 16:14:13 +0000 Subject: [PATCH 1827/1995] Split NewVotes::new docstring --- shared/src/ledger/protocol/transactions/votes/update.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/shared/src/ledger/protocol/transactions/votes/update.rs index 47bdb54cb7..2a1e4fcd20 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/shared/src/ledger/protocol/transactions/votes/update.rs @@ -18,9 +18,11 @@ pub(in super::super) struct NewVotes { } impl NewVotes { - /// Constructs a new [`NewVotes`]. For all `votes` provided, a corresponding - /// [`FractionalVotingPower`] must be provided in `voting_powers` also, - /// otherwise an error will be returned. + /// Constructs a new [`NewVotes`]. + /// + /// For all `votes` provided, a corresponding [`FractionalVotingPower`] must + /// be provided in `voting_powers` also, otherwise an error will be + /// returned. pub fn new( votes: Votes, voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, From bd93396e4a852e64aabadb5dc68bf113d600f710 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 15:33:58 +0000 Subject: [PATCH 1828/1995] Mod docstring improvements --- .../prepare_proposal/block_space_alloc.rs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 027b875f33..ae5bc42d37 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -16,17 +16,19 @@ //! In the current implementation, we allocate space for transactions //! in the following order of preference: //! -//! - First, we allocate space for DKG decrypted txs. -//! - Next, we allocate space for protocol txs. Protocol txs get 1/3 of the -//! block space allotted to them. -//! - Finally, we allocate space for DKG encrypted txs. +//! - First, we allot space for DKG decrypted txs. Decrypted txs take up as much +//! space as needed. We will see, shortly, why in practice this is fine. +//! - Next, we allot space for protocol txs. Protocol txs get half of the +//! remaining block space allotted to them. +//! - Finally, we allot space for DKG encrypted txs. We allow DKG encrypted txs +//! to take up at most 1/3 of the total block space. //! - If any space remains, we try to fit other smaller txs in the block. //! -//! Since decrypted txs will utilize at most as much space as -//! encrypted txs will utilize, and we allocate 1/3 of space -//! that has already been taken up by decrypted txs to protocol -//! txs, we roughly divide the block space in 3 for each kind -//! of major tx type. +//! Since at some fixed height `H` decrypted txs only take up as +//! much space as the encrypted txs from height `H - 1`, and we +//! restrict the space of encrypted txs to at most 1/3 of the +//! total block space, we roughly divide the Tendermint block +//! space in 3, for each major type of tx. pub mod states; From d217d7fb4bddca4765df36eb87f66b3c17fe810c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 16:15:38 +0000 Subject: [PATCH 1829/1995] Refactor AllocStatus into a Result err type --- .../prepare_proposal/block_space_alloc.rs | 24 ++++++++++--------- .../block_space_alloc/states.rs | 4 ++-- .../block_space_alloc/states/decrypted_txs.rs | 4 ++-- .../block_space_alloc/states/encrypted_txs.rs | 8 +++---- .../block_space_alloc/states/protocol_txs.rs | 4 ++-- .../block_space_alloc/states/remaining_txs.rs | 6 ++--- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index ae5bc42d37..defb4afc62 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -56,16 +56,18 @@ use std::marker::PhantomData; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; -/// All status responses from trying to allocate block space for a tx. +/// Block space allocation failure status responses. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum AllocStatus<'tx> { - /// The transaction is able to be included in the current block. - Accepted, +pub enum AllocFailure { /// The transaction can only be included in an upcoming block. - Rejected { tx: &'tx [u8], space_left: u64 }, + /// + /// We return the space left in the tx bin for logging purposes. + Rejected { bin_space_left: u64 }, /// The transaction would overflow the allotted bin space, /// therefore it needs to be handled separately. - OverflowsBin { tx: &'tx [u8], bin_size: u64 }, + /// + /// We return the size of the tx bin for logging purposes. + OverflowsBin { bin_size: u64 }, } /// Allotted space for a batch of transactions in some proposed block, @@ -199,19 +201,19 @@ impl TxBin { /// /// Signal the caller if the tx is larger than its max /// allotted bin space. - fn try_dump<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_dump(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { let tx_len = tx.len() as u64; if tx_len > self.allotted_space_in_bytes { let bin_size = self.allotted_space_in_bytes; - return AllocStatus::OverflowsBin { tx, bin_size }; + return Err(AllocFailure::OverflowsBin { bin_size }); } let occupied = self.occupied_space_in_bytes + tx_len; if occupied <= self.allotted_space_in_bytes { self.occupied_space_in_bytes = occupied; - AllocStatus::Accepted + Ok(()) } else { - let space_left = self.space_left_in_bytes(); - AllocStatus::Rejected { tx, space_left } + let bin_space_left = self.space_left_in_bytes(); + Err(AllocFailure::Rejected { bin_space_left }) } } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 5e076858f4..4be3883950 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -28,7 +28,7 @@ mod encrypted_txs; mod protocol_txs; mod remaining_txs; -use super::AllocStatus; +use super::AllocFailure; #[allow(unused_imports)] use super::BlockSpaceAllocator; @@ -85,7 +85,7 @@ pub enum WithoutEncryptedTxs {} /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. pub trait TryAlloc { /// Try to allocate space for a new transaction. - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx>; + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure>; } /// Represents a state transition in the [`BlockSpaceAllocator`] state machine. diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs index 60a2bec2c9..ec49284e19 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/decrypted_txs.rs @@ -1,13 +1,13 @@ use std::marker::PhantomData; -use super::super::{threshold, AllocStatus, BlockSpaceAllocator, TxBin}; +use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; use super::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, }; impl TryAlloc for BlockSpaceAllocator { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { self.decrypted_txs.try_dump(tx) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index f88f7f55aa..f030648567 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use super::super::{AllocStatus, BlockSpaceAllocator}; +use super::super::{AllocFailure, BlockSpaceAllocator}; use super::{ BuildingEncryptedTxBatch, FillingRemainingSpace, NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, @@ -10,7 +10,7 @@ impl TryAlloc for BlockSpaceAllocator> { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { self.encrypted_txs.try_dump(tx) } } @@ -30,8 +30,8 @@ impl TryAlloc for BlockSpaceAllocator> { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { - AllocStatus::Rejected { tx, space_left: 0 } + fn try_alloc(&mut self, _tx: &[u8]) -> Result<(), AllocFailure> { + Err(AllocFailure::Rejected { bin_space_left: 0 }) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs index a5c1f3931c..48194047a8 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/protocol_txs.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use super::super::{threshold, AllocStatus, BlockSpaceAllocator, TxBin}; +use super::super::{threshold, AllocFailure, BlockSpaceAllocator, TxBin}; use super::{ BuildingEncryptedTxBatch, BuildingProtocolTxBatch, NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, @@ -8,7 +8,7 @@ use super::{ impl TryAlloc for BlockSpaceAllocator { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { self.protocol_txs.try_dump(tx) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index d9210edf79..5f3781cd55 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -1,11 +1,11 @@ -use super::super::{AllocStatus, BlockSpaceAllocator}; +use super::super::{AllocFailure, BlockSpaceAllocator}; use super::{ FillingRemainingSpace, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, }; impl TryAlloc for BlockSpaceAllocator> { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { self.block.try_dump(tx) } } @@ -17,7 +17,7 @@ impl TryAlloc for BlockSpaceAllocator> { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { self.block.try_dump(tx) } } From 15bb7a82521dacadec339cc1a43732223c0354d4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 28 Nov 2022 16:38:40 +0000 Subject: [PATCH 1830/1995] Fix tx bin tests --- .../shell/prepare_proposal/block_space_alloc.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index be43d3d00e..c23fdb6824 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -306,7 +306,7 @@ mod tests { // make sure we can't dump any new decrypted txs in the bin assert_matches!( bins.try_alloc(b"arbitrary tx bytes"), - AllocStatus::Rejected { .. } + Err(AllocFailure::Rejected { .. }) ); } @@ -343,10 +343,7 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in decrypted_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } let bins = RefCell::new(bins.into_inner().next_state()); @@ -356,10 +353,7 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in protocol_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } let bins = @@ -370,10 +364,7 @@ mod tests { new_size < bin.allotted_space_in_bytes }); for tx in encrypted_txs { - assert_matches!( - bins.borrow_mut().try_alloc(&tx), - AllocStatus::Accepted - ); + assert!(bins.borrow_mut().try_alloc(&tx).is_ok()); } } From bb98133a2de3b9b0ec145a9b5e968585bb6e8107 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 29 Nov 2022 10:39:16 +0100 Subject: [PATCH 1831/1995] [fix]: Fixed the wasm for adding transfers to the bridge pool --- apps/src/lib/node/ledger/shell/init_chain.rs | 1 + apps/src/lib/wasm_loader/mod.rs | 1 - shared/src/types/storage.rs | 34 +++++++++----------- wasm/wasm_source/src/tx_bridge_pool.rs | 2 ++ 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d98449d9e5..77adab17df 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -70,6 +70,7 @@ where genesis.gov_params.init_storage(&mut self.storage); // configure the Ethereum bridge if the configuration is set. if let Some(config) = genesis.ethereum_bridge_params { + tracing::debug!("Initializing Ethereum bridge storage."); config.init_storage(&mut self.storage); } diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index fa0ea6448f..e82bb92452 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -109,7 +109,6 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { let checksums_path = wasm_directory .as_ref() .join(crate::config::DEFAULT_WASM_CHECKSUMS_FILE); - tracing::info!("WASM PATH: {}", checksums_path.to_string_lossy()); // If the checksums file doesn't exists ... if tokio::fs::canonicalize(&checksums_path).await.is_err() { tokio::fs::create_dir_all(&wasm_directory).await.unwrap(); diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 5ced875453..7525c35044 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -264,29 +264,25 @@ pub enum MerkleValue { BridgePoolTransfer(PendingTransfer), } -impl From for MerkleValue -where - T: AsRef<[u8]>, -{ - fn from(bytes: T) -> Self { - Self::Bytes(bytes.as_ref().to_owned()) - } -} - -impl From for MerkleValue { - fn from(transfer: PendingTransfer) -> Self { - Self::BridgePoolTransfer(transfer) +impl MerkleValue { + /// Byte length of the value + pub fn len(&self) -> usize { + match self { + MerkleValue::Bytes(bytes) => bytes.len(), + MerkleValue::BridgePoolTransfer(transfer) => transfer + .try_to_vec() + .expect("Serializing a PendingTransfer should not fail.") + .len(), + } } -} -impl MerkleValue { - /// Get the natural byte representation of the value + /// Byte representation of a value in the Merkle tree pub fn to_bytes(self) -> Vec { match self { - Self::Bytes(bytes) => bytes, - Self::BridgePoolTransfer(transfer) => { - transfer.try_to_vec().unwrap() - } + MerkleValue::Bytes(bytes) => bytes, + MerkleValue::BridgePoolTransfer(transfer) => transfer + .try_to_vec() + .expect("Serializing a PendingTransfer should not fail."), } } } diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 31c9388def..d127236abd 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -61,8 +61,10 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { } fn native_erc20_address(ctx: &mut Ctx) -> EnvResult { + log_string("Trying to get wnam key"); let addr = ctx.read_bytes(&native_erc20_key()) .map_err(|_| Error::SimpleMessage("Could not read wNam key from storage"))? .unwrap(); + log_string("Got wnam key"); Ok(BorshDeserialize::try_from_slice(addr.as_slice()).unwrap()) } From adc91eb26efb591a5f45b3f0ea9af175b603d0f7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 10:05:58 +0000 Subject: [PATCH 1832/1995] Test if we divide the block space evenly for each kind of tx type --- .../prepare_proposal/block_space_alloc.rs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index c23fdb6824..46e65bdac4 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -268,6 +268,70 @@ mod tests { decrypted_txs: Vec, } + /// Check that at most 1/3 of the block space is + /// reserved for each kind of tx type, in the + /// allocator's common path. + #[test] + fn test_txs_are_evenly_split_across_block() { + const BLOCK_SIZE: u64 = 60; + + // reserve block space for decrypted txs + let mut alloc = BlockSpaceAllocator::init(BLOCK_SIZE); + + // assume we got ~1/3 encrypted txs at the prev block + assert!(alloc.try_alloc(&[0; 18]).is_ok()); + + // reserve block space for protocol txs + let mut alloc = alloc.next_state(); + + // the space we allotted to decrypted txs was shrunk to + // the total space we actually used up + assert_eq!(alloc.decrypted_txs.allotted_space_in_bytes, 18); + + // check that the allotted space for protocol txs is correct + assert_eq!(21, (BLOCK_SIZE - 18) / 2); + assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 21); + + // fill up the block space with protocol txs + assert!(alloc.try_alloc(&[0; 17]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; (21 - 17) + 1]), + Err(AllocFailure::Rejected { .. }) + ); + + // reserve block space for encrypted txs + let mut alloc = alloc.next_state_with_encrypted_txs(); + + // check that space was shrunk + assert_eq!(alloc.protocol_txs.allotted_space_in_bytes, 17); + + // check that we reserve at most 1/3 of the block space to + // encrypted txs + assert_eq!(25, BLOCK_SIZE - 17 - 18); + assert_eq!(20, BLOCK_SIZE / 3); + assert_eq!(alloc.encrypted_txs.allotted_space_in_bytes, 20); + + // fill up the block space with encrypted txs + assert!(alloc.try_alloc(&[0; 20]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + + // check that there is still remaining space left at the end + let mut alloc = alloc.next_state(); + let remaining_space = alloc.block.allotted_space_in_bytes + - alloc.block.occupied_space_in_bytes; + assert_eq!(remaining_space, 5); + + // fill up the remaining space + assert!(alloc.try_alloc(&[0; 5]).is_ok()); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + } + proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. From d70aaec5f12a5d701c1ad97ada6c5ec9ea9bf4f5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 10:32:42 +0000 Subject: [PATCH 1833/1995] Test if we reject encrypted txs when the block allocator state disallows them --- .../prepare_proposal/block_space_alloc.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index 46e65bdac4..b81e2f202c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -255,7 +255,10 @@ mod tests { use assert_matches::assert_matches; use proptest::prelude::*; - use super::states::{NextState, NextStateWithEncryptedTxs, TryAlloc}; + use super::states::{ + NextState, NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, + TryAlloc, + }; use super::*; use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; @@ -332,6 +335,19 @@ mod tests { ); } + // Test that we cannot include encrypted txs in a block + // when the state invariants banish them from inclusion. + #[test] + fn test_encrypted_txs_are_rejected() { + let alloc = BlockSpaceAllocator::init(1234); + let alloc = alloc.next_state(); + let mut alloc = alloc.next_state_without_encrypted_txs(); + assert_matches!( + alloc.try_alloc(&[0; 1]), + Err(AllocFailure::Rejected { .. }) + ); + } + proptest! { /// Check if we reject a tx when its respective bin /// capacity has been reached on a [`BlockSpaceAllocator`]. From 64c309d2baa94887907889c7b3fe1085c172428c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 11:05:47 +0000 Subject: [PATCH 1834/1995] Remaining block space should not be filled with encrypted txs --- .../block_space_alloc/states.rs | 13 +++++------- .../block_space_alloc/states/encrypted_txs.rs | 6 +++--- .../block_space_alloc/states/remaining_txs.rs | 20 ++++--------------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 4be3883950..02c29db673 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -4,7 +4,7 @@ //! //! # States //! -//! The state machine moves through the following state tree: +//! The state machine moves through the following state DAG: //! //! 1. [`BuildingDecryptedTxBatch`] - the initial state. In //! this state, we populate a block with DKG decrypted txs. @@ -20,8 +20,7 @@ //! encrypted txs in a block proposal. //! 4. [`FillingRemainingSpace`] - the fourth and final state. //! During this phase, we fill all remaining block space with arbitrary -//! transactions that haven't been included yet. This state supports the -//! same two modes of operation defined above. +//! protocol transactions that haven't been included in a block, yet. mod decrypted_txs; mod encrypted_txs; @@ -58,14 +57,12 @@ pub struct BuildingEncryptedTxBatch { /// The leader of the current Tendermint round is populating /// all remaining space in a block proposal with arbitrary -/// transactions. +/// protocol transactions that haven't been included in the +/// block, yet. /// /// For more info, read the module docs of /// [`crate::node::ledger::shell::prepare_proposal::block_space_alloc::states`]. -pub struct FillingRemainingSpace { - /// One of [`WithEncryptedTxs`] and [`WithoutEncryptedTxs`]. - _mode: Mode, -} +pub enum FillingRemainingSpace {} /// Allow block proposals to include encrypted txs. /// diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index f030648567..ae178d1f8a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -18,7 +18,7 @@ impl TryAlloc impl NextStateImpl for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator>; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { @@ -38,7 +38,7 @@ impl TryAlloc impl NextStateImpl for BlockSpaceAllocator> { - type Next = BlockSpaceAllocator>; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { @@ -49,7 +49,7 @@ impl NextStateImpl #[inline] fn next_state( mut alloc: BlockSpaceAllocator>, -) -> BlockSpaceAllocator> { +) -> BlockSpaceAllocator { alloc.encrypted_txs.shrink_to_fit(); // reserve space for any remaining txs diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs index 5f3781cd55..48f3a43df5 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/remaining_txs.rs @@ -1,23 +1,11 @@ use super::super::{AllocFailure, BlockSpaceAllocator}; -use super::{ - FillingRemainingSpace, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, -}; +use super::{FillingRemainingSpace, TryAlloc}; -impl TryAlloc for BlockSpaceAllocator> { - #[inline] - fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { - self.block.try_dump(tx) - } -} - -// TODO: limit txs that can go in the bins at this level? so we don't misuse -// the abstraction. it's not like we can't push encrypted txs into the bins, -// right now... -impl TryAlloc - for BlockSpaceAllocator> -{ +impl TryAlloc for BlockSpaceAllocator { #[inline] fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { + // NOTE: tx dispatching is done at at higher level, to prevent + // allocating space for encrypted txs here self.block.try_dump(tx) } } From 7d0cf091cc82277cd3e180a6f3f0302fbad960d6 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 11:07:19 +0000 Subject: [PATCH 1835/1995] Fix docstr --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index defb4afc62..ac0fbb3d97 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -22,7 +22,8 @@ //! remaining block space allotted to them. //! - Finally, we allot space for DKG encrypted txs. We allow DKG encrypted txs //! to take up at most 1/3 of the total block space. -//! - If any space remains, we try to fit other smaller txs in the block. +//! - If any space remains, we try to fit any leftover protocol txs in the +//! block. //! //! Since at some fixed height `H` decrypted txs only take up as //! much space as the encrypted txs from height `H - 1`, and we From b6b6b5b3e137d441ec3ac7bb9622d40022a3b8ec Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 11:25:16 +0000 Subject: [PATCH 1836/1995] Update index-set to 0.5.0 --- Cargo.lock | 4 ++-- apps/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f92f072187..7428a70d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3266,8 +3266,8 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "index-set" -version = "0.4.1" -source = "git+https://github.com/heliaxdev/index-set?tag=v0.4.1#32e81b025cfb2be32be861e7f488f628e5ce9ffb" +version = "0.5.0" +source = "git+https://github.com/heliaxdev/index-set?tag=v0.5.0#d3ee2073e0656240d8a497b71449d62fa3d9a530" [[package]] name = "indexmap" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index b24b41a09f..05156caf20 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -98,7 +98,7 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" -index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.4.1"} +index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.5.0"} itertools = "0.10.1" libc = "0.2.97" libloading = "0.7.2" From 8ba3f47b82a0b7a89948a2823365b459c8c3b05c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 12:58:33 +0000 Subject: [PATCH 1837/1995] PrepareProposal fixes --- .../lib/node/ledger/shell/prepare_proposal.rs | 275 +++++++++--------- .../block_space_alloc/states.rs | 13 +- .../block_space_alloc/states/encrypted_txs.rs | 15 +- 3 files changed, 146 insertions(+), 157 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 11ba2a4784..7bfba6385c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -19,10 +19,10 @@ use namada::types::vote_extensions::VoteExtensionDigest; use self::block_space_alloc::states::{ BuildingDecryptedTxBatch, BuildingProtocolTxBatch, - EncryptedTxBatchAllocator, NextState, NextStateWithEncryptedTxs, - NextStateWithoutEncryptedTxs, RemainingBatchAllocator, TryAlloc, + EncryptedTxBatchAllocator, FillingRemainingSpace, NextState, + NextStateWithEncryptedTxs, NextStateWithoutEncryptedTxs, TryAlloc, }; -use self::block_space_alloc::{AllocStatus, BlockSpaceAllocator}; +use self::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; use super::super::*; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] @@ -85,7 +85,7 @@ where // add mempool txs let (mut mempool_txs, alloc) = - self.build_mempool_txs(alloc, &mut tx_indices, &req.txs); + self.build_mempool_txs(alloc, &req.txs); txs.append(&mut mempool_txs); // fill up the remaining block space with @@ -197,40 +197,43 @@ where return (vec![], self.get_encrypted_txs_allocator(alloc)); } - let txs = deserialize_vote_extensions(txs, tx_indices).take_while(|tx_bytes| { - match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => true, - AllocStatus::Rejected { tx, space_left } => { - // TODO: maybe we should find a way to include - // validator set updates all the time. for instance, - // we could have recursive bins -> bin space within - // a bin is partitioned into yet more bins. so, we - // could have, say, 2/3 of the bin space available - // for eth events, and 1/3 available for valset - // upds - tracing::debug!( - ?tx, - space_left, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping protocol tx from the current proposal", - ); - false - } - AllocStatus::OverflowsBin { tx, bin_size } => { - // TODO: handle tx whose size is greater - // than bin size - tracing::warn!( - ?tx, - bin_size, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping large protocol tx from the current proposal", - ); - true - } - } - }) + let txs = deserialize_vote_extensions(txs, tx_indices).take_while(|tx_bytes| + alloc.try_alloc(&*tx_bytes) + .map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + // TODO: maybe we should find a way to include + // validator set updates all the time. for instance, + // we could have recursive bins -> bin space within + // a bin is partitioned into yet more bins. so, we + // could have, say, 2/3 of the bin space available + // for eth events, and 1/3 available for valset + // upds + tracing::debug!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping protocol tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large protocol tx from the current proposal", + ); + true + } + }, + |()| true, + ) + ) .collect(); (txs, self.get_encrypted_txs_allocator(alloc)) @@ -266,9 +269,8 @@ where fn build_mempool_txs( &mut self, _alloc: EncryptedTxBatchAllocator, - _tx_indices: &mut IndexSet, txs: &[TxBytes], - ) -> (Vec, RemainingBatchAllocator) { + ) -> (Vec, BlockSpaceAllocator) { // TODO(feature = "abcipp"): implement building batch of mempool txs todo!() } @@ -278,59 +280,52 @@ where fn build_mempool_txs( &mut self, mut alloc: EncryptedTxBatchAllocator, - tx_indices: &mut IndexSet, txs: &[TxBytes], - ) -> (Vec, RemainingBatchAllocator) { - let mut invalid_txs = IndexSet::default(); + ) -> (Vec, BlockSpaceAllocator) { let txs = txs .iter() - .enumerate() - .filter_map(|(index, tx_bytes)| { + .filter_map(|tx_bytes| { if let Ok(Ok(TxType::Wrapper(_))) = Tx::try_from(tx_bytes.as_slice()).map(process_tx) { - Some((index, tx_bytes.clone())) + Some(tx_bytes.clone()) } else { - // found invalid tx. mark it, so we do not include - // it in a block during `build_remaining_batch()` - invalid_txs.insert(index); None } }) - .take_while(|(index, tx_bytes)| match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => { - tx_indices.insert(*index); - true - } - AllocStatus::Rejected { tx, space_left } => { - tracing::debug!( - ?tx, - space_left, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping encrypted tx from the current proposal", - ); - false - } - AllocStatus::OverflowsBin { tx, bin_size } => { - // TODO: handle tx whose size is greater - // than bin size - tracing::warn!( - ?tx, - bin_size, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping large encrypted tx from the current proposal", - ); - true - } + .take_while(|tx_bytes| { + alloc.try_alloc(&*tx_bytes) + .map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::debug!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping encrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large encrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) }) - .map(|(_, tx_bytes)| tx_bytes) .collect(); let alloc = alloc.next_state(); - tx_indices.union(&invalid_txs); - (txs, alloc) } @@ -362,31 +357,35 @@ where .to_bytes() }) // TODO: make sure all txs are accepted - .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => true, - AllocStatus::Rejected { tx, space_left } => { - // TODO: handle rejected txs - tracing::warn!( - ?tx, - space_left, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping decrypted tx from the current proposal", - ); - false - } - AllocStatus::OverflowsBin { tx, bin_size } => { - // TODO: handle tx whose size is greater - // than bin size - tracing::warn!( - ?tx, - bin_size, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping large decrypted tx from the current proposal", - ); - true - } + .take_while(|tx_bytes| { + alloc.try_alloc(&*tx_bytes).map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + // TODO: handle rejected txs + tracing::warn!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping decrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large decrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) }) .collect(); let alloc = alloc.next_state(); @@ -398,43 +397,45 @@ where /// remaining space of the [`BlockSpaceAllocator`]. fn build_remaining_batch( &mut self, - mut alloc: RemainingBatchAllocator, + mut alloc: BlockSpaceAllocator, tx_indices: &IndexSet, txs: Vec, ) -> Vec { - // TODO: do not allocate encrypted txs if the allocator - // state does not allow it get_remaining_txs(tx_indices, txs) - .take_while(|tx_bytes| match alloc.try_alloc(&*tx_bytes) { - AllocStatus::Accepted => true, - AllocStatus::Rejected { tx, space_left } => { - tracing::debug!( - ?tx, - space_left, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping tx from the current proposal", - ); - false - } - AllocStatus::OverflowsBin { tx, bin_size } => { - // TODO: handle tx whose size is greater - // than bin size - tracing::warn!( - ?tx, - bin_size, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping large tx from the current proposal", - ); - true - } + .take_while(|tx_bytes| { + alloc.try_alloc(&*tx_bytes).map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::debug!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + // TODO: handle tx whose size is greater + // than bin size + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large tx from the current proposal", + ); + true + } + }, + |()| true, + ) }) .collect() } } -/// Return a list of the transactions that haven't +/// Return a list of the protocol transactions that haven't /// been marked for inclusion in the block, yet. fn get_remaining_txs( tx_indices: &IndexSet, @@ -448,10 +449,14 @@ fn get_remaining_txs( // in ascending order if hints::likely(Some(index) == skip) { skip = skip_list.next(); - None - } else { - Some(tx) + return None; + } + if let Ok(Ok(TxType::Wrapper(_))) = + Tx::try_from(&tx[..]).map(process_tx) + { + return None; } + Some(tx) }) } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs index 819916a3a0..dca808c09a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states.rs @@ -27,7 +27,7 @@ mod encrypted_txs; mod protocol_txs; mod remaining_txs; -use super::{AllocStatus, BlockSpaceAllocator}; +use super::{AllocFailure, BlockSpaceAllocator}; /// Convenience wrapper for a [`BlockSpaceAllocator`] state that allocates /// encrypted transactions. @@ -40,17 +40,6 @@ pub enum EncryptedTxBatchAllocator { ), } -/// Convenience wrapper for a [`BlockSpaceAllocator`] state that fills up the -/// remaining block space. -pub enum RemainingBatchAllocator { - WithEncryptedTxs( - BlockSpaceAllocator>, - ), - WithoutEncryptedTxs( - BlockSpaceAllocator>, - ), -} - /// The leader of the current Tendermint round is building /// a new batch of DKG decrypted transactions. /// diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs index 4b1aee8f56..019de0a6b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc/states/encrypted_txs.rs @@ -3,8 +3,7 @@ use std::marker::PhantomData; use super::super::{AllocFailure, BlockSpaceAllocator}; use super::{ BuildingEncryptedTxBatch, EncryptedTxBatchAllocator, FillingRemainingSpace, - NextStateImpl, RemainingBatchAllocator, TryAlloc, WithEncryptedTxs, - WithoutEncryptedTxs, + NextStateImpl, TryAlloc, WithEncryptedTxs, WithoutEncryptedTxs, }; impl TryAlloc @@ -76,7 +75,7 @@ fn next_state( impl TryAlloc for EncryptedTxBatchAllocator { #[inline] - fn try_alloc<'tx>(&mut self, tx: &'tx [u8]) -> AllocStatus<'tx> { + fn try_alloc(&mut self, tx: &[u8]) -> Result<(), AllocFailure> { match self { EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { state.try_alloc(tx) @@ -91,20 +90,16 @@ impl TryAlloc for EncryptedTxBatchAllocator { } impl NextStateImpl for EncryptedTxBatchAllocator { - type Next = RemainingBatchAllocator; + type Next = BlockSpaceAllocator; #[inline] fn next_state_impl(self) -> Self::Next { match self { EncryptedTxBatchAllocator::WithEncryptedTxs(state) => { - RemainingBatchAllocator::WithEncryptedTxs( - state.next_state_impl(), - ) + state.next_state_impl() } EncryptedTxBatchAllocator::WithoutEncryptedTxs(state) => { - RemainingBatchAllocator::WithoutEncryptedTxs( - state.next_state_impl(), - ) + state.next_state_impl() } } } From 4131cc728171fab18559f7f1f402ee0a81f6a58a Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 29 Nov 2022 14:18:15 +0100 Subject: [PATCH 1838/1995] [feat]: Removed MerkleValue type. --- shared/src/ledger/queries/shell.rs | 56 ++++++++++++++---------- shared/src/ledger/storage/merkle_tree.rs | 23 +++++----- shared/src/ledger/storage/mod.rs | 12 +++-- shared/src/ledger/storage/traits.rs | 56 ++++++++---------------- shared/src/types/storage.rs | 43 ------------------ 5 files changed, 68 insertions(+), 122 deletions(-) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 5523eac0de..335f9b8203 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -24,9 +24,7 @@ use crate::types::keccak::KeccakHash; use crate::types::storage::MembershipProof::BridgePool; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::storage::TxIndex; -use crate::types::storage::{ - self, BlockResults, Epoch, MerkleValue, PrefixValue, -}; +use crate::types::storage::{self, BlockResults, Epoch, PrefixValue}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::transaction::TxResult; @@ -254,7 +252,7 @@ where .storage .get_existence_proof( &storage_key, - value.clone().into(), + value.clone(), request.height, ) .into_storage_result()?; @@ -311,7 +309,7 @@ where for PrefixValue { key, value } in &data { let mut proof = ctx .storage - .get_existence_proof(key, value.clone().into(), request.height) + .get_existence_proof(key, value.clone(), request.height) .into_storage_result()?; ops.append(&mut proof.ops); } @@ -455,16 +453,12 @@ where ); // from the hashes of the transfers, get the actual values. let mut missing_hashes = vec![]; - let (keys, values): (Vec<_>, Vec) = transfer_hashes + let (keys, values): (Vec<_>, Vec<_>) = transfer_hashes .iter() .filter_map(|hash| { let key = get_key_from_hash(hash); match ctx.storage.read(&key) { - Ok((Some(bytes), _)) => { - PendingTransfer::try_from_slice(&bytes[..]) - .ok() - .map(|transfer| (key, transfer)) - } + Ok((Some(bytes), _)) => Some((key, bytes)), _ => { missing_hashes.push(hash); None @@ -483,10 +477,7 @@ where ))); } // get the membership proof - match tree.get_sub_tree_existence_proof( - &keys, - values.into_iter().map(MerkleValue::from).collect(), - ) { + match tree.get_sub_tree_existence_proof(&keys, values) { Ok(BridgePool(proof)) => { let data = EncodeCell::new(&RelayProof { // TODO: use actual validators @@ -671,7 +662,10 @@ mod test { // write a transfer into the bridge pool client .storage - .write(&get_pending_key(&transfer), transfer.clone()) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // commit the changes and increase block height @@ -709,7 +703,10 @@ mod test { // write a transfer into the bridge pool client .storage - .write(&get_pending_key(&transfer), transfer.clone()) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // commit the changes and increase block height @@ -725,7 +722,10 @@ mod test { transfer2.transfer.amount = 1.into(); client .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) + .write( + &get_pending_key(&transfer2), + transfer2.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // commit the changes and increase block height @@ -763,7 +763,10 @@ mod test { // write a transfer into the bridge pool client .storage - .write(&get_pending_key(&transfer), transfer.clone()) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create a signed Merkle root for this pool @@ -782,7 +785,10 @@ mod test { transfer2.transfer.amount = 1.into(); client .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) + .write( + &get_pending_key(&transfer2), + transfer2.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // add the signature for the pool at the previous block height @@ -853,7 +859,10 @@ mod test { // write a transfer into the bridge pool client .storage - .write(&get_pending_key(&transfer), transfer.clone()) + .write( + &get_pending_key(&transfer), + transfer.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // create a signed Merkle root for this pool @@ -872,7 +881,10 @@ mod test { transfer2.transfer.amount = 1.into(); client .storage - .write(&get_pending_key(&transfer2), transfer2.clone()) + .write( + &get_pending_key(&transfer2), + transfer2.try_to_vec().expect("Test failed"), + ) .expect("Test failed"); // add the signature for the pool at the previous block height diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 9ade2555e0..e0b49f2db4 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -26,8 +26,7 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::keccak::KeccakHash; use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, - StringKey, TreeBytes, + DbKeySeg, Error as StorageError, Key, MembershipProof, StringKey, TreeBytes, }; #[allow(missing_docs)] @@ -315,9 +314,11 @@ impl MerkleTree { &mut self, store_type: &StoreType, key: &Key, - value: MerkleValue, + value: impl AsRef<[u8]>, ) -> Result<()> { - let sub_root = self.tree_mut(store_type).subtree_update(key, value)?; + let sub_root = self + .tree_mut(store_type) + .subtree_update(key, value.as_ref())?; // 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()); @@ -333,13 +334,9 @@ impl MerkleTree { } /// Update the tree with the given key and value - pub fn update( - &mut self, - key: &Key, - value: impl Into, - ) -> Result<()> { + 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, &sub_key, value.into()) + self.update_tree(&store_type, &sub_key, value) } /// Delete the value corresponding to the given key @@ -376,7 +373,7 @@ impl MerkleTree { pub fn get_sub_tree_existence_proof( &self, keys: &[Key], - values: Vec, + values: Vec>, ) -> Result { let first_key = keys.iter().next().ok_or_else(|| { Error::InvalidMerkleKey( @@ -733,7 +730,7 @@ mod test { let proof = match tree .get_sub_tree_existence_proof( std::array::from_ref(&ibc_key), - vec![ibc_val.clone().into()], + vec![ibc_val.clone()], ) .unwrap() { @@ -792,7 +789,7 @@ mod test { let proof = match tree .get_sub_tree_existence_proof( std::array::from_ref(&pos_key), - vec![pos_val.clone().into()], + vec![pos_val.clone()], ) .unwrap() { diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 2a3ab2200d..34acc656e6 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -57,7 +57,7 @@ use crate::types::key::dkg_session_keys::DkgPublicKey; use crate::types::storage::TxQueue; use crate::types::storage::{ BlockHash, BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, - MembershipProof, MerkleValue, TxIndex, BLOCK_HASH_LENGTH, + MembershipProof, TxIndex, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; use crate::types::token::Amount; @@ -532,13 +532,11 @@ where pub fn write( &mut self, key: &Key, - value: impl Into, + value: impl AsRef<[u8]>, ) -> Result<(u64, i64)> { - let value: MerkleValue = value.into(); tracing::debug!("storage write key {}", key,); - self.block.tree.update(key, value.clone())?; - - let bytes = value.to_bytes(); + let bytes = value.as_ref(); + self.block.tree.update(key, bytes)?; let gas = key.len() + bytes.len(); let size_diff = self.db.write_subspace_val(self.block.height, key, bytes)?; @@ -621,7 +619,7 @@ where pub fn get_existence_proof( &self, key: &Key, - value: MerkleValue, + value: Vec, height: BlockHeight, ) -> Result { if height >= self.get_block_height().0 { diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 5ff9f7bb59..9ca7798832 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -5,6 +5,7 @@ use std::fmt; use arse_merkle_tree::traits::{Hasher, Value}; use arse_merkle_tree::{Key as TreeKey, H256}; +use borsh::BorshDeserialize; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof}; use sha2::{Digest, Sha256}; @@ -12,10 +13,9 @@ use sha2::{Digest, Sha256}; use super::merkle_tree::{Amt, Error, Smt}; use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; +use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; -use crate::types::storage::{ - Key, MembershipProof, MerkleValue, StringKey, TreeBytes, -}; +use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; /// Trait for reading from a merkle tree that is a sub-tree /// of the global merkle tree. @@ -26,7 +26,7 @@ pub trait SubTreeRead { fn subtree_membership_proof( &self, keys: &[Key], - values: Vec, + values: Vec>, ) -> Result; } @@ -37,7 +37,7 @@ pub trait SubTreeWrite { fn subtree_update( &mut self, key: &Key, - value: MerkleValue, + value: &[u8], ) -> Result; /// Delete a key from the sub-tree fn subtree_delete(&mut self, key: &Key) -> Result; @@ -54,16 +54,13 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { fn subtree_membership_proof( &self, keys: &[Key], - mut values: Vec, + mut values: Vec>, ) -> Result { if keys.len() != 1 || values.len() != 1 { return Err(Error::Ics23MultiLeaf); } let key: &Key = &keys[0]; - let value = match values.remove(0) { - MerkleValue::Bytes(b) => b, - _ => return Err(Error::InvalidValue), - }; + let value = values.remove(0); let cp = self.membership_proof(&H::hash(key.to_string()).into())?; // Replace the values and the leaf op for the verification match cp.proof.expect("The proof should exist") { @@ -86,12 +83,9 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { fn subtree_update( &mut self, key: &Key, - value: MerkleValue, + value: &[u8], ) -> Result { - let value = match value { - MerkleValue::Bytes(bytes) => H::hash(bytes.as_slice()), - _ => return Err(Error::InvalidValue), - }; + let value = H::hash(value); self.update(H::hash(key.to_string()).into(), value.into()) .map(Hash::from) .map_err(|err| Error::MerkleTree(err.to_string())) @@ -117,7 +111,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { fn subtree_membership_proof( &self, keys: &[Key], - _: Vec, + _: Vec>, ) -> Result { if keys.len() != 1 { return Err(Error::Ics23MultiLeaf); @@ -144,13 +138,10 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { fn subtree_update( &mut self, key: &Key, - value: MerkleValue, + value: &[u8], ) -> Result { let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; - let value = match value { - MerkleValue::Bytes(bytes) => TreeBytes::from(bytes), - _ => return Err(Error::InvalidValue), - }; + let value = TreeBytes::from(value.as_ref().to_owned()); self.update(key, value) .map(Into::into) .map_err(|err| Error::MerkleTree(err.to_string())) @@ -174,13 +165,12 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { fn subtree_membership_proof( &self, _: &[Key], - values: Vec, + values: Vec>, ) -> Result { let values = values - .into_iter() - .filter_map(|val| match val { - MerkleValue::BridgePoolTransfer(transfer) => Some(transfer), - _ => None, + .iter() + .filter_map(|val| { + PendingTransfer::try_from_slice(val.as_slice()).ok() }) .collect(); self.get_membership_proof(values) @@ -190,17 +180,9 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { } impl<'a> SubTreeWrite for &'a mut BridgePoolTree { - fn subtree_update( - &mut self, - key: &Key, - value: MerkleValue, - ) -> Result { - if let MerkleValue::BridgePoolTransfer(_) = value { - self.insert_key(key) - .map_err(|err| Error::MerkleTree(err.to_string())) - } else { - Err(Error::InvalidValue) - } + fn subtree_update(&mut self, key: &Key, _: &[u8]) -> Result { + self.insert_key(key) + .map_err(|err| Error::MerkleTree(err.to_string())) } fn subtree_delete(&mut self, key: &Key) -> Result { diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index a4a7d7ed74..78cdf912a0 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -20,7 +20,6 @@ use crate::bytes::ByteBuf; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolProof; use crate::ledger::storage::IBC_KEY_LIMIT; use crate::types::address::{self, Address}; -use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; @@ -348,48 +347,6 @@ impl FromStr for Key { } } -/// An enum representing the different types of values -/// that can be passed into Anoma's storage. -/// -/// This is a multi-store organized as -/// several Merkle trees, each of which is -/// responsible for understanding how to parse -/// this value. -#[derive(Debug, Clone)] -pub enum MerkleValue { - /// raw bytes - Bytes(Vec), - /// A transfer to be put in the Ethereum bridge pool. - BridgePoolTransfer(PendingTransfer), -} - -impl From for MerkleValue -where - T: AsRef<[u8]>, -{ - fn from(bytes: T) -> Self { - Self::Bytes(bytes.as_ref().to_owned()) - } -} - -impl From for MerkleValue { - fn from(transfer: PendingTransfer) -> Self { - Self::BridgePoolTransfer(transfer) - } -} - -impl MerkleValue { - /// Get the natural byte representation of the value - pub fn to_bytes(self) -> Vec { - match self { - Self::Bytes(bytes) => bytes, - Self::BridgePoolTransfer(transfer) => { - transfer.try_to_vec().unwrap() - } - } - } -} - /// Storage keys that are utf8 encoded strings #[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { From 5cd6449b680cb671a081f4bd76d44a952e3d55ab Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 13:49:48 +0000 Subject: [PATCH 1839/1995] Fixing broken unit tests --- .../lib/node/ledger/shell/prepare_proposal.rs | 73 +++++++++++++------ 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7bfba6385c..5cec9c6469 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -451,12 +451,12 @@ fn get_remaining_txs( skip = skip_list.next(); return None; } - if let Ok(Ok(TxType::Wrapper(_))) = + if let Ok(Ok(TxType::Protocol(_))) = Tx::try_from(&tx[..]).map(process_tx) { - return None; + return Some(tx); } - Some(tx) + None }) } @@ -509,14 +509,47 @@ mod test_prepare_proposal { // https://github.com/tendermint/tendermint/blob/v0.37.x/spec/abci/abci%2B%2B_app_requirements.md#blockparamsmaxbytes const MAX_TM_BLK_SIZE: i64 = 100 << 20; + /// Extract an [`ethereum_events::SignedVext`], from a set of + /// serialized [`TxBytes`]. + #[cfg(not(feature = "abcipp"))] + fn extract_eth_events_vext( + tx_bytes: TxBytes, + ) -> ethereum_events::SignedVext { + let got = Tx::try_from(&tx_bytes[..]).unwrap(); + let got_signed_tx = + SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); + let protocol_tx = + TxType::try_from_slice(&got_signed_tx.data.unwrap()[..]).unwrap(); + let protocol_tx = match protocol_tx { + TxType::Protocol(protocol_tx) => protocol_tx.tx, + _ => panic!("Test failed"), + }; + match protocol_tx { + ProtocolTxType::EthEventsVext(ext) => ext, + _ => panic!("Test failed"), + } + } + /// Test if [`get_remaining_txs`] is working as expected. #[test] fn test_get_remaining_txs() { + // TODO(feature = "abcipp"): use a different tx type here + fn bertha_ext(at_height: u64) -> TxBytes { + let key = wallet::defaults::bertha_keypair(); + let ext = ethereum_events::Vext::empty( + at_height.into(), + wallet::defaults::bertha_address(), + ) + .sign(&key); + ProtocolTxType::EthEventsVext(ext).sign(&key).to_bytes() + } + let excluded_indices = [0, 1, 3, 5, 7]; - let all_txs: Vec<_> = (0..10).map(|tx_bytes| vec![tx_bytes]).collect(); + let all_txs: Vec<_> = (0..10).map(bertha_ext).collect(); let expected_txs: Vec<_> = [2, 4, 6, 8, 9] .into_iter() - .map(|tx_bytes| vec![tx_bytes]) + .map(bertha_ext) + .map(extract_eth_events_vext) .collect(); let set = { @@ -527,7 +560,9 @@ mod test_prepare_proposal { s }; - let got_txs: Vec<_> = get_remaining_txs(&set, all_txs).collect(); + let got_txs: Vec<_> = get_remaining_txs(&set, all_txs) + .map(extract_eth_events_vext) + .collect(); assert_eq!(expected_txs, got_txs); } @@ -575,6 +610,15 @@ mod test_prepare_proposal { /// Test that if a tx from the mempool is not a /// WrapperTx type, it is not included in the /// proposed block. + // TODO: remove this test after CheckTx implements + // filtering of invalid txs; otherwise, we would have + // needed to return invalid txs from PrepareProposal, + // for these to get removed from a node's mempool. + // not returning invalid txs from PrepareProposal is + // a DoS vector, because the mempool will slowly fill + // up with garbage. luckily, Tendermint implements a + // mempool eviction policy, but honest client's txs + // may get lost in the process #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); @@ -927,21 +971,7 @@ mod test_prepare_proposal { assert_eq!(rsp.txs.len(), 1); let tx_bytes = rsp.txs.remove(0); - let got = Tx::try_from(&tx_bytes[..]).unwrap(); - let got_signed_tx = - SignedTxData::try_from_slice(&got.data.unwrap()[..]).unwrap(); - let protocol_tx = - TxType::try_from_slice(&got_signed_tx.data.unwrap()[..]) - .unwrap(); - let protocol_tx = match protocol_tx { - TxType::Protocol(protocol_tx) => protocol_tx.tx, - _ => panic!("Test failed"), - }; - - match protocol_tx { - ProtocolTxType::EthEventsVext(ext) => ext, - _ => panic!("Test failed"), - } + extract_eth_events_vext(tx_bytes) }; assert_eq!(rsp_ext, ext); @@ -1075,6 +1105,7 @@ mod test_prepare_proposal { /// Test that if an error is encountered while /// trying to process a tx from the mempool, /// we simply exclude it from the proposal + // TODO: see note on `test_prepare_proposal_rejects_non_wrapper_tx` #[test] fn test_error_in_processing_tx() { let (mut shell, _recv, _) = test_utils::setup_at_height(3u64); From 88c19907db8101faf5019fbec607ec5a03262921 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 13:54:34 +0000 Subject: [PATCH 1840/1995] Use the vec index set for faster insert ops --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 12 ++++++------ apps/src/lib/node/ledger/shell/vote_extensions.rs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 5cec9c6469..3c43418300 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -2,7 +2,7 @@ mod block_space_alloc; -use index_set::IndexSet; +use index_set::vec::VecIndexSet; use namada::hints; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; @@ -65,7 +65,7 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs let alloc = BlockSpaceAllocator::from(&req); - let mut tx_indices = IndexSet::default(); + let mut tx_indices = VecIndexSet::default(); // decrypt the wrapper txs included in the previous block let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); @@ -189,7 +189,7 @@ where fn build_vote_extension_txs( &mut self, mut alloc: BlockSpaceAllocator, - tx_indices: &mut IndexSet, + tx_indices: &mut VecIndexSet, txs: &[TxBytes], ) -> (Vec, EncryptedTxBatchAllocator) { if self.storage.last_height == BlockHeight(0) { @@ -398,7 +398,7 @@ where fn build_remaining_batch( &mut self, mut alloc: BlockSpaceAllocator, - tx_indices: &IndexSet, + tx_indices: &VecIndexSet, txs: Vec, ) -> Vec { get_remaining_txs(tx_indices, txs) @@ -438,7 +438,7 @@ where /// Return a list of the protocol transactions that haven't /// been marked for inclusion in the block, yet. fn get_remaining_txs( - tx_indices: &IndexSet, + tx_indices: &VecIndexSet, txs: Vec, ) -> impl Iterator + '_ { let mut skip_list = tx_indices.iter(); @@ -553,7 +553,7 @@ mod test_prepare_proposal { .collect(); let set = { - let mut s = IndexSet::default(); + let mut s = VecIndexSet::default(); for idx in excluded_indices.iter().copied() { s.insert(idx as usize); } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 17548fea67..183b0670fd 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -5,7 +5,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; -use index_set::IndexSet; +use index_set::vec::VecIndexSet; use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; @@ -303,7 +303,7 @@ pub fn deserialize_vote_extensions( #[cfg(not(feature = "abcipp"))] pub fn deserialize_vote_extensions<'shell>( txs: &'shell [TxBytes], - tx_indices: &'shell mut IndexSet, + tx_indices: &'shell mut VecIndexSet, ) -> impl Iterator + 'shell { use namada::types::transaction::protocol::ProtocolTx; From ebbbfac29a30c4f348603325f27855f9accef6f8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 29 Nov 2022 14:43:43 +0000 Subject: [PATCH 1841/1995] Some TODO comment changes --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 3c43418300..144dafe2f0 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -208,7 +208,8 @@ where // a bin is partitioned into yet more bins. so, we // could have, say, 2/3 of the bin space available // for eth events, and 1/3 available for valset - // upds + // upds. to be determined, as we implement CheckTx + // changes (issue #367) tracing::debug!( ?tx_bytes, bin_space_left, @@ -356,12 +357,11 @@ where }) .to_bytes() }) - // TODO: make sure all txs are accepted + // TODO: make sure all decrypted txs are accepted .take_while(|tx_bytes| { alloc.try_alloc(&*tx_bytes).map_or_else( |status| match status { AllocFailure::Rejected { bin_space_left } => { - // TODO: handle rejected txs tracing::warn!( ?tx_bytes, bin_space_left, @@ -372,8 +372,6 @@ where false } AllocFailure::OverflowsBin { bin_size } => { - // TODO: handle tx whose size is greater - // than bin size tracing::warn!( ?tx_bytes, bin_size, From becad122d2f4f70de2d55a47d5da72aac9a3f4d2 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 30 Nov 2022 14:35:23 +0100 Subject: [PATCH 1842/1995] [feat]: Added type alias for byte vector in merkle storage --- shared/src/ledger/storage/merkle_tree.rs | 9 ++++++--- shared/src/ledger/storage/traits.rs | 9 +++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index e0b49f2db4..34d5f4bf73 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -55,6 +55,9 @@ pub enum Error { /// Result for functions that may fail type Result = std::result::Result; +/// Type alias for bytes to be put into the Merkle storage +pub(super) type StorageBytes = Vec; + /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; pub type AmtStore = DefaultStore; @@ -321,7 +324,7 @@ impl MerkleTree { .subtree_update(key, value.as_ref())?; // 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()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) @@ -344,7 +347,7 @@ impl MerkleTree { let (store_type, sub_key) = StoreType::sub_key(key)?; let sub_root = self.tree_mut(&store_type).subtree_delete(&sub_key)?; if store_type != StoreType::Base { - let base_key = H::hash(&store_type.to_string()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) @@ -373,7 +376,7 @@ impl MerkleTree { pub fn get_sub_tree_existence_proof( &self, keys: &[Key], - values: Vec>, + values: Vec, ) -> Result { let first_key = keys.iter().next().ok_or_else(|| { Error::InvalidMerkleKey( diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 9ca7798832..b6aff3217a 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -14,6 +14,7 @@ use super::merkle_tree::{Amt, Error, Smt}; use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; @@ -26,7 +27,7 @@ pub trait SubTreeRead { fn subtree_membership_proof( &self, keys: &[Key], - values: Vec>, + values: Vec, ) -> Result; } @@ -54,7 +55,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { fn subtree_membership_proof( &self, keys: &[Key], - mut values: Vec>, + mut values: Vec, ) -> Result { if keys.len() != 1 || values.len() != 1 { return Err(Error::Ics23MultiLeaf); @@ -111,7 +112,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { fn subtree_membership_proof( &self, keys: &[Key], - _: Vec>, + _: Vec, ) -> Result { if keys.len() != 1 { return Err(Error::Ics23MultiLeaf); @@ -165,7 +166,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { fn subtree_membership_proof( &self, _: &[Key], - values: Vec>, + values: Vec, ) -> Result { let values = values .iter() From 2d1c49a6492f4077b369ed25707205778ff4e011 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 30 Nov 2022 14:36:42 +0100 Subject: [PATCH 1843/1995] [feat]: Added type alias for byte vector in merkle storage --- shared/src/ledger/storage/merkle_tree.rs | 9 ++++++--- shared/src/ledger/storage/traits.rs | 9 +++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index e0b49f2db4..34d5f4bf73 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -55,6 +55,9 @@ pub enum Error { /// Result for functions that may fail type Result = std::result::Result; +/// Type alias for bytes to be put into the Merkle storage +pub(super) type StorageBytes = Vec; + /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; pub type AmtStore = DefaultStore; @@ -321,7 +324,7 @@ impl MerkleTree { .subtree_update(key, value.as_ref())?; // 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()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) @@ -344,7 +347,7 @@ impl MerkleTree { let (store_type, sub_key) = StoreType::sub_key(key)?; let sub_root = self.tree_mut(&store_type).subtree_delete(&sub_key)?; if store_type != StoreType::Base { - let base_key = H::hash(&store_type.to_string()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) @@ -373,7 +376,7 @@ impl MerkleTree { pub fn get_sub_tree_existence_proof( &self, keys: &[Key], - values: Vec>, + values: Vec, ) -> Result { let first_key = keys.iter().next().ok_or_else(|| { Error::InvalidMerkleKey( diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 9ca7798832..b6aff3217a 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -14,6 +14,7 @@ use super::merkle_tree::{Amt, Error, Smt}; use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; @@ -26,7 +27,7 @@ pub trait SubTreeRead { fn subtree_membership_proof( &self, keys: &[Key], - values: Vec>, + values: Vec, ) -> Result; } @@ -54,7 +55,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { fn subtree_membership_proof( &self, keys: &[Key], - mut values: Vec>, + mut values: Vec, ) -> Result { if keys.len() != 1 || values.len() != 1 { return Err(Error::Ics23MultiLeaf); @@ -111,7 +112,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Amt { fn subtree_membership_proof( &self, keys: &[Key], - _: Vec>, + _: Vec, ) -> Result { if keys.len() != 1 { return Err(Error::Ics23MultiLeaf); @@ -165,7 +166,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { fn subtree_membership_proof( &self, _: &[Key], - values: Vec>, + values: Vec, ) -> Result { let values = values .iter() From ceaae64672355da751cae4985af60148ad6ab153 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Dec 2022 10:25:20 +0100 Subject: [PATCH 1844/1995] [fix]: Formatting --- shared/src/ledger/storage/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index b6aff3217a..ac426c563c 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -13,8 +13,8 @@ use sha2::{Digest, Sha256}; use super::merkle_tree::{Amt, Error, Smt}; use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; -use crate::types::eth_bridge_pool::PendingTransfer; use crate::ledger::storage::merkle_tree::StorageBytes; +use crate::types::eth_bridge_pool::PendingTransfer; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; From 818da8892dc4043aca36f6dc55e7865b352bfb37 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Dec 2022 10:52:01 +0100 Subject: [PATCH 1845/1995] [feat]: Changed the bytes passed into merkle proof to be references --- shared/src/ledger/queries/shell.rs | 13 ++++++------- shared/src/ledger/storage/merkle_tree.rs | 6 +++--- shared/src/ledger/storage/mod.rs | 4 ++-- shared/src/ledger/storage/traits.rs | 18 ++++++++++-------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 335f9b8203..516992aa94 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -250,11 +250,7 @@ where let proof = if request.prove { let proof = ctx .storage - .get_existence_proof( - &storage_key, - value.clone(), - request.height, - ) + .get_existence_proof(&storage_key, &value, request.height) .into_storage_result()?; Some(proof) } else { @@ -309,7 +305,7 @@ where for PrefixValue { key, value } in &data { let mut proof = ctx .storage - .get_existence_proof(key, value.clone(), request.height) + .get_existence_proof(key, value, request.height) .into_storage_result()?; ops.append(&mut proof.ops); } @@ -477,7 +473,10 @@ where ))); } // get the membership proof - match tree.get_sub_tree_existence_proof(&keys, values) { + match tree.get_sub_tree_existence_proof( + &keys, + values.iter().map(|v| v.as_slice()).collect(), + ) { Ok(BridgePool(proof)) => { let data = EncodeCell::new(&RelayProof { // TODO: use actual validators diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 34d5f4bf73..edd4d2b452 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -56,7 +56,7 @@ pub enum Error { type Result = std::result::Result; /// Type alias for bytes to be put into the Merkle storage -pub(super) type StorageBytes = Vec; +pub(super) type StorageBytes<'a> = &'a [u8]; /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; @@ -733,7 +733,7 @@ mod test { let proof = match tree .get_sub_tree_existence_proof( std::array::from_ref(&ibc_key), - vec![ibc_val.clone()], + vec![&ibc_val], ) .unwrap() { @@ -792,7 +792,7 @@ mod test { let proof = match tree .get_sub_tree_existence_proof( std::array::from_ref(&pos_key), - vec![pos_val.clone()], + vec![&pos_val], ) .unwrap() { diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 34acc656e6..da517989d8 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -36,7 +36,7 @@ use crate::ledger::pos::namada_proof_of_stake::PosBase; use crate::ledger::pos::types::WeightedValidator; use crate::ledger::pos::PosParams; use crate::ledger::storage::merkle_tree::{ - Error as MerkleTreeError, MerkleRoot, + Error as MerkleTreeError, MerkleRoot, StorageBytes, }; pub use crate::ledger::storage::merkle_tree::{ MerkleTree, MerkleTreeStoresRead, MerkleTreeStoresWrite, StoreRef, @@ -619,7 +619,7 @@ where pub fn get_existence_proof( &self, key: &Key, - value: Vec, + value: StorageBytes, height: BlockHeight, ) -> Result { if height >= self.get_block_height().0 { diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index ac426c563c..b615abaa98 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -38,7 +38,7 @@ pub trait SubTreeWrite { fn subtree_update( &mut self, key: &Key, - value: &[u8], + value: StorageBytes, ) -> Result; /// Delete a key from the sub-tree fn subtree_delete(&mut self, key: &Key) -> Result; @@ -68,7 +68,7 @@ impl<'a, H: StorageHasher + Default> SubTreeRead for &'a Smt { Ics23Proof::Exist(ep) => Ok(CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: key.to_string().as_bytes().to_vec(), - value, + value: value.to_vec(), leaf: Some(ics23_specs::leaf_spec::()), ..ep })), @@ -84,7 +84,7 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Smt { fn subtree_update( &mut self, key: &Key, - value: &[u8], + value: StorageBytes, ) -> Result { let value = H::hash(value); self.update(H::hash(key.to_string()).into(), value.into()) @@ -139,7 +139,7 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { fn subtree_update( &mut self, key: &Key, - value: &[u8], + value: StorageBytes, ) -> Result { let key = StringKey::try_from_bytes(key.to_string().as_bytes())?; let value = TreeBytes::from(value.as_ref().to_owned()); @@ -170,9 +170,7 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { ) -> Result { let values = values .iter() - .filter_map(|val| { - PendingTransfer::try_from_slice(val.as_slice()).ok() - }) + .filter_map(|val| PendingTransfer::try_from_slice(val).ok()) .collect(); self.get_membership_proof(values) .map(Into::into) @@ -181,7 +179,11 @@ impl<'a> SubTreeRead for &'a BridgePoolTree { } impl<'a> SubTreeWrite for &'a mut BridgePoolTree { - fn subtree_update(&mut self, key: &Key, _: &[u8]) -> Result { + fn subtree_update( + &mut self, + key: &Key, + _: StorageBytes, + ) -> Result { self.insert_key(key) .map_err(|err| Error::MerkleTree(err.to_string())) } From 4f51d4b5bc97d2e0665693f4bd2667900c67a17e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 11:27:13 +0000 Subject: [PATCH 1846/1995] Ensure pending events require at least the protocol specified min confirmations --- apps/src/lib/node/ledger/ethereum_node/events.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index d2e98de2dd..4c87b967bc 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -126,7 +126,8 @@ pub mod eth_events { match signature { signatures::TRANSFER_TO_NAMADA_SIG => { RawTransfersToNamada::decode(data).map(|txs| PendingEvent { - confirmations: txs.confirmations.into(), + confirmations: min_confirmations + .max(txs.confirmations.into()), block_height, event: EthereumEvent::TransfersToNamada { nonce: txs.nonce, @@ -137,7 +138,8 @@ pub mod eth_events { signatures::TRANSFER_TO_ETHEREUM_SIG => { RawTransfersToEthereum::decode(data).map(|txs| { PendingEvent { - confirmations: txs.confirmations.into(), + confirmations: min_confirmations + .max(txs.confirmations.into()), block_height, event: EthereumEvent::TransfersToEthereum { nonce: txs.nonce, From 53be7c5019eb1a356efad211dc1252e0bbc830d6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 11:40:55 +0000 Subject: [PATCH 1847/1995] Add a basic test for min confirmations --- .../lib/node/ledger/ethereum_node/events.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 4c87b967bc..2095b9bd5c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -67,6 +67,7 @@ pub mod eth_events { pub type Result = std::result::Result; + #[derive(Clone, Debug, PartialEq)] /// An event waiting for a certain number of confirmations /// before being sent to the ledger pub(in super::super) struct PendingEvent { @@ -759,6 +760,8 @@ pub mod eth_events { #[cfg(test)] mod test_events { + use assert_matches::assert_matches; + use super::*; #[test] @@ -800,6 +803,34 @@ pub mod eth_events { }] ) } + + /// Test that for Ethereum events for which a custom number of + /// confirmations may be specified, if a value lower than the + /// protocol-specified minimum confirmations is attempted to be used, + /// then the protcol-specified minimum confirmations is used instead. + #[test] + fn test_min_confirmations_enforced() -> Result<()> { + let sig = signatures::TRANSFER_TO_NAMADA_SIG; + let arbitrary_block_height = 123u64.into(); + let min_confirmations: Uint256 = 100u64.into(); + let event = RawTransfersToNamada { + transfers: vec![], + nonce: 0.into(), + confirmations: 1, // this is lower than `min_confirmations` + }; + let data = event.encode(); + + let pending_event = PendingEvent::decode( + sig, + arbitrary_block_height, + &data, + min_confirmations.clone(), + )?; + + assert_matches!(pending_event, PendingEvent { confirmations, .. } if confirmations == min_confirmations); + Ok(()) + } + /// For each of the basic types, test that roundtrip /// encoding - decoding is a no-op #[test] From b785eca43f228a4f0cdf9f202a5498f1f481062a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 11:59:06 +0000 Subject: [PATCH 1848/1995] Expand test to cover transfers to Ethereum also --- .../lib/node/ledger/ethereum_node/events.rs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index 2095b9bd5c..f603b2b5ff 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -810,16 +810,37 @@ pub mod eth_events { /// then the protcol-specified minimum confirmations is used instead. #[test] fn test_min_confirmations_enforced() -> Result<()> { - let sig = signatures::TRANSFER_TO_NAMADA_SIG; - let arbitrary_block_height = 123u64.into(); + let arbitrary_block_height: Uint256 = 123u64.into(); let min_confirmations: Uint256 = 100u64.into(); - let event = RawTransfersToNamada { - transfers: vec![], - nonce: 0.into(), - confirmations: 1, // this is lower than `min_confirmations` - }; + let lower_than_min_confirmations = 5; + + let (sig, event) = ( + signatures::TRANSFER_TO_NAMADA_SIG, + RawTransfersToNamada { + transfers: vec![], + nonce: 0.into(), + confirmations: lower_than_min_confirmations, + }, + ); let data = event.encode(); + let pending_event = PendingEvent::decode( + sig, + arbitrary_block_height.clone(), + &data, + min_confirmations.clone(), + )?; + + assert_matches!(pending_event, PendingEvent { confirmations, .. } if confirmations == min_confirmations); + let (sig, event) = ( + signatures::TRANSFER_TO_ETHEREUM_SIG, + RawTransfersToEthereum { + transfers: vec![], + nonce: 0.into(), + confirmations: lower_than_min_confirmations, + }, + ); + let data = event.encode(); let pending_event = PendingEvent::decode( sig, arbitrary_block_height, @@ -828,6 +849,7 @@ pub mod eth_events { )?; assert_matches!(pending_event, PendingEvent { confirmations, .. } if confirmations == min_confirmations); + Ok(()) } From d34a23f60e1d3d374b183f8214e6a5324c5c2bce Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 1 Dec 2022 12:02:19 +0000 Subject: [PATCH 1849/1995] Test custom confirmations may be used --- .../lib/node/ledger/ethereum_node/events.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index f603b2b5ff..e451746265 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -853,6 +853,54 @@ pub mod eth_events { Ok(()) } + /// Test that for Ethereum events for which a custom number of + /// confirmations may be specified, the custom number is used if it is + /// at least the protocol-specified minimum confirmations. + #[test] + fn test_custom_confirmations_used() { + let arbitrary_block_height: Uint256 = 123u64.into(); + let min_confirmations: Uint256 = 100u64.into(); + let higher_than_min_confirmations = 200; + + let (sig, event) = ( + signatures::TRANSFER_TO_NAMADA_SIG, + RawTransfersToNamada { + transfers: vec![], + nonce: 0.into(), + confirmations: higher_than_min_confirmations, + }, + ); + let data = event.encode(); + let pending_event = PendingEvent::decode( + sig, + arbitrary_block_height.clone(), + &data, + min_confirmations.clone(), + ) + .unwrap(); + + assert_matches!(pending_event, PendingEvent { confirmations, .. } if confirmations == higher_than_min_confirmations.into()); + + let (sig, event) = ( + signatures::TRANSFER_TO_ETHEREUM_SIG, + RawTransfersToEthereum { + transfers: vec![], + nonce: 0.into(), + confirmations: higher_than_min_confirmations, + }, + ); + let data = event.encode(); + let pending_event = PendingEvent::decode( + sig, + arbitrary_block_height, + &data, + min_confirmations, + ) + .unwrap(); + + assert_matches!(pending_event, PendingEvent { confirmations, .. } if confirmations == higher_than_min_confirmations.into()); + } + /// For each of the basic types, test that roundtrip /// encoding - decoding is a no-op #[test] From d4bc962e13c65490646e85b840fdb5225c68cfd7 Mon Sep 17 00:00:00 2001 From: satan Date: Thu, 1 Dec 2022 13:36:00 +0100 Subject: [PATCH 1850/1995] [fix]: Merge in the part of PR #813 to fix the tx_unbond.wasm tests --- proof_of_stake/src/lib.rs | 10 ----- shared/src/types/token.rs | 10 ++++- .../proptest-regressions/tx_bond.txt | 2 +- wasm/wasm_source/src/tx_bond.rs | 2 +- wasm/wasm_source/src/tx_unbond.rs | 42 ++++++++++--------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c2fd2523cc..b6be63cf44 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1046,8 +1046,6 @@ pub enum BondError { InactiveValidator(Address), #[error("Voting power overflow: {0}")] VotingPowerOverflow(TryFromIntError), - #[error("Given zero amount to bond")] - ZeroAmount, } #[allow(missing_docs)] @@ -1065,8 +1063,6 @@ pub enum UnbondError { ValidatorHasNoVotingPower(Address), #[error("Voting power overflow: {0}")] VotingPowerOverflow(TryFromIntError), - #[error("Given zero amount to unbond")] - ZeroAmount, } #[allow(missing_docs)] @@ -1611,9 +1607,6 @@ where + BorshSerialize + BorshSchema, { - if amount == TokenAmount::default() { - return Err(BondError::ZeroAmount); - } // Check the validator state match validator_state { None => { @@ -1791,9 +1784,6 @@ where + BorshSerialize + BorshSchema, { - if amount == TokenAmount::default() { - return Err(UnbondError::ZeroAmount); - } // We can unbond tokens that are bonded for a future epoch (not yet // active), hence we check the total at the pipeline offset let unbondable_amount = bond diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 7bc914b6ba..3109fee837 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -234,7 +234,7 @@ impl FromStr for Amount { match rust_decimal::Decimal::from_str(s) { Ok(decimal) => { let scale = decimal.scale(); - if scale > 6 { + if scale > MAX_DECIMAL_PLACES { return Err(AmountParseError::ScaleTooLarge(scale)); } let whole = @@ -518,4 +518,12 @@ pub mod testing { pub fn arb_amount_ceiled(max: u64) -> impl Strategy { (0..=max).prop_map(Amount::from) } + + /// Generate an arbitrary non-zero token amount up to and including given + /// `max` value + pub fn arb_amount_non_zero_ceiled( + max: u64, + ) -> impl Strategy { + (1..=max).prop_map(Amount::from) + } } diff --git a/wasm/wasm_source/proptest-regressions/tx_bond.txt b/wasm/wasm_source/proptest-regressions/tx_bond.txt index 3a88756618..8c589d1abd 100644 --- a/wasm/wasm_source/proptest-regressions/tx_bond.txt +++ b/wasm/wasm_source/proptest-regressions/tx_bond.txt @@ -1 +1 @@ -cc e54347c5114ef29538127ba9ad68d1572af839ec63c015318fc0827818853a22 +cc f22e874350910b197cb02a4a07ec5bef18e16c0d1a39eaabaee43d1fc05ce11d diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 8def153df3..ca53c11635 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -353,7 +353,7 @@ mod tests { ( arb_established_address(), prop::option::of(arb_non_internal_address()), - token::testing::arb_amount_ceiled(max_amount), + token::testing::arb_amount_non_zero_ceiled(max_amount), ) .prop_map(|(validator, source, amount)| { transaction::pos::Bond { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 0f249e73f3..46db9ec99c 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -225,24 +225,24 @@ mod tests { 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 start_epoch = if is_delegation { + // This bond was a delegation + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, + ) + } else { + // 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 expected_unbond = if unbond.amount == token::Amount::default() { + HashMap::new() + } else { + HashMap::from_iter([((start_epoch, end_epoch), unbond.amount)]) + }; let actual_unbond: Unbond = unbonds_post.get(pos_params.unbonding_len).unwrap(); assert_eq!( @@ -390,12 +390,14 @@ mod tests { 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) - }) + token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).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 @@ -406,7 +408,7 @@ mod tests { ( address::testing::arb_established_address(), prop::option::of(address::testing::arb_non_internal_address()), - token::testing::arb_amount_ceiled(max_amount), + token::testing::arb_amount_non_zero_ceiled(max_amount), ) .prop_map(|(validator, source, amount)| { let validator = Address::Established(validator); From efd3f0b6b43a30f3d059031348129e73c9aa8b73 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 13:33:33 +0000 Subject: [PATCH 1851/1995] Update comment in PrepareProposal to reflect new allocator design --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 144dafe2f0..e6b85d4359 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -89,8 +89,10 @@ where txs.append(&mut mempool_txs); // fill up the remaining block space with - // arbitrary transactions that can fit in - // the free space left + // protocol transactions that haven't been + // selected for inclusion yet, and whose + // size allows them to fit in the free + // space left let mut remaining_txs = self.build_remaining_batch(alloc, &tx_indices, req.txs); txs.append(&mut remaining_txs); From 4a48a99063a667497769a01c50d9f12f22c4a39a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 13:35:58 +0000 Subject: [PATCH 1852/1995] Remove IndexSet from tree --- shared/src/types/index_set.rs | 99 ----------------------------------- shared/src/types/mod.rs | 1 - 2 files changed, 100 deletions(-) delete mode 100644 shared/src/types/index_set.rs diff --git a/shared/src/types/index_set.rs b/shared/src/types/index_set.rs deleted file mode 100644 index d669649a74..0000000000 --- a/shared/src/types/index_set.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! Set data structure optimized to store [`usize`] values. - -use std::collections::BTreeMap; - -/// The storage unit for the bits in an [`IndexSet`]. -#[allow(dead_code)] -type IndexSetStorage = u128; - -/// The width, in bytes, of the storage unit for an [`IndexSet`]. -#[allow(dead_code)] -const INDEX_SET_STORAGE_WIDTH: usize = std::mem::size_of::(); - -/// Set data structure optimized to store [`usize`] values. -#[derive(Default, Debug, Clone)] -#[allow(dead_code)] -pub struct IndexSet { - /// Map of indices to bit vectors, containing the actual boolean - /// values to be asserted. - /// - /// If the bit `B` is set, at the bit vector with index `S`, then - /// the index `INDEX_SET_STORAGE_WIDTH * S + B` is in the set. - bit_sets: BTreeMap, -} - -impl IndexSet { - /// Add a new index to this [`IndexSet`]. - #[allow(dead_code)] - pub fn insert(&mut self, index: usize) { - // theset let exprs will get optimized into a single op, - // since they're ordered in sequence, which is nice - let map_index = index / INDEX_SET_STORAGE_WIDTH; - let bit_set_index = index % INDEX_SET_STORAGE_WIDTH; - - let set = self.bit_sets.entry(map_index).or_insert(0); - *set |= 1 << bit_set_index; - } - - /// Return an iterator over the transaction indices in - /// this [`IndexSet`], in ascending order. - #[allow(dead_code)] - #[inline] - pub fn iter(&self) -> impl Iterator + '_ { - self.bit_sets.iter().flat_map(|(&map_index, &set)| { - (0..INDEX_SET_STORAGE_WIDTH).into_iter().flat_map( - move |bit_set_index| { - let is_bit_set = (set & (1 << bit_set_index)) != 0; - is_bit_set.then(|| { - map_index as usize * INDEX_SET_STORAGE_WIDTH - + bit_set_index as usize - }) - }, - ) - }) - } - - /// Merge two [`IndexSet`] instances. - /// - /// Corresponds to a mutating union set operation, - /// between `self` and `other`. - #[allow(dead_code)] - #[inline] - pub fn merge(&mut self, other: &IndexSet) { - for (&map_index, &other_set) in other.bit_sets.iter() { - let set = self.bit_sets.entry(map_index).or_insert(0); - *set |= other_set; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Test [`IndexSet`] index insert ops. - #[test] - fn test_index_set_insert() { - let mut set = IndexSet::default(); - let mut indices = vec![1, 4, 6, 3, 1, 100, 123, 12, 3]; - - // insert some elements into the set - for i in indices.iter().copied() { - set.insert(i); - } - - // check if the set contains the same elements - // we inserted, in ascending order - indices.sort_unstable(); - indices.dedup(); - - let set_indices: Vec<_> = set.iter().collect(); - assert_eq!(indices, set_indices); - - // check that the no. of storage elements used is lower - // than the max no. of bitsets we would otherwise need - let storage_elements_max = - indices[indices.len() - 1] / INDEX_SET_STORAGE_WIDTH; - assert!(set.bit_sets.len() <= storage_elements_max); - } -} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 440ce4c67c..b51a9bbc7e 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -9,7 +9,6 @@ pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; -pub mod index_set; pub mod internal; pub mod keccak; pub mod key; From 0e8127e62d64649bdb5c1de934dd131887ff1356 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 13:37:23 +0000 Subject: [PATCH 1853/1995] Rename: height_off -> height_offset --- shared/src/ledger/storage/mod.rs | 6 +++--- shared/src/ledger/storage_api/queries.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 4d9afe8546..7cd2950489 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -1319,7 +1319,7 @@ where } } - fn is_deciding_offset_within_epoch(&self, height_off: u64) -> bool { + fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool { let current_decision_height = self.get_current_decision_height(); // NOTE: the first stored height in `fst_block_heights_of_each_epoch` @@ -1328,7 +1328,7 @@ where // // we can remove this check once that's fixed if self.get_current_epoch().0 == Epoch(0) { - let height_offset_within_epoch = BlockHeight(1 + height_off); + let height_offset_within_epoch = BlockHeight(1 + height_offset); return current_decision_height == height_offset_within_epoch; } @@ -1338,7 +1338,7 @@ where fst_heights_of_each_epoch .last() .map(|&h| { - let height_offset_within_epoch = h + height_off; + let height_offset_within_epoch = h + height_offset; current_decision_height == height_offset_within_epoch }) .unwrap_or(false) diff --git a/shared/src/ledger/storage_api/queries.rs b/shared/src/ledger/storage_api/queries.rs index 540980c418..af9c5188d4 100644 --- a/shared/src/ledger/storage_api/queries.rs +++ b/shared/src/ledger/storage_api/queries.rs @@ -128,9 +128,9 @@ pub trait QueriesExt { /// extension at the provided [`BlockHeight`] in [`SendValsetUpd`]. fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool; - /// Check if we are at a given [`BlockHeight`] offset, `height_off`, within - /// the current [`Epoch`]. - fn is_deciding_offset_within_epoch(&self, height_off: u64) -> bool; + /// Check if we are at a given [`BlockHeight`] offset, `height_offset`, + /// within the current [`Epoch`]. + fn is_deciding_offset_within_epoch(&self, height_offset: u64) -> bool; /// Given some [`BlockHeight`], return the corresponding [`Epoch`]. fn get_epoch(&self, height: BlockHeight) -> Option; From cdc694f3fd0ffe1039d961c0162a29371d35c605 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:09:16 +0000 Subject: [PATCH 1854/1995] Add link to block space alloc docs in PrepareProposal --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e6b85d4359..10d4ad3e7a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -45,11 +45,8 @@ where { /// Begin a new block. /// - /// Block space is roughly divided into three equal parts, which we call - /// allotments. In the following order, each allotted area is home to: - /// decrypted, protocol and encrypted transactions, respectively. During - /// some protocol phases, we might omit encrypted and decrypted transactions - /// entirely, such as when we negotiate DKG parameters. + /// Block construction is documented in [`block_space_alloc`] + /// and [`block_space_alloc::states`]. /// /// INVARIANT: Any changes applied in this method must be reverted if /// the proposal is rejected (unless we can simply overwrite From c6c2b87158a421b05a677edf73bdb3ab9f37fad9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:10:50 +0000 Subject: [PATCH 1855/1995] Rename: build_mempool_txs -> build_encrypted_txs --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 10d4ad3e7a..9bff2b430f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -80,10 +80,10 @@ where ); txs.append(&mut protocol_txs); - // add mempool txs - let (mut mempool_txs, alloc) = - self.build_mempool_txs(alloc, &req.txs); - txs.append(&mut mempool_txs); + // add encrypted txs + let (mut encrypted_txs, alloc) = + self.build_encrypted_txs(alloc, &req.txs); + txs.append(&mut encrypted_txs); // fill up the remaining block space with // protocol transactions that haven't been @@ -266,7 +266,7 @@ where /// Builds a batch of mempool transactions. #[cfg(feature = "abcipp")] - fn build_mempool_txs( + fn build_encrypted_txs( &mut self, _alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], @@ -277,7 +277,7 @@ where /// Builds a batch of mempool transactions. #[cfg(not(feature = "abcipp"))] - fn build_mempool_txs( + fn build_encrypted_txs( &mut self, mut alloc: EncryptedTxBatchAllocator, txs: &[TxBytes], From 0cd446578659385b1a4d68ec657c03355322664b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:12:56 +0000 Subject: [PATCH 1856/1995] Rename: tx_indices -> protocol_tx_indices --- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +++++++++++-------- .../lib/node/ledger/shell/vote_extensions.rs | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9bff2b430f..6a700a8947 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -62,7 +62,7 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs let alloc = BlockSpaceAllocator::from(&req); - let mut tx_indices = VecIndexSet::default(); + let mut protocol_tx_indices = VecIndexSet::default(); // decrypt the wrapper txs included in the previous block let (decrypted_txs, alloc) = self.build_decrypted_txs(alloc); @@ -72,7 +72,7 @@ where let (mut protocol_txs, alloc) = self.build_vote_extension_txs( alloc, #[cfg(not(feature = "abcipp"))] - &mut tx_indices, + &mut protocol_tx_indices, #[cfg(feature = "abcipp")] req.local_last_commit, #[cfg(not(feature = "abcipp"))] @@ -90,8 +90,11 @@ where // selected for inclusion yet, and whose // size allows them to fit in the free // space left - let mut remaining_txs = - self.build_remaining_batch(alloc, &tx_indices, req.txs); + let mut remaining_txs = self.build_remaining_batch( + alloc, + &protocol_tx_indices, + req.txs, + ); txs.append(&mut remaining_txs); txs @@ -188,7 +191,7 @@ where fn build_vote_extension_txs( &mut self, mut alloc: BlockSpaceAllocator, - tx_indices: &mut VecIndexSet, + protocol_tx_indices: &mut VecIndexSet, txs: &[TxBytes], ) -> (Vec, EncryptedTxBatchAllocator) { if self.storage.last_height == BlockHeight(0) { @@ -196,7 +199,7 @@ where return (vec![], self.get_encrypted_txs_allocator(alloc)); } - let txs = deserialize_vote_extensions(txs, tx_indices).take_while(|tx_bytes| + let txs = deserialize_vote_extensions(txs, protocol_tx_indices).take_while(|tx_bytes| alloc.try_alloc(&*tx_bytes) .map_or_else( |status| match status { @@ -395,10 +398,10 @@ where fn build_remaining_batch( &mut self, mut alloc: BlockSpaceAllocator, - tx_indices: &VecIndexSet, + protocol_tx_indices: &VecIndexSet, txs: Vec, ) -> Vec { - get_remaining_txs(tx_indices, txs) + get_remaining_txs(protocol_tx_indices, txs) .take_while(|tx_bytes| { alloc.try_alloc(&*tx_bytes).map_or_else( |status| match status { @@ -435,10 +438,10 @@ where /// Return a list of the protocol transactions that haven't /// been marked for inclusion in the block, yet. fn get_remaining_txs( - tx_indices: &VecIndexSet, + protocol_tx_indices: &VecIndexSet, txs: Vec, ) -> impl Iterator + '_ { - let mut skip_list = tx_indices.iter(); + let mut skip_list = protocol_tx_indices.iter(); let mut skip = skip_list.next(); txs.into_iter().enumerate().filter_map(move |(index, tx)| { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index 183b0670fd..ec385da041 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -303,7 +303,7 @@ pub fn deserialize_vote_extensions( #[cfg(not(feature = "abcipp"))] pub fn deserialize_vote_extensions<'shell>( txs: &'shell [TxBytes], - tx_indices: &'shell mut VecIndexSet, + protocol_tx_indices: &'shell mut VecIndexSet, ) -> impl Iterator + 'shell { use namada::types::transaction::protocol::ProtocolTx; @@ -326,7 +326,7 @@ pub fn deserialize_vote_extensions<'shell>( .. }) => { // mark tx for inclusion - tx_indices.insert(index); + protocol_tx_indices.insert(index); Some(tx_bytes.clone()) } _ => None, From e2f3fed013c13443fd91d9c095a630784efb3ff2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:14:10 +0000 Subject: [PATCH 1857/1995] Rename: get_remaining_txs -> get_remaining_protocol_txs --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6a700a8947..a952d234a6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -401,7 +401,7 @@ where protocol_tx_indices: &VecIndexSet, txs: Vec, ) -> Vec { - get_remaining_txs(protocol_tx_indices, txs) + get_remaining_protocol_txs(protocol_tx_indices, txs) .take_while(|tx_bytes| { alloc.try_alloc(&*tx_bytes).map_or_else( |status| match status { @@ -437,7 +437,7 @@ where /// Return a list of the protocol transactions that haven't /// been marked for inclusion in the block, yet. -fn get_remaining_txs( +fn get_remaining_protocol_txs( protocol_tx_indices: &VecIndexSet, txs: Vec, ) -> impl Iterator + '_ { @@ -530,9 +530,9 @@ mod test_prepare_proposal { } } - /// Test if [`get_remaining_txs`] is working as expected. + /// Test if [`get_remaining_protocol_txs`] is working as expected. #[test] - fn test_get_remaining_txs() { + fn test_get_remaining_protocol_txs() { // TODO(feature = "abcipp"): use a different tx type here fn bertha_ext(at_height: u64) -> TxBytes { let key = wallet::defaults::bertha_keypair(); @@ -560,7 +560,7 @@ mod test_prepare_proposal { s }; - let got_txs: Vec<_> = get_remaining_txs(&set, all_txs) + let got_txs: Vec<_> = get_remaining_protocol_txs(&set, all_txs) .map(extract_eth_events_vext) .collect(); assert_eq!(expected_txs, got_txs); From a4ae45b41b9934c8c56f49960eff2f819ad6e89a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:15:34 +0000 Subject: [PATCH 1858/1995] Rename: build_vote_extension_txs -> build_protocol_txs --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index a952d234a6..d6fcfa61fb 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -69,7 +69,7 @@ where let mut txs = decrypted_txs; // add vote extension protocol txs - let (mut protocol_txs, alloc) = self.build_vote_extension_txs( + let (mut protocol_txs, alloc) = self.build_protocol_txs( alloc, #[cfg(not(feature = "abcipp"))] &mut protocol_tx_indices, @@ -125,7 +125,7 @@ where /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update. #[cfg(feature = "abcipp")] - fn build_vote_extension_txs( + fn build_protocol_txs( &mut self, mut alloc: BlockSpaceAllocator, local_last_commit: Option, @@ -188,7 +188,7 @@ where /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update #[cfg(not(feature = "abcipp"))] - fn build_vote_extension_txs( + fn build_protocol_txs( &mut self, mut alloc: BlockSpaceAllocator, protocol_tx_indices: &mut VecIndexSet, From 70805d940498c1921d97fb5c1e8323c346d44fcb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:20:49 +0000 Subject: [PATCH 1859/1995] Improve get_encrypted_txs_allocator() docstr --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d6fcfa61fb..30f6fd18e6 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -242,7 +242,18 @@ where (txs, self.get_encrypted_txs_allocator(alloc)) } - /// Transition to an [`EncryptedTxBatchAllocator`]. + /// Depending on the current block height offset within the epoch, + /// transition state accordingly, from a protocol tx batch allocator + /// to an encrypted tx batch allocator. + /// + /// # How to determine which path to take in the states DAG + /// + /// If we are at the second or third block height offset within an + /// epoch, we do not allow encrypted transactions to be included in + /// a block, therefore we return an allocator wrapped in an + /// [`EncryptedTxBatchAllocator::WithoutEncryptedTxs`] value. + /// Otherwise, we return an allocator wrapped in an + /// [`EncryptedTxBatchAllocator::WithEncryptedTxs`] value. #[inline] fn get_encrypted_txs_allocator( &self, From ca2be409f6450f83f33a21720fcd33f6dc61fcd8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:22:56 +0000 Subject: [PATCH 1860/1995] Improve docstr --- .../node/ledger/shell/prepare_proposal/block_space_alloc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs index ee06a47142..11b5a1796f 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal/block_space_alloc.rs @@ -8,8 +8,8 @@ //! The code in this module doesn't perform any deserializing to //! verify if we are, in fact, allocating space for the correct //! kind of tx for the current [`BlockSpaceAllocator`] state. It -//! is up to the user to dispatch the correct kind of tx into the -//! current state of the allocator. +//! is up to `PrepareProposal` to dispatch the correct kind of tx +//! into the current state of the allocator. //! //! # How space is allocated //! From 933d45897f9b773939d542caf1b2374bab070305 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 2 Dec 2022 14:33:48 +0000 Subject: [PATCH 1861/1995] Improve docstr --- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 30f6fd18e6..7e130a40c9 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -278,7 +278,8 @@ where } } - /// Builds a batch of mempool transactions. + /// Builds a batch of encrypted transactions, retrieved from + /// Tendermint's mempool. #[cfg(feature = "abcipp")] fn build_encrypted_txs( &mut self, @@ -289,7 +290,8 @@ where todo!() } - /// Builds a batch of mempool transactions. + /// Builds a batch of encrypted transactions, retrieved from + /// Tendermint's mempool. #[cfg(not(feature = "abcipp"))] fn build_encrypted_txs( &mut self, From ab122240e94c6639a3482c7264bf52750461a15b Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 2 Dec 2022 15:52:29 +0100 Subject: [PATCH 1862/1995] [feat]: E2E tests for the bridge pool are now working --- apps/src/lib/client/eth_bridge_pool.rs | 17 ++++++-- apps/src/lib/config/genesis.rs | 22 ++++++++-- shared/src/ledger/storage/traits.rs | 1 - tests/src/e2e/eth_bridge_tests.rs | 57 ++++++++++++++++++++++---- tests/src/e2e/setup.rs | 2 + wasm/checksums.json | 38 ++++++++++------- 6 files changed, 107 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index b697995eb4..09171a9cc9 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -1,6 +1,9 @@ +use std::collections::HashMap; + use borsh::BorshSerialize; use namada::ledger::queries::RPC; use namada::proto::Tx; +use namada::types::eth_abi::Encode; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; @@ -67,11 +70,19 @@ pub async fn construct_bridge_pool_proof(args: args::BridgePoolProof) { /// Prints out a json payload. pub async fn query_bridge_pool(args: args::Query) { let client = HttpClient::new(args.ledger_address).unwrap(); - let response = RPC + let response: Vec = RPC .shell() .read_ethereum_bridge_pool(&client) .await .unwrap(); - - println!("{:#?}", serde_json::to_string_pretty(&response)); + let pool_contents: HashMap = response + .into_iter() + .map(|transfer| (transfer.keccak256().to_string(), transfer)) + .collect(); + if pool_contents.is_empty() { + println!("Bridge pool is empty."); + } else { + println!("Bridge pool contents: "); + } + println!("{}", serde_json::to_string_pretty(&pool_contents).unwrap()); } diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index d27827a1d9..ffbc22cbba 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,13 +6,16 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; -use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; +use namada::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, +}; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::types::address::Address; +use namada::types::address::{wnam, Address}; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; +use namada::types::ethereum_events::EthAddress; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::DateTimeUtc; @@ -881,7 +884,20 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - ethereum_bridge_params: None, + ethereum_bridge_params: Some(EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([0; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([1; 20]), + version: Default::default(), + }, + }, + }), } } diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 3b0a58725e..b615abaa98 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -15,7 +15,6 @@ use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index bc8a7cc5eb..1dc629394b 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,6 +1,10 @@ use color_eyre::eyre::Result; +use namada::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, +}; use namada::types::address::wnam; use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED; +use namada::types::ethereum_events::EthAddress; use namada_apps::config::ethereum_bridge; use super::setup::set_ethereum_bridge_mode; @@ -128,13 +132,37 @@ fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { Ok(()) } +/// In this test, we check the following: +/// 1. We can successfully add tranfers to the bridge pool. +/// 2. We can query the bridge pool and it is non-empty. #[test] fn test_add_to_bridge_pool() { const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 40; const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 60; + const QUERY_TIMEOUT_SECONDS: u64 = 40; const SOLE_VALIDATOR: Who = Who::Validator(0); let wnam_address = wnam().to_canonical(); - let test = setup::single_node_net().unwrap(); + let test = setup::network( + |mut genesis| { + genesis.ethereum_bridge_params = Some(EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([0; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([1; 20]), + version: Default::default(), + }, + }, + }); + genesis + }, + None, + ) + .unwrap(); set_ethereum_bridge_mode( &test, &test.net.chain_id, @@ -142,7 +170,7 @@ fn test_add_to_bridge_pool() { ethereum_bridge::ledger::Mode::EventsEndpoint, ); - let mut anoman_ledger = run_as!( + let mut namadan_ledger = run_as!( test, SOLE_VALIDATOR, Bin::Node, @@ -150,12 +178,14 @@ fn test_add_to_bridge_pool() { Some(LEDGER_STARTUP_TIMEOUT_SECONDS) ) .unwrap(); - anoman_ledger + namadan_ledger .exp_string("Anoma ledger node started") .unwrap(); - anoman_ledger.exp_string("Tendermint node started").unwrap(); - anoman_ledger.exp_string("Committed block hash").unwrap(); - let _bg_ledger = anoman_ledger.background(); + namadan_ledger + .exp_string("Tendermint node started") + .unwrap(); + namadan_ledger.exp_string("Committed block hash").unwrap(); + let _bg_ledger = namadan_ledger.background(); let ledger_addr = get_actor_rpc(&test, &SOLE_VALIDATOR); let tx_args = vec![ @@ -184,12 +214,23 @@ fn test_add_to_bridge_pool() { &ledger_addr, ]; - let mut anomac_tx = run!( + let mut namadac_tx = run!( test, Bin::Client, tx_args, Some(CLIENT_COMMAND_TIMEOUT_SECONDS) ) .unwrap(); - anomac_tx.exp_string("Transaction applied").unwrap(); + namadac_tx.exp_string("Transaction applied").unwrap(); + namadac_tx.exp_string("Transaction is valid").unwrap(); + drop(namadac_tx); + + let mut namadar = run!( + test, + Bin::BridgePool, + ["query", "--ledger-address", &ledger_addr,], + Some(QUERY_TIMEOUT_SECONDS), + ) + .unwrap(); + namadar.exp_string("Bridge pool contents:").unwrap(); } diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 773a93069c..cf02c3e790 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -253,6 +253,7 @@ pub enum Bin { Node, Client, Wallet, + BridgePool, } #[derive(Debug)] @@ -711,6 +712,7 @@ where Bin::Node => ("namadan", "info"), Bin::Client => ("namadac", "tendermint_rpc=debug"), Bin::Wallet => ("namadaw", "info"), + Bin::BridgePool => ("namadar", "info"), }; let mut run_cmd = generate_bin_command( diff --git a/wasm/checksums.json b/wasm/checksums.json index 24e4c0b43e..8e54008eac 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,25 @@ { - "tx_bond.wasm": "tx_bond.4fc2b1c226d57d94e4043109d1af164ac69f8eb72e12525d97a123d8100817af.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.f41d51ed01d5c60909c8ff9a7ed31f19b2d3ef42c24d1daa4143d59e0e797d97.wasm", - "tx_ibc.wasm": "tx_ibc.582a0a6433782caaee708205fc22f40d2af34fcd6994ac8f5fb8bef627778b4d.wasm", - "tx_init_account.wasm": "tx_init_account.0c204ba845658516b20670346c0682595d16d1ca99f42eddde51d1cde4295ffb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0a9cbdd68510d723818bb5d95cb0770b4f054ca402f8bbcff18108ea413875de.wasm", - "tx_init_validator.wasm": "tx_init_validator.09d0e561565cb083cb243b4594882c4f424eda73a91f89cce1e814b35ba2eae5.wasm", - "tx_transfer.wasm": "tx_transfer.9f4ad0aed0f1fcf21398c4d12da4ae26937415b01524811c0dceb810f1d1bf4a.wasm", - "tx_unbond.wasm": "tx_unbond.72018d106cca5a856bca041ea30eee579ec61d29f246f1387f3da4ef72c6cfbd.wasm", - "tx_update_vp.wasm": "tx_update_vp.4d585d52ed7f3b1507ed092df82ff7edec362305eb9e84f4dae06f09d75cc727.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.49638241211faf327390a0f07920d8dd40c9d7676d9c1beccd3bad0ff5f6f1a9.wasm", - "tx_withdraw.wasm": "tx_withdraw.1f8faa002868664e42d6e4b7140d5f295a2a0f1e99968656ee4d91c496b8f08d.wasm", - "vp_masp.wasm": "vp_masp.ad12a384f8690ad1a7c084b0a2ce7e72b9743fac7b2229f9a0e290fd0e75619a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e7d6cc83ad23f7db10484a051e6ba25105900b154e179768ccc79b955d6f47ca.wasm", - "vp_token.wasm": "vp_token.fbec5c10439c0f2d421799188f9ab677967b931535262dbf4cc7591ea0978aa7.wasm", - "vp_user.wasm": "vp_user.4db5100a828f04ed1fe6d2fee09b56facd8dde1e3bc0ea8d288875bf934d581a.wasm" + "tx_bond.wasm": "tx_bond.d39b2ae31f454a352d9ed31db2d4b55fadb2246eb7fb736890100de77fb57f84.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.5ad5652c40311fffb98f5b452c06928ceccb8a94fb6a1524368cce3e4445e8aa.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.2ddee7d15e2e9884f98c8a423a140ba9ac89417ccfcc8473615a464a0d4acf0a.wasm", + "tx_from_intent.wasm": "tx_from_intent.8d25dc3453f6ea39b7b2a3574ff47f26a17ee91e1c71f88adf116dd34cb198dc.wasm", + "tx_ibc.wasm": "tx_ibc.9ec5f2d7e3abf9847d414fcfbf5b1a2cadf166991c8ae941a8a10c6f2fc701fd.wasm", + "tx_init_account.wasm": "tx_init_account.2ca339b1a54239555853a5ace3aba1702d4a2027add6ba53ed1dd3814e45e1a1.wasm", + "tx_init_nft.wasm": "tx_init_nft.5fb223d13032f03e454a763125e69d9188b0bff54f14967161b91dd2bc8efc27.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c530e0ff5ab37cef227cbc1f26f58daf9c70ca15edc9e714f9ade6d0a989ec61.wasm", + "tx_init_validator.wasm": "tx_init_validator.6504445e5c18660bf8f4e1b42baadf1cd200943b092e90c8a93bdad40ebc7961.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1278e59c0ed5f0b9834ce121b69f7f3ccba91e7ff4be305b5fa898cdc324690c.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.b4d258ab70fdb136bacfa1c2caa765b596c796b330ff81c5756c080f2e21b36b.wasm", + "tx_transfer.wasm": "tx_transfer.ba8a40548e65d7af64e9151a44c4e45c9e4e5d0e8b4bd26b74ecc36fc752c349.wasm", + "tx_unbond.wasm": "tx_unbond.19f40a5bb264aacde8725ea475d73be3618281171d344bd6fc825e970e7fbcf9.wasm", + "tx_update_vp.wasm": "tx_update_vp.5dae0339928ab57bd938b1b10437127e9b01fd43f584103ba703424229a72618.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.60940ab81a965afbc5a1c973d5b4fbd917add369fe561c7ef2ba70b9835dbdae.wasm", + "tx_withdraw.wasm": "tx_withdraw.8496025271b977e5543647cd68452c2d17f4173537fd5390f0e8c379fdfc9a44.wasm", + "vp_implicit.wasm": "vp_implicit.ef275670c60bb360ae7ffe5e5153c06dd18ad63110ce47396114416bcf82ebb8.wasm", + "vp_masp.wasm": "vp_masp.e8b7276779cfd69eaa980f5e1cca3879073b0ac987170c58d0f867c9ecf0cb48.wasm", + "vp_nft.wasm": "vp_nft.173da472fb0b54d24a0e7f45ef78dc7a045b6119967045e37e20b6844db1dd4d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ef0688ce368780e0d77160c22e3655bdde1295ac6357ce10f83eda04d9f2075.wasm", + "vp_token.wasm": "vp_token.4df4c16e3276ab80cd9be6f21c88b37091d17a7dc021b12f950c742be6ed3794.wasm", + "vp_user.wasm": "vp_user.997779f95f446909d5dc27b879ba31d8ebf811bb9875e329701521991b52657b.wasm", + "vp_validator.wasm": "vp_validator.e335315f0fcd81c869a66d1d9be9b98ea3e102076379a038cbf7eb86d2d85685.wasm" } \ No newline at end of file From 127e805a2a0542925eb36c2f1dc5ad22fc9b4349 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 5 Dec 2022 15:36:17 +0000 Subject: [PATCH 1863/1995] Set the highest priority for validator set update protocol txs --- apps/src/lib/node/ledger/shell/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8768f5d7e4..8ba062d534 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -737,6 +737,9 @@ where self.storage.last_height, ) { response.log = String::from(VALID_MSG); + // validator set update votes should be decided + // as soon as possible + response.priority = i64::MAX; } else { response.code = 1; response.log = String::from( From 4bfa3ae55cf5087c517b97c482257142a9fc4a45 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Dec 2022 09:33:23 +0000 Subject: [PATCH 1864/1995] Harden CheckTx Reject txs that do not pass `process_tx` validation, and reject decrypted txs, because these are essentially protocol transactions, which can only be crafted by block proposers. --- apps/src/lib/node/ledger/shell/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8ba062d534..ecec0c288a 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -705,7 +705,9 @@ where }; let mut response = response::CheckTx::default(); + const VALID_MSG: &str = "Mempool validation passed"; + const INVALID_MSG: &str = "Mempool validation failed"; match Tx::try_from(tx_bytes).map_err(Error::TxDecoding) { Ok(tx) => { @@ -754,9 +756,20 @@ where mempool: {tx:?}" ); } - // `process_tx` errors are handled by - // `Shell::finalize_block` - _ => response.log = String::from(VALID_MSG), + Ok(TxType::Wrapper(_) | TxType::Raw(_)) => { + response.log = String::from(VALID_MSG); + } + Ok(TxType::Decrypted(_)) => { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Decrypted txs cannot be sent by \ + clients" + ); + } + Err(err) => { + response.code = 1; + response.log = format!("{INVALID_MSG}: {err}"); + } } } Err(msg) => { From 0ac0bc191f816baaddbcc61545f31274e841d859 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Dec 2022 10:36:27 +0100 Subject: [PATCH 1865/1995] Update shared/src/ledger/storage/merkle_tree.rs Co-authored-by: Tiago Carvalho --- 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 edd4d2b452..b23e9db7de 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -56,7 +56,7 @@ pub enum Error { type Result = std::result::Result; /// Type alias for bytes to be put into the Merkle storage -pub(super) type StorageBytes<'a> = &'a [u8]; +pub(super) type StorageBytes<'a> = std::borrow::Cow<'a, [u8]>; /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; From fb734528e0dfb25c06e359f33d5179fc9563ee5c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Dec 2022 09:48:34 +0000 Subject: [PATCH 1866/1995] Homogenize error msgs in CheckTx --- apps/src/lib/node/ledger/shell/mod.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ecec0c288a..b6c55c29cb 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -724,8 +724,9 @@ where response.log = String::from(VALID_MSG); } else { response.code = 1; - response.log = String::from( - "Invalid Ethereum events vote extension", + response.log = format!( + "{INVALID_MSG}: Invalid Ethereum events vote \ + extension", ); } } @@ -744,16 +745,17 @@ where response.priority = i64::MAX; } else { response.code = 1; - response.log = String::from( - "Invalid validator set update vote extension", + response.log = format!( + "{INVALID_MSG}: Invalid validator set update \ + vote extension", ); } } Ok(TxType::Protocol(ProtocolTx { tx, .. })) => { response.code = 1; response.log = format!( - "The following protocol tx cannot be added to the \ - mempool: {tx:?}" + "{INVALID_MSG}: The following protocol tx cannot \ + be added to the mempool: {tx:?}" ); } Ok(TxType::Wrapper(_) | TxType::Raw(_)) => { @@ -774,7 +776,7 @@ where } Err(msg) => { response.code = 1; - response.log = msg.to_string(); + response.log = format!("{INVALID_MSG}: {msg}"); } } From 8844903b57c94cce94992025a6a8928999da41d4 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 6 Dec 2022 09:51:06 +0000 Subject: [PATCH 1867/1995] Update apps/src/lib/node/ledger/ethereum_node/events.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/node/ledger/ethereum_node/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/ethereum_node/events.rs b/apps/src/lib/node/ledger/ethereum_node/events.rs index e451746265..afd8e086fe 100644 --- a/apps/src/lib/node/ledger/ethereum_node/events.rs +++ b/apps/src/lib/node/ledger/ethereum_node/events.rs @@ -807,7 +807,7 @@ pub mod eth_events { /// Test that for Ethereum events for which a custom number of /// confirmations may be specified, if a value lower than the /// protocol-specified minimum confirmations is attempted to be used, - /// then the protcol-specified minimum confirmations is used instead. + /// then the protocol-specified minimum confirmations is used instead. #[test] fn test_min_confirmations_enforced() -> Result<()> { let arbitrary_block_height: Uint256 = 123u64.into(); From 2af693a3965a5271616a56fb12b2603e34054d94 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 6 Dec 2022 11:00:51 +0100 Subject: [PATCH 1868/1995] Revert "Update shared/src/ledger/storage/merkle_tree.rs" This reverts commit 0ac0bc191f816baaddbcc61545f31274e841d859. --- 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 b23e9db7de..edd4d2b452 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -56,7 +56,7 @@ pub enum Error { type Result = std::result::Result; /// Type alias for bytes to be put into the Merkle storage -pub(super) type StorageBytes<'a> = std::borrow::Cow<'a, [u8]>; +pub(super) type StorageBytes<'a> = &'a [u8]; /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; From fe3401d2612d5e49737dd76b9970a436742cab32 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Dec 2022 13:20:05 +0100 Subject: [PATCH 1869/1995] Update apps/src/lib/client/eth_bridge_pool.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/client/eth_bridge_pool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index 09171a9cc9..94f2ce1208 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -81,6 +81,7 @@ pub async fn query_bridge_pool(args: args::Query) { .collect(); if pool_contents.is_empty() { println!("Bridge pool is empty."); + return; } else { println!("Bridge pool contents: "); } From 1e9dcf5ac2a891cb0a7d6071f5414435590ad5dc Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Dec 2022 13:21:10 +0100 Subject: [PATCH 1870/1995] Update tests/src/e2e/eth_bridge_tests.rs Co-authored-by: Tiago Carvalho --- tests/src/e2e/eth_bridge_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1dc629394b..c447efe5cc 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -221,6 +221,7 @@ fn test_add_to_bridge_pool() { Some(CLIENT_COMMAND_TIMEOUT_SECONDS) ) .unwrap(); + namadac_tx.exp_string("Transaction accepted").unwrap(); namadac_tx.exp_string("Transaction applied").unwrap(); namadac_tx.exp_string("Transaction is valid").unwrap(); drop(namadac_tx); From 0a917fc262efb2db116ef66857c712343abfb14b Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 29 Nov 2022 10:39:16 +0100 Subject: [PATCH 1871/1995] [fix]: Fixed the wasm for adding transfers to the bridge pool --- apps/src/lib/node/ledger/shell/init_chain.rs | 1 + wasm/wasm_source/src/tx_bridge_pool.rs | 23 +++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 6bc119a6e0..7ead94a34a 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -73,6 +73,7 @@ where genesis.gov_params.init_storage(&mut self.storage); // configure the Ethereum bridge if the configuration is set. if let Some(config) = genesis.ethereum_bridge_params { + tracing::debug!("Initializing Ethereum bridge storage."); config.init_storage(&mut self.storage); } diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 812e95b25f..805ee65381 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -9,7 +9,9 @@ use namada_tx_prelude::*; fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let transfer = - PendingTransfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); + PendingTransfer::try_from_slice(&signed.data.unwrap()[..]) + .map_err(|_| Error::SimpleMessage("Error deserializing PendingTransfer"))?; + log_string("Received transfer to add to pool."); // pay the gas fees let GasFee { amount, ref payer } = transfer.gas_fee; token::transfer( @@ -22,14 +24,15 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { &None, &None, )?; + log_string("Token transfer succeeded."); let TransferToEthereum { - ref asset, + asset, ref sender, amount, .. } = transfer.transfer; // if minting wNam, escrow the correct amount - if *asset == native_erc20_address(ctx) { + if asset == native_erc20_address(ctx)? { token::transfer( ctx, sender, @@ -42,7 +45,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { )?; } else { // Otherwise we escrow ERC20 tokens. - let sub_prefix = wrapped_erc20s::sub_prefix(asset); + let sub_prefix = wrapped_erc20s::sub_prefix(&asset); token::transfer( ctx, sender, @@ -57,11 +60,15 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { // add transfer into the pool let pending_key = bridge_pool::get_pending_key(&transfer); ctx.write_bytes(&pending_key, transfer.try_to_vec().unwrap()) - .unwrap(); + .map_err(|_| Error::SimpleMessage("Could not write transfer to bridge pool"))?; Ok(()) } -fn native_erc20_address(ctx: &mut Ctx) -> EthAddress { - let addr = ctx.read_bytes(&native_erc20_key()).unwrap().unwrap(); - BorshDeserialize::try_from_slice(addr.as_slice()).unwrap() +fn native_erc20_address(ctx: &mut Ctx) -> EnvResult { + log_string("Trying to get wnam key"); + let addr = ctx.read_bytes(&native_erc20_key()) + .map_err(|_| Error::SimpleMessage("Could not read wNam key from storage"))? + .unwrap(); + log_string("Got wnam key"); + Ok(BorshDeserialize::try_from_slice(addr.as_slice()).unwrap()) } From 92921b8e48d437e79836fe47b9d2aed7786929b8 Mon Sep 17 00:00:00 2001 From: satan Date: Wed, 30 Nov 2022 14:35:23 +0100 Subject: [PATCH 1872/1995] [feat]: Added type alias for byte vector in merkle storage --- shared/src/ledger/storage/traits.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index b615abaa98..3b0a58725e 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -15,6 +15,7 @@ use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::eth_bridge_pool::PendingTransfer; +use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; From 7eb1d285b5b0d5d599f8980a7c1ea14460be7eeb Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 2 Dec 2022 15:52:29 +0100 Subject: [PATCH 1873/1995] [feat]: E2E tests for the bridge pool are now working --- apps/src/lib/client/eth_bridge_pool.rs | 17 +++- apps/src/lib/config/genesis.rs | 22 ++++- shared/src/ledger/storage/traits.rs | 1 - tests/src/e2e/eth_bridge_tests.rs | 111 ++++++++++++++++++++++++- tests/src/e2e/setup.rs | 2 + wasm/checksums.json | 38 +++++---- 6 files changed, 168 insertions(+), 23 deletions(-) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index b697995eb4..09171a9cc9 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -1,6 +1,9 @@ +use std::collections::HashMap; + use borsh::BorshSerialize; use namada::ledger::queries::RPC; use namada::proto::Tx; +use namada::types::eth_abi::Encode; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; @@ -67,11 +70,19 @@ pub async fn construct_bridge_pool_proof(args: args::BridgePoolProof) { /// Prints out a json payload. pub async fn query_bridge_pool(args: args::Query) { let client = HttpClient::new(args.ledger_address).unwrap(); - let response = RPC + let response: Vec = RPC .shell() .read_ethereum_bridge_pool(&client) .await .unwrap(); - - println!("{:#?}", serde_json::to_string_pretty(&response)); + let pool_contents: HashMap = response + .into_iter() + .map(|transfer| (transfer.keccak256().to_string(), transfer)) + .collect(); + if pool_contents.is_empty() { + println!("Bridge pool is empty."); + } else { + println!("Bridge pool contents: "); + } + println!("{}", serde_json::to_string_pretty(&pool_contents).unwrap()); } diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index d27827a1d9..ffbc22cbba 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,13 +6,16 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; -use namada::ledger::eth_bridge::parameters::EthereumBridgeConfig; +use namada::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, +}; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::types::address::Address; +use namada::types::address::{wnam, Address}; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; +use namada::types::ethereum_events::EthAddress; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::DateTimeUtc; @@ -881,7 +884,20 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - ethereum_bridge_params: None, + ethereum_bridge_params: Some(EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([0; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([1; 20]), + version: Default::default(), + }, + }, + }), } } diff --git a/shared/src/ledger/storage/traits.rs b/shared/src/ledger/storage/traits.rs index 3b0a58725e..b615abaa98 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/shared/src/ledger/storage/traits.rs @@ -15,7 +15,6 @@ use super::{ics23_specs, IBC_KEY_LIMIT}; use crate::ledger::eth_bridge::storage::bridge_pool::BridgePoolTree; use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::eth_bridge_pool::PendingTransfer; -use crate::ledger::storage::merkle_tree::StorageBytes; use crate::types::hash::Hash; use crate::types::storage::{Key, MembershipProof, StringKey, TreeBytes}; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 57d9fa4793..1dc629394b 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,11 +1,17 @@ use color_eyre::eyre::Result; +use namada::ledger::eth_bridge::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, +}; +use namada::types::address::wnam; +use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED; +use namada::types::ethereum_events::EthAddress; use namada_apps::config::ethereum_bridge; use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::get_actor_rpc; use crate::e2e::setup; use crate::e2e::setup::constants::{ - wasm_abs_path, ALBERT, TX_WRITE_STORAGE_KEY_WASM, + wasm_abs_path, ALBERT, BERTHA, NAM, TX_WRITE_STORAGE_KEY_WASM, }; use crate::e2e::setup::{Bin, Who}; use crate::{run, run_as}; @@ -125,3 +131,106 @@ fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { Ok(()) } + +/// In this test, we check the following: +/// 1. We can successfully add tranfers to the bridge pool. +/// 2. We can query the bridge pool and it is non-empty. +#[test] +fn test_add_to_bridge_pool() { + const LEDGER_STARTUP_TIMEOUT_SECONDS: u64 = 40; + const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 60; + const QUERY_TIMEOUT_SECONDS: u64 = 40; + const SOLE_VALIDATOR: Who = Who::Validator(0); + let wnam_address = wnam().to_canonical(); + let test = setup::network( + |mut genesis| { + genesis.ethereum_bridge_params = Some(EthereumBridgeConfig { + min_confirmations: Default::default(), + contracts: Contracts { + native_erc20: wnam(), + bridge: UpgradeableContract { + address: EthAddress([0; 20]), + version: Default::default(), + }, + governance: UpgradeableContract { + address: EthAddress([1; 20]), + version: Default::default(), + }, + }, + }); + genesis + }, + None, + ) + .unwrap(); + set_ethereum_bridge_mode( + &test, + &test.net.chain_id, + &Who::Validator(0), + ethereum_bridge::ledger::Mode::EventsEndpoint, + ); + + let mut namadan_ledger = run_as!( + test, + SOLE_VALIDATOR, + Bin::Node, + &["ledger"], + Some(LEDGER_STARTUP_TIMEOUT_SECONDS) + ) + .unwrap(); + namadan_ledger + .exp_string("Anoma ledger node started") + .unwrap(); + namadan_ledger + .exp_string("Tendermint node started") + .unwrap(); + namadan_ledger.exp_string("Committed block hash").unwrap(); + let _bg_ledger = namadan_ledger.background(); + + let ledger_addr = get_actor_rpc(&test, &SOLE_VALIDATOR); + let tx_args = vec![ + "add-erc20-transfer", + "--address", + BERTHA, + "--signer", + BERTHA, + "--amount", + "100", + "--erc20", + &wnam_address, + "--ethereum-address", + DAI_ERC20_ETH_ADDRESS_CHECKSUMMED, + "--fee-amount", + "10", + "--fee-payer", + BERTHA, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + &ledger_addr, + ]; + + let mut namadac_tx = run!( + test, + Bin::Client, + tx_args, + Some(CLIENT_COMMAND_TIMEOUT_SECONDS) + ) + .unwrap(); + namadac_tx.exp_string("Transaction applied").unwrap(); + namadac_tx.exp_string("Transaction is valid").unwrap(); + drop(namadac_tx); + + let mut namadar = run!( + test, + Bin::BridgePool, + ["query", "--ledger-address", &ledger_addr,], + Some(QUERY_TIMEOUT_SECONDS), + ) + .unwrap(); + namadar.exp_string("Bridge pool contents:").unwrap(); +} diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 773a93069c..cf02c3e790 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -253,6 +253,7 @@ pub enum Bin { Node, Client, Wallet, + BridgePool, } #[derive(Debug)] @@ -711,6 +712,7 @@ where Bin::Node => ("namadan", "info"), Bin::Client => ("namadac", "tendermint_rpc=debug"), Bin::Wallet => ("namadaw", "info"), + Bin::BridgePool => ("namadar", "info"), }; let mut run_cmd = generate_bin_command( diff --git a/wasm/checksums.json b/wasm/checksums.json index 24e4c0b43e..8e54008eac 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,25 @@ { - "tx_bond.wasm": "tx_bond.4fc2b1c226d57d94e4043109d1af164ac69f8eb72e12525d97a123d8100817af.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.f41d51ed01d5c60909c8ff9a7ed31f19b2d3ef42c24d1daa4143d59e0e797d97.wasm", - "tx_ibc.wasm": "tx_ibc.582a0a6433782caaee708205fc22f40d2af34fcd6994ac8f5fb8bef627778b4d.wasm", - "tx_init_account.wasm": "tx_init_account.0c204ba845658516b20670346c0682595d16d1ca99f42eddde51d1cde4295ffb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0a9cbdd68510d723818bb5d95cb0770b4f054ca402f8bbcff18108ea413875de.wasm", - "tx_init_validator.wasm": "tx_init_validator.09d0e561565cb083cb243b4594882c4f424eda73a91f89cce1e814b35ba2eae5.wasm", - "tx_transfer.wasm": "tx_transfer.9f4ad0aed0f1fcf21398c4d12da4ae26937415b01524811c0dceb810f1d1bf4a.wasm", - "tx_unbond.wasm": "tx_unbond.72018d106cca5a856bca041ea30eee579ec61d29f246f1387f3da4ef72c6cfbd.wasm", - "tx_update_vp.wasm": "tx_update_vp.4d585d52ed7f3b1507ed092df82ff7edec362305eb9e84f4dae06f09d75cc727.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.49638241211faf327390a0f07920d8dd40c9d7676d9c1beccd3bad0ff5f6f1a9.wasm", - "tx_withdraw.wasm": "tx_withdraw.1f8faa002868664e42d6e4b7140d5f295a2a0f1e99968656ee4d91c496b8f08d.wasm", - "vp_masp.wasm": "vp_masp.ad12a384f8690ad1a7c084b0a2ce7e72b9743fac7b2229f9a0e290fd0e75619a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e7d6cc83ad23f7db10484a051e6ba25105900b154e179768ccc79b955d6f47ca.wasm", - "vp_token.wasm": "vp_token.fbec5c10439c0f2d421799188f9ab677967b931535262dbf4cc7591ea0978aa7.wasm", - "vp_user.wasm": "vp_user.4db5100a828f04ed1fe6d2fee09b56facd8dde1e3bc0ea8d288875bf934d581a.wasm" + "tx_bond.wasm": "tx_bond.d39b2ae31f454a352d9ed31db2d4b55fadb2246eb7fb736890100de77fb57f84.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.5ad5652c40311fffb98f5b452c06928ceccb8a94fb6a1524368cce3e4445e8aa.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.2ddee7d15e2e9884f98c8a423a140ba9ac89417ccfcc8473615a464a0d4acf0a.wasm", + "tx_from_intent.wasm": "tx_from_intent.8d25dc3453f6ea39b7b2a3574ff47f26a17ee91e1c71f88adf116dd34cb198dc.wasm", + "tx_ibc.wasm": "tx_ibc.9ec5f2d7e3abf9847d414fcfbf5b1a2cadf166991c8ae941a8a10c6f2fc701fd.wasm", + "tx_init_account.wasm": "tx_init_account.2ca339b1a54239555853a5ace3aba1702d4a2027add6ba53ed1dd3814e45e1a1.wasm", + "tx_init_nft.wasm": "tx_init_nft.5fb223d13032f03e454a763125e69d9188b0bff54f14967161b91dd2bc8efc27.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c530e0ff5ab37cef227cbc1f26f58daf9c70ca15edc9e714f9ade6d0a989ec61.wasm", + "tx_init_validator.wasm": "tx_init_validator.6504445e5c18660bf8f4e1b42baadf1cd200943b092e90c8a93bdad40ebc7961.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1278e59c0ed5f0b9834ce121b69f7f3ccba91e7ff4be305b5fa898cdc324690c.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.b4d258ab70fdb136bacfa1c2caa765b596c796b330ff81c5756c080f2e21b36b.wasm", + "tx_transfer.wasm": "tx_transfer.ba8a40548e65d7af64e9151a44c4e45c9e4e5d0e8b4bd26b74ecc36fc752c349.wasm", + "tx_unbond.wasm": "tx_unbond.19f40a5bb264aacde8725ea475d73be3618281171d344bd6fc825e970e7fbcf9.wasm", + "tx_update_vp.wasm": "tx_update_vp.5dae0339928ab57bd938b1b10437127e9b01fd43f584103ba703424229a72618.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.60940ab81a965afbc5a1c973d5b4fbd917add369fe561c7ef2ba70b9835dbdae.wasm", + "tx_withdraw.wasm": "tx_withdraw.8496025271b977e5543647cd68452c2d17f4173537fd5390f0e8c379fdfc9a44.wasm", + "vp_implicit.wasm": "vp_implicit.ef275670c60bb360ae7ffe5e5153c06dd18ad63110ce47396114416bcf82ebb8.wasm", + "vp_masp.wasm": "vp_masp.e8b7276779cfd69eaa980f5e1cca3879073b0ac987170c58d0f867c9ecf0cb48.wasm", + "vp_nft.wasm": "vp_nft.173da472fb0b54d24a0e7f45ef78dc7a045b6119967045e37e20b6844db1dd4d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9ef0688ce368780e0d77160c22e3655bdde1295ac6357ce10f83eda04d9f2075.wasm", + "vp_token.wasm": "vp_token.4df4c16e3276ab80cd9be6f21c88b37091d17a7dc021b12f950c742be6ed3794.wasm", + "vp_user.wasm": "vp_user.997779f95f446909d5dc27b879ba31d8ebf811bb9875e329701521991b52657b.wasm", + "vp_validator.wasm": "vp_validator.e335315f0fcd81c869a66d1d9be9b98ea3e102076379a038cbf7eb86d2d85685.wasm" } \ No newline at end of file From 72e22c795666c7f69a22e2ae5c5915c349718d05 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Dec 2022 13:20:05 +0100 Subject: [PATCH 1874/1995] Update apps/src/lib/client/eth_bridge_pool.rs Co-authored-by: Tiago Carvalho --- apps/src/lib/client/eth_bridge_pool.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index 09171a9cc9..94f2ce1208 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -81,6 +81,7 @@ pub async fn query_bridge_pool(args: args::Query) { .collect(); if pool_contents.is_empty() { println!("Bridge pool is empty."); + return; } else { println!("Bridge pool contents: "); } From f94015b1f9e2d73d8a62ecd61dc06cb2d0105bf6 Mon Sep 17 00:00:00 2001 From: Jacob Turner Date: Tue, 6 Dec 2022 13:21:10 +0100 Subject: [PATCH 1875/1995] Update tests/src/e2e/eth_bridge_tests.rs Co-authored-by: Tiago Carvalho --- tests/src/e2e/eth_bridge_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1dc629394b..c447efe5cc 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -221,6 +221,7 @@ fn test_add_to_bridge_pool() { Some(CLIENT_COMMAND_TIMEOUT_SECONDS) ) .unwrap(); + namadac_tx.exp_string("Transaction accepted").unwrap(); namadac_tx.exp_string("Transaction applied").unwrap(); namadac_tx.exp_string("Transaction is valid").unwrap(); drop(namadac_tx); From 1c4aec6ba0ed86c63f577f79d90abd7a528c7052 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Dec 2022 12:39:24 +0000 Subject: [PATCH 1876/1995] Reject raw txs on CheckTx --- apps/src/lib/node/ledger/shell/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index b6c55c29cb..9bd6f81156 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -758,9 +758,16 @@ where be added to the mempool: {tx:?}" ); } - Ok(TxType::Wrapper(_) | TxType::Raw(_)) => { + Ok(TxType::Wrapper(_)) => { response.log = String::from(VALID_MSG); } + Ok(TxType::Raw(_)) => { + response.code = 1; + response.log = format!( + "{INVALID_MSG}: Raw transactions cannot be \ + accepted into the mempool" + ); + } Ok(TxType::Decrypted(_)) => { response.code = 1; response.log = format!( From 56412e2e9c695903f7f0947eb2391ab1efa69d96 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Dec 2022 12:47:26 +0000 Subject: [PATCH 1877/1995] Add a TODO item --- apps/src/lib/node/ledger/shell/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9bd6f81156..db30fefcc3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -695,6 +695,8 @@ where /// Validate a transaction request. On success, the transaction will /// included in the mempool and propagated to peers, otherwise it will be /// rejected. + // TODO: move this to another file after 0.11 merges, + // since this method has become fairly large at this point pub fn mempool_validate( &self, tx_bytes: &[u8], From 685d50b551adfa9d014381b7f0dfcb5501702878 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 6 Dec 2022 13:47:43 +0100 Subject: [PATCH 1878/1995] [feat]: Added e2e test to CI workflow --- .github/workflows/scripts/e2e.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index fd8939ba9a..e2fc12f9ea 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -1,5 +1,6 @@ { "e2e::eth_bridge_tests::everything": 4, + "e2e::eth_bridge_tests::test_add_to_bridge_pool": 10, "e2e::ledger_tests::double_signing_gets_slashed": 12, "e2e::ledger_tests::invalid_transactions": 8, "e2e::ledger_tests::ledger_many_txs_in_a_block": 41, From 67cb4133f8cd4f98094a15bb0c49d0d35e534c9d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 6 Dec 2022 12:54:38 +0000 Subject: [PATCH 1879/1995] CheckTx error message improvements --- apps/src/lib/node/ledger/shell/mod.rs | 44 ++++++++++--------- .../shell/vote_extensions/eth_events.rs | 1 + .../shell/vote_extensions/val_set_update.rs | 1 + 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index db30fefcc3..0e0f7a119e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -719,17 +719,19 @@ where tx: ProtocolTxType::EthEventsVext(ext), .. })) => { - if self.validate_eth_events_vext( - ext, - self.storage.last_height, - ) { - response.log = String::from(VALID_MSG); - } else { + if let Err(err) = self + .validate_eth_events_vext_and_get_it_back( + ext, + self.storage.last_height, + ) + { response.code = 1; response.log = format!( "{INVALID_MSG}: Invalid Ethereum events vote \ - extension", + extension: {err}", ); + } else { + response.log = String::from(VALID_MSG); } } #[cfg(not(feature = "abcipp"))] @@ -737,27 +739,29 @@ where tx: ProtocolTxType::ValSetUpdateVext(ext), .. })) => { - if self.validate_valset_upd_vext( - ext, - self.storage.last_height, - ) { - response.log = String::from(VALID_MSG); - // validator set update votes should be decided - // as soon as possible - response.priority = i64::MAX; - } else { + if let Err(err) = self + .validate_valset_upd_vext_and_get_it_back( + ext, + self.storage.last_height, + ) + { response.code = 1; response.log = format!( "{INVALID_MSG}: Invalid validator set update \ - vote extension", + vote extension: {err}", ); + } else { + response.log = String::from(VALID_MSG); + // validator set update votes should be decided + // as soon as possible + response.priority = i64::MAX; } } - Ok(TxType::Protocol(ProtocolTx { tx, .. })) => { + Ok(TxType::Protocol(ProtocolTx { .. })) => { response.code = 1; response.log = format!( - "{INVALID_MSG}: The following protocol tx cannot \ - be added to the mempool: {tx:?}" + "{INVALID_MSG}: The given protocol tx cannot be \ + added to the mempool" ); } Ok(TxType::Wrapper(_)) => { diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 2be4a2bdf4..a0f4850cc2 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -33,6 +33,7 @@ where /// * There are no duplicate Ethereum events in this vote extension, and /// the events are sorted in ascending order. #[inline] + #[allow(dead_code)] pub fn validate_eth_events_vext( &self, ext: Signed, diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 719476a42f..985c3ba2a5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -34,6 +34,7 @@ where /// * The voting powers are normalized to `2^32`, and sorted in descending /// order. #[inline] + #[allow(dead_code)] pub fn validate_valset_upd_vext( &self, ext: validator_set_update::SignedVext, From b6cea36f508fcd77b3752254ea747fbc5d8f661b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 6 Dec 2022 15:25:30 +0100 Subject: [PATCH 1880/1995] move eth-bridge types and storage mods to core --- {shared => core}/src/ledger/eth_bridge/mod.rs | 0 {shared => core}/src/ledger/eth_bridge/storage/bridge_pool.rs | 0 {shared => core}/src/ledger/eth_bridge/storage/mod.rs | 0 {shared => core}/src/ledger/eth_bridge/storage/wrapped_erc20s.rs | 0 {shared => core}/src/types/eth_abi.rs | 0 {shared => core}/src/types/eth_bridge_pool.rs | 0 {shared => core}/src/types/ethereum_events.rs | 0 {shared => core}/src/types/keccak.rs | 0 {shared => core}/src/types/vote_extensions.rs | 0 {shared => core}/src/types/vote_extensions/ethereum_events.rs | 0 .../src/types/vote_extensions/validator_set_update.rs | 0 {shared => core}/src/types/voting_power.rs | 0 .../storage_api/queries.rs => shared/src/ledger/queries_ext.rs | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {shared => core}/src/ledger/eth_bridge/mod.rs (100%) rename {shared => core}/src/ledger/eth_bridge/storage/bridge_pool.rs (100%) rename {shared => core}/src/ledger/eth_bridge/storage/mod.rs (100%) rename {shared => core}/src/ledger/eth_bridge/storage/wrapped_erc20s.rs (100%) rename {shared => core}/src/types/eth_abi.rs (100%) rename {shared => core}/src/types/eth_bridge_pool.rs (100%) rename {shared => core}/src/types/ethereum_events.rs (100%) rename {shared => core}/src/types/keccak.rs (100%) rename {shared => core}/src/types/vote_extensions.rs (100%) rename {shared => core}/src/types/vote_extensions/ethereum_events.rs (100%) rename {shared => core}/src/types/vote_extensions/validator_set_update.rs (100%) rename {shared => core}/src/types/voting_power.rs (100%) rename core/src/ledger/storage_api/queries.rs => shared/src/ledger/queries_ext.rs (100%) diff --git a/shared/src/ledger/eth_bridge/mod.rs b/core/src/ledger/eth_bridge/mod.rs similarity index 100% rename from shared/src/ledger/eth_bridge/mod.rs rename to core/src/ledger/eth_bridge/mod.rs diff --git a/shared/src/ledger/eth_bridge/storage/bridge_pool.rs b/core/src/ledger/eth_bridge/storage/bridge_pool.rs similarity index 100% rename from shared/src/ledger/eth_bridge/storage/bridge_pool.rs rename to core/src/ledger/eth_bridge/storage/bridge_pool.rs diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs similarity index 100% rename from shared/src/ledger/eth_bridge/storage/mod.rs rename to core/src/ledger/eth_bridge/storage/mod.rs diff --git a/shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs similarity index 100% rename from shared/src/ledger/eth_bridge/storage/wrapped_erc20s.rs rename to core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs diff --git a/shared/src/types/eth_abi.rs b/core/src/types/eth_abi.rs similarity index 100% rename from shared/src/types/eth_abi.rs rename to core/src/types/eth_abi.rs diff --git a/shared/src/types/eth_bridge_pool.rs b/core/src/types/eth_bridge_pool.rs similarity index 100% rename from shared/src/types/eth_bridge_pool.rs rename to core/src/types/eth_bridge_pool.rs diff --git a/shared/src/types/ethereum_events.rs b/core/src/types/ethereum_events.rs similarity index 100% rename from shared/src/types/ethereum_events.rs rename to core/src/types/ethereum_events.rs diff --git a/shared/src/types/keccak.rs b/core/src/types/keccak.rs similarity index 100% rename from shared/src/types/keccak.rs rename to core/src/types/keccak.rs diff --git a/shared/src/types/vote_extensions.rs b/core/src/types/vote_extensions.rs similarity index 100% rename from shared/src/types/vote_extensions.rs rename to core/src/types/vote_extensions.rs diff --git a/shared/src/types/vote_extensions/ethereum_events.rs b/core/src/types/vote_extensions/ethereum_events.rs similarity index 100% rename from shared/src/types/vote_extensions/ethereum_events.rs rename to core/src/types/vote_extensions/ethereum_events.rs diff --git a/shared/src/types/vote_extensions/validator_set_update.rs b/core/src/types/vote_extensions/validator_set_update.rs similarity index 100% rename from shared/src/types/vote_extensions/validator_set_update.rs rename to core/src/types/vote_extensions/validator_set_update.rs diff --git a/shared/src/types/voting_power.rs b/core/src/types/voting_power.rs similarity index 100% rename from shared/src/types/voting_power.rs rename to core/src/types/voting_power.rs diff --git a/core/src/ledger/storage_api/queries.rs b/shared/src/ledger/queries_ext.rs similarity index 100% rename from core/src/ledger/storage_api/queries.rs rename to shared/src/ledger/queries_ext.rs From e95d71a03127d2c9848f427648b84f0652660bb1 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 6 Dec 2022 16:02:38 +0100 Subject: [PATCH 1881/1995] [feat]: Birdge pool query now returns only a json payload --- apps/src/lib/client/eth_bridge_pool.rs | 13 ++++++++++--- tests/src/e2e/eth_bridge_tests.rs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/eth_bridge_pool.rs b/apps/src/lib/client/eth_bridge_pool.rs index 94f2ce1208..1f4594c92b 100644 --- a/apps/src/lib/client/eth_bridge_pool.rs +++ b/apps/src/lib/client/eth_bridge_pool.rs @@ -7,6 +7,7 @@ use namada::types::eth_abi::Encode; use namada::types::eth_bridge_pool::{ GasFee, PendingTransfer, TransferToEthereum, }; +use serde::{Deserialize, Serialize}; use super::signing::TxSigningKey; use super::tx::process_tx; @@ -66,6 +67,13 @@ pub async fn construct_bridge_pool_proof(args: args::BridgePoolProof) { ); } +/// A json serializable representation of the Ethereum +/// bridge pool. +#[derive(Serialize, Deserialize)] +struct BridgePoolResponse { + bridge_pool_contents: HashMap, +} + /// Query the contents of the Ethereum bridge pool. /// Prints out a json payload. pub async fn query_bridge_pool(args: args::Query) { @@ -82,8 +90,7 @@ pub async fn query_bridge_pool(args: args::Query) { if pool_contents.is_empty() { println!("Bridge pool is empty."); return; - } else { - println!("Bridge pool contents: "); } - println!("{}", serde_json::to_string_pretty(&pool_contents).unwrap()); + let contents = BridgePoolResponse{ bridge_pool_contents: pool_contents}; + println!("{}", serde_json::to_string_pretty(&contents).unwrap()); } diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index c447efe5cc..b124f967b9 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -233,5 +233,5 @@ fn test_add_to_bridge_pool() { Some(QUERY_TIMEOUT_SECONDS), ) .unwrap(); - namadar.exp_string("Bridge pool contents:").unwrap(); + namadar.exp_string(r#""bridge_pool_contents":"#).unwrap(); } From db48ac94986d03bbe29d22e03fff8d832e55d271 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 6 Dec 2022 16:10:33 +0100 Subject: [PATCH 1882/1995] [fix]: Removed DAI address as receiver address from e2e test --- tests/src/e2e/eth_bridge_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index b124f967b9..9d39c6ce03 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -3,7 +3,6 @@ use namada::ledger::eth_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; use namada::types::address::wnam; -use namada::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS_CHECKSUMMED; use namada::types::ethereum_events::EthAddress; use namada_apps::config::ethereum_bridge; @@ -141,6 +140,7 @@ fn test_add_to_bridge_pool() { const CLIENT_COMMAND_TIMEOUT_SECONDS: u64 = 60; const QUERY_TIMEOUT_SECONDS: u64 = 40; const SOLE_VALIDATOR: Who = Who::Validator(0); + const RECEIVER: &str = "0x6B175474E89094C55Da98b954EedeAC495271d0F"; let wnam_address = wnam().to_canonical(); let test = setup::network( |mut genesis| { @@ -199,7 +199,7 @@ fn test_add_to_bridge_pool() { "--erc20", &wnam_address, "--ethereum-address", - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED, + RECEIVER, "--fee-amount", "10", "--fee-payer", From a123542514dbb4f5900e54287fc831098ea195c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 6 Dec 2022 16:36:11 +0100 Subject: [PATCH 1883/1995] fix 0.11.0 merge --- Cargo.lock | 25 +- apps/Cargo.toml | 2 +- .../{anoma-relayer => namada-relayer}/cli.rs | 4 +- .../{anoma-relayer => namada-relayer}/main.rs | 0 apps/src/lib/cli.rs | 11 +- apps/src/lib/client/tx.rs | 2 +- apps/src/lib/config/genesis.rs | 3 +- .../node/ledger/ethereum_node/oracle/mod.rs | 6 +- .../lib/node/ledger/shell/finalize_block.rs | 1 - apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 11 +- .../lib/node/ledger/shell/prepare_proposal.rs | 23 +- .../lib/node/ledger/shell/process_proposal.rs | 4 +- apps/src/lib/node/ledger/shell/queries.rs | 116 +++---- .../lib/node/ledger/shell/vote_extensions.rs | 2 +- .../shell/vote_extensions/eth_events.rs | 12 +- .../shell/vote_extensions/val_set_update.rs | 12 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 1 + apps/src/lib/node/ledger/tendermint_node.rs | 2 +- apps/src/lib/wallet/defaults.rs | 2 +- core/Cargo.toml | 4 + core/src/ledger/eth_bridge/mod.rs | 6 +- core/src/ledger/eth_bridge/storage/mod.rs | 1 - core/src/ledger/mod.rs | 1 + core/src/ledger/storage/mod.rs | 34 ++- core/src/ledger/storage_api/mod.rs | 1 - core/src/types/eth_abi.rs | 21 +- core/src/types/ethereum_events.rs | 26 +- core/src/types/hash.rs | 41 +-- core/src/types/keccak.rs | 9 +- core/src/types/key/mod.rs | 26 ++ core/src/types/key/secp256k1.rs | 10 +- core/src/types/mod.rs | 6 + core/src/types/storage.rs | 44 +-- .../vote_extensions/validator_set_update.rs | 14 +- core/src/types/voting_power.rs | 4 +- .../ethereum-bridge/transfers_to_namada.md | 4 +- proof_of_stake/src/lib.rs | 31 +- proof_of_stake/src/storage.rs | 16 +- shared/Cargo.toml | 10 +- .../src/ledger/eth_bridge/bridge_pool_vp.rs | 10 +- shared/src/ledger/eth_bridge/mod.rs | 7 + shared/src/ledger/eth_bridge/storage/mod.rs | 4 + shared/src/ledger/eth_bridge/vp/mod.rs | 4 +- shared/src/ledger/mod.rs | 1 + shared/src/ledger/native_vp/mod.rs | 5 +- shared/src/ledger/pos/mod.rs | 2 +- shared/src/ledger/pos/vp.rs | 2 + .../transactions/ethereum_events/mod.rs | 2 +- .../src/ledger/protocol/transactions/utils.rs | 57 ++-- .../transactions/validator_set_update/mod.rs | 2 +- shared/src/ledger/queries/shell.rs | 32 +- shared/src/ledger/queries_ext.rs | 286 +++++++++++++++++- shared/src/types/mod.rs | 11 +- tests/src/e2e/eth_bridge_tests.rs | 2 +- tests/src/native_vp/eth_bridge_pool.rs | 2 +- tx_prelude/src/lib.rs | 2 +- tx_prelude/src/proof_of_stake.rs | 12 +- wasm/Cargo.lock | 250 +++++++++++++-- wasm/checksums.json | 36 ++- .../src/tx_change_validator_commission.rs | 4 + wasm/wasm_source/src/tx_unbond.rs | 4 +- wasm_for_tests/tx_memory_limit.wasm | Bin 133398 -> 133415 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 352856 -> 356158 bytes wasm_for_tests/tx_no_op.wasm | Bin 24984 -> 25554 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201328 -> 205390 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152564 -> 152627 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 226646 -> 228291 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156536 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156536 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157473 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158984 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170817 -> 168360 bytes wasm_for_tests/wasm_source/Cargo.lock | 14 +- 74 files changed, 893 insertions(+), 408 deletions(-) rename apps/src/bin/{anoma-relayer => namada-relayer}/cli.rs (86%) rename apps/src/bin/{anoma-relayer => namada-relayer}/main.rs (100%) create mode 100644 shared/src/ledger/eth_bridge/mod.rs create mode 100644 shared/src/ledger/eth_bridge/storage/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8315106a65..fcfc0371f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4043,6 +4043,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", + "ferveo-common", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -4054,15 +4055,17 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", - "num-rational 0.4.1", - "parity-wasm 0.45.0", + "parity-wasm", "paste", "pretty_assertions", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", + "serde 1.0.147", "serde_json", "sha2 0.9.9", "tempfile", @@ -4074,7 +4077,6 @@ dependencies = [ "tendermint-rpc 0.23.6", "test-log", "thiserror", - "tiny-keccak", "tokio", "toml", "tracing 0.1.37", @@ -4201,6 +4203,8 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", @@ -4212,6 +4216,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "num-rational 0.4.1", "pretty_assertions", "proptest", "prost", @@ -4231,6 +4236,7 @@ dependencies = [ "tendermint-proto 0.23.6", "test-log", "thiserror", + "tiny-keccak", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.16", @@ -4828,12 +4834,6 @@ dependencies = [ "syn", ] -[[package]] -name = "parity-wasm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" - [[package]] name = "parity-wasm" version = "0.45.0" @@ -5309,13 +5309,12 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" +version = "0.20.0" +source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" dependencies = [ "byteorder", "log 0.4.17", - "parity-wasm 0.42.2", + "parity-wasm", ] [[package]] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index a498528a97..2aca234899 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -43,7 +43,7 @@ path = "src/bin/namada-wallet/main.rs" [[bin]] doc = false name = "namadar" -path = "src/bin/anoma-relayer/main.rs" +path = "src/bin/namada-relayer/main.rs" [features] default = ["std", "abciplus"] diff --git a/apps/src/bin/anoma-relayer/cli.rs b/apps/src/bin/namada-relayer/cli.rs similarity index 86% rename from apps/src/bin/anoma-relayer/cli.rs rename to apps/src/bin/namada-relayer/cli.rs index 4dbab91796..16576a2a82 100644 --- a/apps/src/bin/anoma-relayer/cli.rs +++ b/apps/src/bin/namada-relayer/cli.rs @@ -1,4 +1,4 @@ -//! Anoma client CLI. +//! Namada client CLI. use color_eyre::eyre::Result; use namada_apps::cli; @@ -6,7 +6,7 @@ use namada_apps::cli::cmds; use namada_apps::client::eth_bridge_pool; pub async fn main() -> Result<()> { - let (cmd, _) = cli::anoma_relayer_cli()?; + let (cmd, _) = cli::namada_relayer_cli()?; use cmds::EthBridgePool as Sub; match cmd { Sub::ConstructProof(args) => { diff --git a/apps/src/bin/anoma-relayer/main.rs b/apps/src/bin/namada-relayer/main.rs similarity index 100% rename from apps/src/bin/anoma-relayer/main.rs rename to apps/src/bin/namada-relayer/main.rs diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 7a34db35ff..9607d3e276 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1514,7 +1514,7 @@ pub mod cmds { } } - /// Used as sub-commands (`SubCmd` instance) in `anoma` binary. + /// Used as sub-commands (`SubCmd` instance) in `namada` binary. #[derive(Clone, Debug)] pub enum EthBridgePool { /// Construct a proof that a set of transfers is in the pool. @@ -1690,7 +1690,6 @@ pub mod args { arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); const FEE_PAYER: Arg = arg("fee-payer"); const FORCE: ArgFlag = flag("force"); - const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_AMOUNT: ArgDefault = arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = @@ -3029,7 +3028,7 @@ pub mod args { .arg(GAS_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(GAS_TOKEN.def().about("The token for paying the gas")) + .arg(GAS_TOKEN.def().about("The token for paying the fee")) .arg( GAS_LIMIT.def().about( "The maximum amount of gas needed to run transaction", @@ -3743,10 +3742,10 @@ fn namada_wallet_app() -> App { cmds::NamadaWallet::add_sub(args::Global::def(app)) } -fn anoma_relayer_app() -> App { +fn namada_relayer_app() -> App { let app = App::new(APP_NAME) - .version(anoma_version()) - .about("Anoma Ethereum bridge pool command line interface.") + .version(namada_version()) + .about("Namada Ethereum bridge pool command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); cmds::EthBridgePool::add_sub(args::Global::def(app)) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 35a27384b0..9334eb6dd7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -50,6 +50,7 @@ use namada::types::storage::{ self, BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, }; use namada::types::time::DateTimeUtc; +use namada::types::token; use namada::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; @@ -57,7 +58,6 @@ use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use namada::types::{address, storage, token}; use namada::{ledger, vm}; use rand_core::{CryptoRng, OsRng, RngCore}; use rust_decimal::Decimal; diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c62333f139..01814335b2 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -522,6 +522,7 @@ pub mod genesis_config { pos_params, gov_params, wasm, + ethereum_bridge_params, } = config; let native_token = Address::decode( @@ -656,7 +657,7 @@ pub mod genesis_config { parameters, pos_params, gov_params, - ethereum_bridge_params: config.ethereum_bridge_params, + ethereum_bridge_params, }; genesis.init(); genesis diff --git a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs index eb32e2b1c2..b23747be0c 100644 --- a/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs +++ b/apps/src/lib/node/ledger/ethereum_node/oracle/mod.rs @@ -22,7 +22,7 @@ use super::test_tools::mock_web3_client::Web3; const DEFAULT_BACKOFF: Duration = std::time::Duration::from_secs(1); /// A client that can talk to geth and parse -/// and relay events relevant to Anoma to the +/// and relay events relevant to Namada to the /// ledger process pub struct Oracle { /// The client that talks to the Ethereum fullnode @@ -61,7 +61,7 @@ impl Oracle { } } - /// Send a series of [`EthereumEvent`]s to the Anoma + /// Send a series of [`EthereumEvent`]s to the Namada /// ledger. Returns a boolean indicating that all sent /// successfully. If false is returned, the receiver /// has hung up. @@ -131,7 +131,7 @@ pub fn run_oracle( } /// Given an oracle, watch for new Ethereum events, processing -/// them into Anoma native types. +/// them into Namada native types. /// /// It also checks that once the specified number of confirmations /// is reached, an event is forwarded to the ledger process diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index df76865d88..f5a4b40e58 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -377,7 +377,6 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_finalize_block { - use namada::types::address::nam; use namada::types::ethereum_events::EthAddress; use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee}; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 1819d48525..5b5203b9f3 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -225,7 +225,7 @@ where ) { // Initialize genesis implicit for genesis::ImplicitAccount { public_key } in accounts { - let address: address::Address = (&public_key).into(); + let address: Address = (&public_key).into(); let pk_storage_key = pk_key(&address); self.storage .write(&pk_storage_key, public_key.try_to_vec().unwrap()) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index dc4e25e10d..a28f83b710 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -224,7 +224,7 @@ impl EthereumReceiver { impl ShellMode { /// Get the validator address if ledger is in validator mode - pub fn get_validator_address(&self) -> Option<&address::Address> { + pub fn get_validator_address(&self) -> Option<&Address> { match &self { ShellMode::Validator { data, .. } => Some(&data.address), _ => None, @@ -346,6 +346,7 @@ where { /// Create a new shell from a path to a database and a chain id. Looks /// up the database with this data and tries to load the last state. + #[allow(clippy::too_many_arguments)] pub fn new( config: config::Ledger, wasm_dir: PathBuf, @@ -826,11 +827,9 @@ mod test_utils { use std::ops::{Deref, DerefMut}; use std::path::PathBuf; - #[cfg(not(feature = "abcipp"))] - use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; - use namada::types::address::EstablishedAddressGen; + use namada::types::address::{self, EstablishedAddressGen}; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; @@ -1051,8 +1050,8 @@ mod test_utils { /// Get the only validator's voting power. #[inline] #[cfg(not(feature = "abcipp"))] - pub fn get_validator_voting_power() -> VotingPower { - 200.into() + pub fn get_validator_bonded_stake() -> namada::types::token::Amount { + 200_000_000_000.into() } /// Start a new test shell and initialize it. Returns the shell paired with diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c4497c1fde..7d1c2d0742 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,11 +1,11 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell -use namada::ledger::storage::traits::StorageHasher; -use namada::ledger::storage::{DBIter, DB}; #[cfg(feature = "abcipp")] -use namada::ledger::storage_api::queries::QueriesExt; +use namada::ledger::queries_ext::QueriesExt; #[cfg(feature = "abcipp")] -use namada::ledger::storage_api::queries::SendValsetUpd; +use namada::ledger::queries_ext::SendValsetUpd; +use namada::ledger::storage::traits::StorageHasher; +use namada::ledger::storage::{DBIter, DB}; use namada::proto::Tx; use namada::types::storage::BlockHeight; use namada::types::transaction::tx_types::TxType; @@ -179,12 +179,12 @@ where } /// Builds a batch of DKG decrypted transactions - // TODO: we won't have frontrunning protection until V2 of the Anoma + // TODO: we won't have frontrunning protection until V2 of the Namada // protocol; Namada runs V1, therefore this method is // essentially a NOOP, and ought to be removed // // sources: - // - https://specs.anoma.net/main/releases/v2.html + // - https://specs.namada.net/main/releases/v2.html // - https://github.com/anoma/ferveo fn build_decrypted_txs(&mut self) -> Vec { // TODO: This should not be hardcoded @@ -221,13 +221,10 @@ mod test_prepare_proposal { use std::collections::{BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSerialize}; - use namada::ledger::pos::namada_proof_of_stake::types::{ - VotingPower, WeightedValidator, - }; + use namada::ledger::pos::namada_proof_of_stake::types::WeightedValidator; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::storage_api::queries::QueriesExt; + use namada::ledger::queries_ext::QueriesExt; use namada::proto::{Signed, SignedTxData}; - use namada::types::address::nam; use namada::types::ethereum_events::EthereumEvent; #[cfg(feature = "abcipp")] use namada::types::key::common; @@ -400,7 +397,7 @@ mod test_prepare_proposal { assert_eq!( filtered_votes, vec![( - test_utils::get_validator_voting_power(), + test_utils::get_validator_bonded_stake(), signed_vote_extension )] ) @@ -690,7 +687,7 @@ mod test_prepare_proposal { .iter() .cloned() .map(|v| WeightedValidator { - voting_power: VotingPower::from(0u64), + bonded_stake: 0, ..v }) .collect(); diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5a79eb68fa..8408c1db47 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,9 +4,9 @@ use data_encoding::HEXUPPER; #[cfg(feature = "abcipp")] use namada::ledger::pos::types::VotingPower; -use namada::ledger::storage_api::queries::QueriesExt; +use namada::ledger::queries_ext::QueriesExt; #[cfg(feature = "abcipp")] -use namada::ledger::storage_api::queries::SendValsetUpd; +use namada::ledger::queries_ext::SendValsetUpd; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 860142633a..ed3a0bac14 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,9 +1,11 @@ //! Shell methods for querying state -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use ferveo_common::TendermintValidator; use namada::ledger::pos::into_tm_voting_power; use namada::ledger::queries::{RequestCtx, ResponseQuery}; +use namada::types::key; +use namada::types::key::dkg_session_keys::DkgPublicKey; use super::*; use crate::node::ledger::response; @@ -57,14 +59,68 @@ where }, } } + + /// Lookup data about a validator from their protocol signing key + #[allow(dead_code)] + pub fn get_validator_from_protocol_pk( + &self, + pk: &common::PublicKey, + ) -> Option> { + let pk_bytes = pk + .try_to_vec() + .expect("Serializing public key should not fail"); + // get the current epoch + let (current_epoch, _) = self.storage.get_current_epoch(); + // get the PoS params + let pos_params = self.storage.read_pos_params(); + // get the active validator set + self.storage + .read_validator_set() + .get(current_epoch) + .expect("Validators for the next epoch should be known") + .active + .iter() + .find(|validator| { + let pk_key = key::protocol_pk_key(&validator.address); + match self.storage.read(&pk_key) { + Ok((Some(bytes), _)) => bytes == pk_bytes, + _ => false, + } + }) + .map(|validator| { + let dkg_key = + key::dkg_session_keys::dkg_pk_key(&validator.address); + let bytes = self + .storage + .read(&dkg_key) + .expect("Validator should have public dkg key") + .0 + .expect("Validator should have public dkg key"); + let dkg_publickey = + &::deserialize( + &mut bytes.as_ref(), + ) + .expect( + "DKG public key in storage should be deserializable", + ); + TendermintValidator { + power: into_tm_voting_power( + pos_params.tm_votes_per_token, + validator.bonded_stake, + ) as u64, + address: validator.address.to_string(), + public_key: dkg_publickey.into(), + } + }) + } } -// NOTE: we are testing `namada::ledger::storage_api::queries`, +// NOTE: we are testing `namada::ledger::queries_ext`, // which is not possible from `namada` since we do not have // access to the `Shell` there #[cfg(test)] mod test_queries { - use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; + use namada::ledger::queries_ext::{QueriesExt, SendValsetUpd}; use namada::types::storage::Epoch; use super::*; @@ -175,58 +231,4 @@ mod test_queries { (2, 28, false), ], } - - /// Lookup data about a validator from their protocol signing key - #[allow(dead_code)] - pub fn get_validator_from_protocol_pk( - &self, - pk: &key::common::PublicKey, - ) -> Option> { - let pk_bytes = pk - .try_to_vec() - .expect("Serializing public key should not fail"); - // get the current epoch - let (current_epoch, _) = self.storage.get_current_epoch(); - // get the PoS params - let pos_params = self.storage.read_pos_params(); - // get the active validator set - self.storage - .read_validator_set() - .get(current_epoch) - .expect("Validators for the next epoch should be known") - .active - .iter() - .find(|validator| { - let pk_key = key::protocol_pk_key(&validator.address); - match self.storage.read(&pk_key) { - Ok((Some(bytes), _)) => bytes == pk_bytes, - _ => false, - } - }) - .map(|validator| { - let dkg_key = - key::dkg_session_keys::dkg_pk_key(&validator.address); - let bytes = self - .storage - .read(&dkg_key) - .expect("Validator should have public dkg key") - .0 - .expect("Validator should have public dkg key"); - let dkg_publickey = - &::deserialize( - &mut bytes.as_ref(), - ) - .expect( - "DKG public key in storage should be deserializable", - ); - TendermintValidator { - power: into_tm_voting_power( - pos_params.tm_votes_per_token, - validator.bonded_stake, - ) as u64, - address: validator.address.to_string(), - public_key: dkg_publickey.into(), - } - }) - } } diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c2a7f132e9..a22c482340 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -5,7 +5,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; -use namada::ledger::storage_api::queries::{QueriesExt, SendValsetUpd}; +use namada::ledger::queries_ext::{QueriesExt, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 2be4a2bdf4..d80a938fc7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -2,13 +2,13 @@ use std::collections::{BTreeMap, HashMap}; -use namada::ledger::pos::namada_proof_of_stake::types::VotingPower; +use namada::ledger::queries_ext::QueriesExt; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::storage_api::queries::QueriesExt; use namada::proto::Signed; use namada::types::ethereum_events::EthereumEvent; use namada::types::storage::BlockHeight; +use namada::types::token; use namada::types::vote_extensions::ethereum_events::{ self, MultiSignedEthEvent, }; @@ -50,7 +50,7 @@ where ext: Signed, last_height: BlockHeight, ) -> std::result::Result< - (VotingPower, Signed), + (token::Amount, Signed), VoteExtensionError, > { #[cfg(feature = "abcipp")] @@ -165,7 +165,7 @@ where + 'iter, ) -> impl Iterator< Item = std::result::Result< - (VotingPower, Signed), + (token::Amount, Signed), VoteExtensionError, >, > + 'iter { @@ -184,7 +184,7 @@ where &'iter self, vote_extensions: impl IntoIterator> + 'iter, - ) -> impl Iterator)> + 'iter + ) -> impl Iterator)> + 'iter { self.validate_eth_events_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) @@ -308,7 +308,7 @@ mod test_vote_extensions { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::storage_api::queries::QueriesExt; + use namada::ledger::queries_ext::QueriesExt; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 719476a42f..82fbaeb6f5 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,11 +4,11 @@ use std::collections::HashMap; use namada::ledger::pos::namada_proof_of_stake::PosBase; -use namada::ledger::pos::types::VotingPower; +use namada::ledger::queries_ext::QueriesExt; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; -use namada::ledger::storage_api::queries::QueriesExt; use namada::types::storage::BlockHeight; +use namada::types::token; use namada::types::vote_extensions::validator_set_update; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; @@ -51,7 +51,7 @@ where ext: validator_set_update::SignedVext, last_height: BlockHeight, ) -> std::result::Result< - (VotingPower, validator_set_update::SignedVext), + (token::Amount, validator_set_update::SignedVext), VoteExtensionError, > { #[cfg(feature = "abcipp")] @@ -171,7 +171,7 @@ where + 'static, ) -> impl Iterator< Item = std::result::Result< - (VotingPower, validator_set_update::SignedVext), + (token::Amount, validator_set_update::SignedVext), VoteExtensionError, >, > + '_ { @@ -190,7 +190,7 @@ where &self, vote_extensions: impl IntoIterator + 'static, - ) -> impl Iterator + '_ + ) -> impl Iterator + '_ { self.validate_valset_upd_vext_list(vote_extensions) .filter_map(|ext| ext.ok()) @@ -318,7 +318,7 @@ mod test_vote_extensions { use borsh::BorshSerialize; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::storage_api::queries::QueriesExt; + use namada::ledger::queries_ext::QueriesExt; use namada::types::key::RefTo; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::ethereum_events; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 7ee55b608b..3f351b89b3 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -45,6 +45,7 @@ pub struct AbcippShim { impl AbcippShim { /// Create a shell with a ABCI service that passes messages to and from the /// shell. + #[allow(clippy::too_many_arguments)] pub fn new( config: config::Ledger, wasm_dir: PathBuf, diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index f8b3820a13..2fdc6ce4a9 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -49,7 +49,7 @@ async fn get_version(tendermint_path: &str) -> eyre::Result { /// Runs `tendermint version` and returns the output as a string async fn run_version_command(tendermint_path: &str) -> eyre::Result { - let output = Command::new(&tendermint_path) + let output = Command::new(tendermint_path) .arg("version") .output() .await?; diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 5cbdd11d9d..df251da589 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -19,7 +19,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { let mut addresses: Vec<(Alias, Address)> = vec![ ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), - ("governance".into(), governance::vp::ADDRESS), + ("governance".into(), governance::ADDRESS), ("eth_bridge".into(), eth_bridge::ADDRESS), ]; // Genesis validators diff --git a/core/Cargo.toml b/core/Cargo.toml index 5749522136..689d5171e6 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -66,6 +66,8 @@ chrono = {version = "0.4.22", default-features = false, features = ["clock", "st data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" +ethabi = "17.0.0" +eyre = "0.6.8" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} @@ -78,6 +80,7 @@ ics23 = "0.7.0" itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +num-rational = "0.4.1" proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" prost-types = "0.9.0" @@ -94,6 +97,7 @@ tendermint-proto = {version = "0.23.6", optional = true} tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} thiserror = "1.0.30" +tiny-keccak = {version = "2.0.2", features = ["keccak"]} tracing = "0.1.30" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} diff --git a/core/src/ledger/eth_bridge/mod.rs b/core/src/ledger/eth_bridge/mod.rs index a740fa16a2..5932c2c8d7 100644 --- a/core/src/ledger/eth_bridge/mod.rs +++ b/core/src/ledger/eth_bridge/mod.rs @@ -1,8 +1,6 @@ -//! Validity predicate and storage keys for the Ethereum bridge account -pub mod bridge_pool_vp; -pub mod parameters; +//! Storage keys for the Ethereum bridge account + pub mod storage; -pub mod vp; use crate::types::address::{Address, InternalAddress}; diff --git a/core/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs index 4a4a5de395..60160710e1 100644 --- a/core/src/ledger/eth_bridge/storage/mod.rs +++ b/core/src/ledger/eth_bridge/storage/mod.rs @@ -1,6 +1,5 @@ //! Functionality for accessing the storage subspace pub mod bridge_pool; -pub mod vote_tallies; pub mod wrapped_erc20s; use super::ADDRESS; diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index 83568c0da7..9a929cccd3 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -1,5 +1,6 @@ //! The ledger modules +pub mod eth_bridge; pub mod gas; pub mod governance; #[cfg(any(feature = "abciplus", feature = "abcipp"))] diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 3b2c5b59e8..55e8ced657 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -27,7 +27,6 @@ use rayon::prelude::ParallelSlice; use thiserror::Error; pub use traits::{Sha256Hasher, StorageHasher}; -use self::merkle_tree::StorageBytes; use crate::ledger::gas::MIN_STORAGE_GAS; use crate::ledger::parameters::{self, EpochDuration, Parameters}; use crate::ledger::storage::merkle_tree::{ @@ -46,7 +45,7 @@ use crate::types::chain::{ChainId, CHAIN_ID_LENGTH}; use crate::types::internal::TxQueue; use crate::types::storage::{ BlockHash, BlockHeight, BlockResults, Epoch, Epochs, Header, Key, KeySeg, - MembershipProof, TxIndex, BLOCK_HASH_LENGTH, + TxIndex, BLOCK_HASH_LENGTH, }; use crate::types::time::DateTimeUtc; use crate::types::token; @@ -628,19 +627,27 @@ where pub fn get_existence_proof( &self, key: &Key, - value: StorageBytes, + value: Vec, height: BlockHeight, ) -> Result { + use std::array; + + use crate::types::storage::MembershipProof; + if height >= self.get_block_height().0 { if let MembershipProof::ICS23(proof) = self .block .tree - .get_sub_tree_existence_proof(array::from_ref(key), vec![value]) + .get_sub_tree_existence_proof( + array::from_ref(key), + vec![&value], + ) .map_err(Error::MerkleTreeError)? { self.block .tree - .get_tendermint_proof(key, proof) + .get_sub_tree_proof(key, proof) + .map(Into::into) .map_err(Error::MerkleTreeError) } else { Err(Error::MerkleTreeError(MerkleTreeError::TendermintProof)) @@ -652,11 +659,12 @@ where if let MembershipProof::ICS23(proof) = tree .get_sub_tree_existence_proof( array::from_ref(key), - vec![value], + vec![&value], ) .map_err(Error::MerkleTreeError)? { - tree.get_tendermint_proof(key, proof) + tree.get_sub_tree_proof(key, proof) + .map(Into::into) .map_err(Error::MerkleTreeError) } else { Err(Error::MerkleTreeError( @@ -676,11 +684,17 @@ where height: BlockHeight, ) -> Result { if height >= self.last_height { - Ok(self.block.tree.get_non_existence_proof(key)?) + self.block + .tree + .get_non_existence_proof(key) + .map(Into::into) + .map_err(Error::MerkleTreeError) } else { match self.db.read_merkle_tree_stores(height)? { - Some(stores) => Ok(MerkleTree::::new(stores) - .get_non_existence_proof(key)?), + Some(stores) => MerkleTree::::new(stores) + .get_non_existence_proof(key) + .map(Into::into) + .map_err(Error::MerkleTreeError), None => Err(Error::NoMerkleTree { height }), } } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 1a44abfbcd..7c842a6136 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -4,7 +4,6 @@ pub mod collections; mod error; pub mod key; -pub mod queries; pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/core/src/types/eth_abi.rs b/core/src/types/eth_abi.rs index 6b1f25ea50..75f0dd1f25 100644 --- a/core/src/types/eth_abi.rs +++ b/core/src/types/eth_abi.rs @@ -103,6 +103,7 @@ impl Encode for AbiEncode { mod tests { use std::convert::TryInto; + use data_encoding::HEXLOWER; use ethabi::ethereum_types::U256; use super::*; @@ -112,7 +113,9 @@ mod tests { #[test] fn test_abi_encode() { let expected = "0x000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"; - let expected = hex::decode(&expected[2..]).expect("Test failed"); + let expected = HEXLOWER + .decode(&expected.as_bytes()[2..]) + .expect("Test failed"); let got = AbiEncode::encode(&[ Token::Uint(U256::from(42u64)), Token::String("test".into()), @@ -127,13 +130,15 @@ mod tests { "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8"; assert_eq!( expected, - &hex::encode({ - let mut st = Keccak::v256(); - let mut output = [0; 32]; - st.update(b"hello"); - st.finalize(&mut output); - output - }) + &HEXLOWER.encode( + &{ + let mut st = Keccak::v256(); + let mut output = [0; 32]; + st.update(b"hello"); + st.finalize(&mut output); + output + }[..] + ) ); } diff --git a/core/src/types/ethereum_events.rs b/core/src/types/ethereum_events.rs index 32aa10a7d4..f3b6af9d5a 100644 --- a/core/src/types/ethereum_events.rs +++ b/core/src/types/ethereum_events.rs @@ -1,4 +1,4 @@ -//! Types representing data intended for Anoma via Ethereum events +//! Types representing data intended for Namada via Ethereum events use std::fmt::Display; use std::str::FromStr; @@ -14,7 +14,7 @@ use crate::types::keccak::KeccakHash; use crate::types::storage::{DbKeySeg, KeySeg}; use crate::types::token::Amount; -/// Anoma native type to replace the ethabi::Uint type +/// Namada native type to replace the ethabi::Uint type #[derive( Clone, Debug, @@ -127,7 +127,7 @@ impl KeySeg for EthAddress { } } -/// An Ethereum event to be processed by the Anoma ledger +/// An Ethereum event to be processed by the Namada ledger #[derive( PartialEq, Eq, @@ -142,7 +142,7 @@ impl KeySeg for EthAddress { )] pub enum EthereumEvent { /// Event transferring batches of ether or Ethereum based ERC20 tokens - /// from Ethereum to wrapped assets on Anoma + /// from Ethereum to wrapped assets on Namada TransfersToNamada { /// Monotonically increasing nonce #[allow(dead_code)] @@ -152,7 +152,7 @@ pub enum EthereumEvent { transfers: Vec, }, /// A confirmation event that a batch of transfers have been made - /// from Anoma to Ethereum + /// from Namada to Ethereum TransfersToEthereum { /// Monotonically increasing nonce #[allow(dead_code)] @@ -209,11 +209,11 @@ impl EthereumEvent { /// SHA256 of the Borsh serialization of the [`EthereumEvent`]. pub fn hash(&self) -> Result { let bytes = self.try_to_vec()?; - Ok(Hash::sha256(&bytes)) + Ok(Hash::sha256(bytes)) } } -/// An event transferring some kind of value from Ethereum to Anoma +/// An event transferring some kind of value from Ethereum to Namada #[derive( Clone, Debug, @@ -231,11 +231,11 @@ pub struct TransferToNamada { pub amount: Amount, /// Address of the smart contract issuing the token pub asset: EthAddress, - /// The address receiving wrapped assets on Anoma + /// The address receiving wrapped assets on Namada pub receiver: Address, } -/// An event transferring some kind of value from Anoma to Ethereum +/// An event transferring some kind of value from Namada to Ethereum #[derive( Clone, Debug, @@ -340,10 +340,8 @@ pub mod tests { /// Test helpers #[cfg(any(test, feature = "testing"))] pub mod testing { - use namada_proof_of_stake::types::VotingPower; - use super::*; - use crate::types::token::Amount; + use crate::types::token::{self, Amount}; pub const DAI_ERC20_ETH_ADDRESS_CHECKSUMMED: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; @@ -374,8 +372,8 @@ pub mod testing { Amount::from(1_000) } - pub fn arbitrary_voting_power() -> VotingPower { - VotingPower::from(1_000) + pub fn arbitrary_bonded_stake() -> token::Amount { + token::Amount::from(1_000) } /// A [`EthereumEvent::TransfersToNamada`] containing a single transfer of diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 0e178adbd6..01c38de5ee 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -4,8 +4,6 @@ use std::fmt::{self, Display}; use std::ops::Deref; use std::str::FromStr; -use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; use serde::{Deserialize, Serialize}; @@ -100,7 +98,7 @@ impl TryFrom<&str> for Hash { fn try_from(string: &str) -> HashResult { let vec = HEXUPPER - .decode(string.as_ref()) + .decode(string.to_uppercase().as_ref()) .map_err(Error::FromStringError)?; Self::try_from(&vec[..]) } @@ -114,14 +112,6 @@ impl FromStr for Hash { } } -impl FromStr for Hash { - type Err = self::Error; - - fn from_str(str: &str) -> Result { - Self::try_from(str) - } -} - impl Hash { /// Compute sha256 of some bytes pub fn sha256(data: impl AsRef<[u8]>) -> Self { @@ -172,32 +162,3 @@ mod tests { } } } - -impl Value for Hash { - fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } - - fn zero() -> Self { - Hash([0u8; HASH_LENGTH]) - } -} - -impl From for H256 { - fn from(hash: Hash) -> Self { - hash.0.into() - } -} - -impl From for Hash { - fn from(hash: H256) -> Self { - Self(hash.into()) - } -} - -impl From<&H256> for Hash { - fn from(hash: &H256) -> Self { - let hash = hash.to_owned(); - Self(hash.into()) - } -} diff --git a/core/src/types/keccak.rs b/core/src/types/keccak.rs index b1e04c8691..a2ee9c0d87 100644 --- a/core/src/types/keccak.rs +++ b/core/src/types/keccak.rs @@ -5,7 +5,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use hex::FromHex; +use data_encoding::HEXUPPER; use thiserror::Error; use tiny_keccak::{Hasher, Keccak}; @@ -20,7 +20,7 @@ pub enum TryFromError { #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), #[error("Failed to convert string into a hash: {0}")] - FromStringError(hex::FromHexError), + FromStringError(data_encoding::DecodeError), } /// Represents a Keccak hash. @@ -84,8 +84,9 @@ impl TryFrom<&str> for KeccakHash { type Error = TryFromError; fn try_from(string: &str) -> Result { - let bytes: Vec = - Vec::from_hex(string).map_err(TryFromError::FromStringError)?; + let bytes: Vec = HEXUPPER + .decode(string.as_bytes()) + .map_err(TryFromError::FromStringError)?; Self::try_from(bytes.as_slice()) } } diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 3a500addb0..9b4efd6f21 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -423,6 +423,32 @@ pub mod testing { .unwrap() } + /// An Ethereum keypair for tests + pub fn keypair_3() -> ::SecretKey { + let bytes = [ + 0xf3, 0x78, 0x78, 0x80, 0xba, 0x85, 0x0b, 0xa4, 0xc5, 0x74, 0x50, + 0x5a, 0x23, 0x54, 0x6d, 0x46, 0x74, 0xa1, 0x3f, 0x09, 0x75, 0x0c, + 0xf4, 0xb5, 0xb8, 0x17, 0x69, 0x64, 0xf4, 0x08, 0xd4, 0x80, + ]; + secp256k1::SecretKey::try_from_slice(bytes.as_ref()) + .unwrap() + .try_to_sk() + .unwrap() + } + + /// An Ethereum keypair for tests + pub fn keypair_4() -> ::SecretKey { + let bytes = [ + 0x68, 0xab, 0xce, 0x64, 0x54, 0x07, 0x7e, 0xf5, 0x1a, 0xb4, 0x31, + 0x7a, 0xb8, 0x8b, 0x98, 0x30, 0x27, 0x11, 0x4e, 0x58, 0x69, 0xd6, + 0x45, 0x94, 0xdc, 0x90, 0x8d, 0x94, 0xee, 0x58, 0x46, 0x91, + ]; + secp256k1::SecretKey::try_from_slice(bytes.as_ref()) + .unwrap() + .try_to_sk() + .unwrap() + } + /// Generate an arbitrary [`super::SecretKey`]. pub fn arb_keypair() -> impl Strategy { any::<[u8; 32]>().prop_map(move |seed| { diff --git a/core/src/types/key/secp256k1.rs b/core/src/types/key/secp256k1.rs index 733690387e..ba622b9d7e 100644 --- a/core/src/types/key/secp256k1.rs +++ b/core/src/types/key/secp256k1.rs @@ -612,16 +612,16 @@ mod test { let expected_pk_hex = "a225bf565ff4ea039bccba3e26456e910cd74e4616d67ee0a166e26da6e5e55a08d0fa1659b4b547ba7139ca531f62907b9c2e72b80712f1c81ece43c33f4b8b"; let expected_eth_addr_hex = "6ea27154616a29708dce7650b475dd6b82eba6a3"; - let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); + let sk_bytes = HEXLOWER.decode(SECRET_KEY_HEX.as_bytes()).unwrap(); let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); let pk: PublicKey = sk.ref_to(); // We're removing the first byte with // `libsecp256k1::util::TAG_PUBKEY_FULL` - let pk_hex = hex::encode(&pk.0.serialize()[1..]); + let pk_hex = HEXLOWER.encode(&pk.0.serialize()[1..]); assert_eq!(expected_pk_hex, pk_hex); let eth_addr: EthAddress = (&pk).into(); - let eth_addr_hex = hex::encode(eth_addr.0); + let eth_addr_hex = HEXLOWER.encode(ð_addr.0[..]); assert_eq!(expected_eth_addr_hex, eth_addr_hex); } @@ -629,7 +629,7 @@ mod test { /// with Serde is idempotent. #[test] fn test_roundtrip_serde() { - let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); + let sk_bytes = HEXLOWER.decode(SECRET_KEY_HEX.as_bytes()).unwrap(); let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); let to_sign = "test".as_bytes(); let mut signature = SigScheme::sign(&sk, to_sign); @@ -644,7 +644,7 @@ mod test { /// with Borsh is idempotent. #[test] fn test_roundtrip_borsh() { - let sk_bytes = hex::decode(SECRET_KEY_HEX).unwrap(); + let sk_bytes = HEXLOWER.decode(SECRET_KEY_HEX.as_bytes()).unwrap(); let sk = SecretKey::try_from_slice(&sk_bytes[..]).unwrap(); let to_sign = "test".as_bytes(); let mut signature = SigScheme::sign(&sk, to_sign); diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 0550060498..ccae2b8c31 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,10 +2,14 @@ pub mod address; pub mod chain; +pub mod eth_abi; +pub mod eth_bridge_pool; +pub mod ethereum_events; pub mod governance; pub mod hash; pub mod ibc; pub mod internal; +pub mod keccak; pub mod key; pub mod masp; pub mod storage; @@ -13,3 +17,5 @@ pub mod time; pub mod token; pub mod transaction; pub mod validity_predicate; +pub mod vote_extensions; +pub mod voting_power; diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index 8dac327075..4a38eeccdc 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -6,8 +6,7 @@ use std::num::ParseIntError; use std::ops::{Add, Deref, Div, Mul, Rem, Sub}; use std::str::FromStr; -use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{InternalKey, Key as TreeKey}; +use arse_merkle_tree::InternalKey; use bit_vec::BitVec; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::BASE32HEX_NOPAD; @@ -367,35 +366,6 @@ pub enum TreeKeyError { InvalidMerkleKey(String), } -impl TreeKey for StringKey { - type Error = TreeKeyError; - - fn as_slice(&self) -> &[u8] { - &self.original.as_slice()[..self.length] - } - - fn try_from_bytes(bytes: &[u8]) -> std::result::Result { - 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 { - return Err(TreeKeyError::InvalidMerkleKey( - "Input IBC key is too large".into(), - )); - } - original[i] = *byte; - tree_key[i] = byte.wrapping_add(1); - length += 1; - } - Ok(Self { - original, - tree_key: tree_key.into(), - length, - }) - } -} - impl Deref for StringKey { type Target = InternalKey; @@ -477,16 +447,6 @@ impl From for MembershipProof { } } -impl Value for TreeBytes { - fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } - - fn zero() -> Self { - TreeBytes::zero() - } -} - impl From for MembershipProof { fn from(proof: BridgePoolProof) -> Self { Self::BridgePool(proof) @@ -788,7 +748,7 @@ impl KeySeg for Epoch { fn parse(string: String) -> Result { string .split_once('=') - .and_then(|(prefix, epoch)| (prefix == "E").then(|| epoch)) + .and_then(|(prefix, epoch)| (prefix == "E").then_some(epoch)) .ok_or_else(|| { Error::ParseKeySeg(format!( "Invalid epoch prefix on key: {string}" diff --git a/core/src/types/vote_extensions/validator_set_update.rs b/core/src/types/vote_extensions/validator_set_update.rs index 697a7aabd2..f9bb6e96f4 100644 --- a/core/src/types/vote_extensions/validator_set_update.rs +++ b/core/src/types/vote_extensions/validator_set_update.rs @@ -7,7 +7,6 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use ethabi::ethereum_types as ethereum; use num_rational::Ratio; -use crate::ledger::pos::types::VotingPower; use crate::proto::Signed; use crate::types::address::Address; use crate::types::eth_abi::{AbiEncode, Encode, Token}; @@ -17,6 +16,7 @@ use crate::types::key::common::{self, Signature}; use crate::types::storage::BlockHeight; #[allow(unused_imports)] use crate::types::storage::Epoch; +use crate::types::token; // the namespace strings plugged into validator set hashes const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge"; @@ -168,8 +168,8 @@ pub struct EthAddrBook { pub cold_key_addr: EthAddress, } -/// Provides a mapping between [`EthAddress`] and [`VotingPower`] instances. -pub type VotingPowersMap = HashMap; +/// Provides a mapping between [`EthAddress`] and [`token::Amount`] instances. +pub type VotingPowersMap = HashMap; /// This trait contains additional methods for a [`VotingPowersMap`], related /// with validator set update vote extensions logic. @@ -213,8 +213,8 @@ pub trait VotingPowersMapExt { /// Compare two items of [`VotingPowersMap`]. This comparison operation must /// match the equivalent comparison operation in Ethereum bridge code. fn compare_voting_powers_map_items( - first: &(&EthAddrBook, &VotingPower), - second: &(&EthAddrBook, &VotingPower), + first: &(&EthAddrBook, &token::Amount), + second: &(&EthAddrBook, &token::Amount), ) -> Ordering { let (first_power, second_power) = (first.1, second.1); let (first_addr, second_addr) = (first.0, second.0); @@ -367,6 +367,8 @@ pub use tag::SerializeWithAbiEncode; #[cfg(test)] mod tests { + use data_encoding::HEXLOWER; + use super::*; /// Test the keccak hash of a validator set update @@ -397,7 +399,7 @@ mod tests { vec![], ); - assert_eq!(&hex::encode(got), EXPECTED); + assert_eq!(&HEXLOWER.encode(&got[..]), EXPECTED); } /// Checks that comparing two [`VotingPowersMap`] items which have the same diff --git a/core/src/types/voting_power.rs b/core/src/types/voting_power.rs index 6cbc3895e2..186d6d6c4e 100644 --- a/core/src/types/voting_power.rs +++ b/core/src/types/voting_power.rs @@ -56,7 +56,7 @@ impl Add<&FractionalVotingPower> for FractionalVotingPower { type Output = Self; fn add(self, rhs: &FractionalVotingPower) -> Self::Output { - Self(self.0 + (*rhs).0) + Self(self.0 + rhs.0) } } @@ -68,7 +68,7 @@ impl AddAssign for FractionalVotingPower { impl AddAssign<&FractionalVotingPower> for FractionalVotingPower { fn add_assign(&mut self, rhs: &FractionalVotingPower) { - *self = Self(self.0 + (*rhs).0) + *self = Self(self.0 + rhs.0) } } diff --git a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md index 106196e7b3..d45abbc8cf 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md +++ b/documentation/specs/src/interoperability/ethereum-bridge/transfers_to_namada.md @@ -20,13 +20,13 @@ the receiver, or release the escrowed native Namada token. ```rust pub struct EthAddress(pub [u8; 20]); -/// An event transferring some kind of value from Ethereum to Anoma +/// An event transferring some kind of value from Ethereum to Namada pub struct TransferToNamada { /// Quantity of ether in the transfer pub amount: Amount, /// Address on Ethereum of the asset pub asset: EthereumAsset, - /// The Namada address receiving wrapped assets on Anoma + /// The Namada address receiving wrapped assets on Namada pub receiver: Address, } ``` diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a8e4a40f56..015bc14ea3 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -36,10 +36,10 @@ pub use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, EthAddress, GenesisValidator, - Slash, SlashType, Slashes, TotalDeltas, Unbond, Unbonds, - ValidatorConsensusKeys, ValidatorDeltas, ValidatorEthKey, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, + ActiveValidator, Bonds, CommissionRates, GenesisValidator, Slash, + SlashType, Slashes, TotalDeltas, Unbond, Unbonds, ValidatorConsensusKeys, + ValidatorDeltas, ValidatorEthKey, ValidatorSet, ValidatorSetUpdate, + ValidatorSets, ValidatorState, ValidatorStates, }; use crate::btree_set::BTreeSetShims; @@ -118,10 +118,6 @@ pub trait PosReadOnly { /// Read PoS total deltas for all validators (active and inactive) fn read_total_deltas(&self) -> Result; - /// Check if the given address is a validator by checking that it has some - /// state. - fn is_validator(&self) -> Result; - /// Read PoS validator's Eth bridge governance key fn read_validator_eth_cold_key( &self, @@ -134,6 +130,16 @@ pub trait PosReadOnly { key: &Address, ) -> Option; + /// Check if the given address is a validator by checking that it has some + /// state. + fn is_validator( + &self, + address: &Address, + ) -> Result { + let state = self.read_validator_state(address)?; + Ok(state.is_some()) + } + /// Get the total bond amount for the given bond ID at the given epoch. fn bond_amount( &self, @@ -239,6 +245,7 @@ pub trait PosActions: PosReadOnly { key: &Address, value: ValidatorConsensusKeys, ) -> Result<(), storage_api::Error>; + /// Write PoS validator's Eth bridge governance key fn write_validator_eth_cold_key( &mut self, address: &Address, @@ -316,6 +323,7 @@ pub trait PosActions: PosReadOnly { ) -> Result<(), storage_api::Error>; /// Attempt to update the given account to become a validator. + #[allow(clippy::too_many_arguments)] fn become_validator( &mut self, address: &Address, @@ -1287,6 +1295,7 @@ struct BecomeValidatorData { } /// A function that initialized data for a new validator. +#[allow(clippy::too_many_arguments)] fn become_validator( params: &PosParams, address: &Address, @@ -1333,15 +1342,15 @@ fn become_validator( params, ); - Ok(BecomeValidatorData { + BecomeValidatorData { consensus_key, - state, eth_cold_key, eth_hot_key, + state, deltas, commission_rate, max_commission_rate_change, - }) + } } struct BondData { diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 7c2c91c4cb..0df7ab4e12 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -491,7 +491,7 @@ where fn read_validator_eth_cold_key( &self, - key: &Self::Address, + key: &Address, ) -> Option { let (value, _gas) = self.read(&validator_eth_cold_key_key(key)).unwrap(); @@ -500,7 +500,7 @@ where fn read_validator_eth_hot_key( &self, - key: &Self::Address, + key: &Address, ) -> Option { let (value, _gas) = self.read(&validator_eth_hot_key_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) @@ -796,21 +796,21 @@ macro_rules! impl_pos_read_only { // TODO: return result fn read_validator_eth_cold_key( &self, - key: &Self::Address, + key: &Address, ) -> Option { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_cold_key_key(key)).unwrap().unwrap(); - Some($crate::ledger::storage::types::decode(value).unwrap()) + namada_core::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_cold_key_key(key)).unwrap().unwrap(); + Some(namada_core::ledger::storage::types::decode(value).unwrap()) } // TODO: return result fn read_validator_eth_hot_key( &self, - key: &Self::Address, + key: &Address, ) -> Option { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_hot_key_key(key)).unwrap().unwrap(); - Some($crate::ledger::storage::types::decode(value).unwrap()) + namada_core::ledger::storage_api::StorageRead::read_bytes(self, &validator_eth_hot_key_key(key)).unwrap().unwrap(); + Some(namada_core::ledger::storage::types::decode(value).unwrap()) } } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 43a72dd15c..05cf37de21 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -72,6 +72,8 @@ testing = [ "namada_proof_of_stake/testing", "async-client", "proptest", + "rand_core", + "rand", "tempfile", ] @@ -88,7 +90,7 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" eyre = "0.6.8" -num-rational = "0.4.1" +ferveo-common = {git = "https://github.com/anoma/ferveo"} # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} @@ -101,9 +103,12 @@ paste = "1.0.9" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} prost = "0.9.0" -pwasm-utils = {version = "0.18.0", optional = true} +pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} +rand = {version = "0.8", optional = true} +rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" +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 @@ -115,7 +120,6 @@ tendermint = {version = "0.23.6", optional = true} tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" -tiny-keccak = {version = "2.0.2", features = ["keccak"]} tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} wasmer-cache = {version = "=2.2.0", optional = true} diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs index a9be95f49a..a4f38bb98c 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool_vp.rs @@ -14,11 +14,11 @@ use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSerialize}; use eyre::eyre; - -use crate::ledger::eth_bridge::storage; -use crate::ledger::eth_bridge::storage::bridge_pool::{ +use namada_core::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; + +use crate::ledger::eth_bridge::storage; use crate::ledger::eth_bridge::storage::wrapped_erc20s; use crate::ledger::eth_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; @@ -393,12 +393,13 @@ mod test_bridge_pool_vp { use std::env::temp_dir; use borsh::{BorshDeserialize, BorshSerialize}; + use namada_core::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; + use namada_core::types::address; use super::*; use crate::ledger::eth_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; - use crate::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::traits::Sha256Hasher; @@ -558,6 +559,7 @@ mod test_bridge_pool_vp { let mut storage = Storage::::open( std::path::Path::new(""), ChainId::default(), + address::nam(), None, ); // a dummy config for testing diff --git a/shared/src/ledger/eth_bridge/mod.rs b/shared/src/ledger/eth_bridge/mod.rs new file mode 100644 index 0000000000..c2415ed7d7 --- /dev/null +++ b/shared/src/ledger/eth_bridge/mod.rs @@ -0,0 +1,7 @@ +//! Validity predicate and storage keys for the Ethereum bridge account +pub mod bridge_pool_vp; +pub mod parameters; +pub mod storage; +pub mod vp; + +pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/shared/src/ledger/eth_bridge/storage/mod.rs new file mode 100644 index 0000000000..a71644d9af --- /dev/null +++ b/shared/src/ledger/eth_bridge/storage/mod.rs @@ -0,0 +1,4 @@ +//! Functionality for accessing the storage subspace +pub use namada_core::ledger::eth_bridge::storage::bridge_pool; +pub mod vote_tallies; +pub use namada_core::ledger::eth_bridge::storage::{wrapped_erc20s, *}; diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/eth_bridge/vp/mod.rs index a21945ebad..f2f4456a90 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/eth_bridge/vp/mod.rs @@ -410,13 +410,14 @@ mod tests { use std::default::Default; use std::env::temp_dir; + use namada_core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; + use namada_core::types::address; use rand::Rng; use super::*; use crate::ledger::eth_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; - use crate::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::traits::Sha256Hasher; @@ -453,6 +454,7 @@ mod tests { let mut storage = Storage::::open( std::path::Path::new(""), ChainId::default(), + address::nam(), None, ); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 73f39dda05..951bf0ff4f 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -9,6 +9,7 @@ pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; pub mod queries; +pub mod queries_ext; pub mod storage; pub mod vp_host_fns; diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index fc45d3bfda..4212789aeb 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -4,10 +4,11 @@ pub mod governance; pub mod parameters; pub mod slash_fund; - use std::cell::RefCell; use std::collections::BTreeSet; +use borsh::BorshDeserialize; +use eyre::WrapErr; pub use namada_core::ledger::vp_env::VpEnv; use super::storage_api::{self, ResultExt, StorageRead}; @@ -657,6 +658,8 @@ where pub(super) mod testing { use std::collections::HashMap; + use borsh::BorshDeserialize; + use super::*; #[derive(Debug, Default)] diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 538db14b8a..0ee800ca47 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -2,7 +2,7 @@ pub mod vp; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index a62c060020..fb1bcfae1b 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -5,6 +5,7 @@ use std::panic::{RefUnwindSafe, UnwindSafe}; use borsh::BorshDeserialize; use itertools::Itertools; +use namada_core::ledger::vp_env::VpEnv; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::types::{self, Slash, Slashes, ValidatorStates}; @@ -30,6 +31,7 @@ use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_commission_rate_key, is_validator_consensus_key_key, is_validator_max_commission_rate_change_key, is_validator_state_key, + validator_eth_cold_key_key, validator_eth_hot_key_key, }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::StorageRead; diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs index 820bc66494..110a12acfa 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -292,7 +292,7 @@ mod tests { active: active_validators .into_iter() .map(|address| WeightedValidator { - voting_power: 100.into(), + bonded_stake: 100_u64, address, }) .collect(), diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/shared/src/ledger/protocol/transactions/utils.rs index 2ad56bd8b8..a5e98467f5 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/shared/src/ledger/protocol/transactions/utils.rs @@ -2,11 +2,12 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; use itertools::Itertools; +use namada_core::types::token; -use crate::ledger::pos::types::{VotingPower, WeightedValidator}; +use crate::ledger::pos::types::WeightedValidator; +use crate::ledger::queries_ext::QueriesExt; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::ledger::storage_api::queries::QueriesExt; use crate::types::address::Address; use crate::types::storage::BlockHeight; use crate::types::voting_power::FractionalVotingPower; @@ -56,7 +57,7 @@ where pub(super) fn get_active_validators( storage: &Storage, block_heights: HashSet, -) -> BTreeMap>> +) -> BTreeMap> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -75,7 +76,7 @@ where /// Gets the voting power of `selected` from `all_active`. Errors if a /// `selected` validator is not found in `all_active`. pub(super) fn get_voting_powers_for_selected( - all_active: &BTreeMap>>, + all_active: &BTreeMap>, selected: HashSet<(Address, BlockHeight)>, ) -> eyre::Result> { let total_voting_powers = sum_voting_powers_for_block_heights(all_active); @@ -99,7 +100,7 @@ pub(super) fn get_voting_powers_for_selected( for height {height}" ) })? - .voting_power; + .bonded_stake; let total_voting_power = total_voting_powers .get(&height) .ok_or_else(|| { @@ -112,7 +113,7 @@ pub(super) fn get_voting_powers_for_selected( Ok(( (addr, height), FractionalVotingPower::new( - individual_voting_power.into(), + individual_voting_power, total_voting_power.into(), )?, )) @@ -123,8 +124,8 @@ pub(super) fn get_voting_powers_for_selected( } pub(super) fn sum_voting_powers_for_block_heights( - validators: &BTreeMap>>, -) -> BTreeMap { + validators: &BTreeMap>, +) -> BTreeMap { validators .iter() .map(|(h, vs)| (h.to_owned(), sum_voting_powers(vs))) @@ -132,11 +133,11 @@ pub(super) fn sum_voting_powers_for_block_heights( } pub(super) fn sum_voting_powers( - validators: &BTreeSet>, -) -> VotingPower { + validators: &BTreeSet, +) -> token::Amount { validators .iter() - .map(|validator| u64::from(validator.voting_power)) + .map(|validator| validator.bonded_stake) .sum::() .into() } @@ -149,16 +150,16 @@ mod tests { use super::*; use crate::types::address; - use crate::types::ethereum_events::testing::arbitrary_voting_power; + use crate::types::ethereum_events::testing::arbitrary_bonded_stake; #[test] /// Test getting the voting power for the sole active validator from the set /// of active validators fn test_get_voting_powers_for_selected_sole_validator() { let sole_validator = address::testing::established_address_1(); - let voting_power = arbitrary_voting_power(); + let bonded_stake = arbitrary_bonded_stake(); let weighted_sole_validator = WeightedValidator { - voting_power, + bonded_stake: bonded_stake.into(), address: sole_validator.clone(), }; let validators = HashSet::from_iter(vec![( @@ -190,9 +191,9 @@ mod tests { fn test_get_voting_powers_for_selected_missing_validator() { let present_validator = address::testing::established_address_1(); let missing_validator = address::testing::established_address_2(); - let voting_power = arbitrary_voting_power(); + let bonded_stake = arbitrary_bonded_stake(); let weighted_present_validator = WeightedValidator { - voting_power, + bonded_stake: bonded_stake.into(), address: present_validator.clone(), }; let validators = HashSet::from_iter(vec![ @@ -231,14 +232,14 @@ mod tests { fn test_get_voting_powers_for_selected_two_validators() { let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); - let voting_power_1 = VotingPower::from(100); - let voting_power_2 = VotingPower::from(200); + let bonded_stake_1 = token::Amount::from(100); + let bonded_stake_2 = token::Amount::from(200); let weighted_validator_1 = WeightedValidator { - voting_power: voting_power_1, + bonded_stake: bonded_stake_1.into(), address: validator_1.clone(), }; let weighted_validator_2 = WeightedValidator { - voting_power: voting_power_2, + bonded_stake: bonded_stake_2.into(), address: validator_2.clone(), }; let validators = HashSet::from_iter(vec![ @@ -276,16 +277,16 @@ mod tests { /// one validator fn test_sum_voting_powers_sole_validator() { let sole_validator = address::testing::established_address_1(); - let voting_power = arbitrary_voting_power(); + let bonded_stake = arbitrary_bonded_stake(); let weighted_sole_validator = WeightedValidator { - voting_power, + bonded_stake: bonded_stake.into(), address: sole_validator, }; let validators = BTreeSet::from_iter(vec![weighted_sole_validator]); let total = sum_voting_powers(&validators); - assert_eq!(total, voting_power); + assert_eq!(total, bonded_stake); } #[test] @@ -294,14 +295,14 @@ mod tests { fn test_sum_voting_powers_two_validators() { let validator_1 = address::testing::established_address_1(); let validator_2 = address::testing::established_address_2(); - let voting_power_1 = VotingPower::from(100); - let voting_power_2 = VotingPower::from(200); + let bonded_stake_1 = token::Amount::from(100); + let bonded_stake_2 = token::Amount::from(200); let weighted_validator_1 = WeightedValidator { - voting_power: voting_power_1, + bonded_stake: bonded_stake_1.into(), address: validator_1, }; let weighted_validator_2 = WeightedValidator { - voting_power: voting_power_2, + bonded_stake: bonded_stake_2.into(), address: validator_2, }; let validators = BTreeSet::from_iter(vec![ @@ -311,6 +312,6 @@ mod tests { let total = sum_voting_powers(&validators); - assert_eq!(total, VotingPower::from(300)); + assert_eq!(total, token::Amount::from(300)); } } diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs index 7fbc82f896..e2b93a9e50 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -8,9 +8,9 @@ use super::ChangedKeys; use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; use crate::ledger::protocol::transactions::votes::{self, Votes}; +use crate::ledger::queries_ext::QueriesExt; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::ledger::storage_api::queries::QueriesExt; use crate::types::address::Address; use crate::types::storage::BlockHeight; #[allow(unused_imports)] diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 0e8f6193e6..6132f9641b 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -2,32 +2,29 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; +use namada_core::ledger::eth_bridge::storage::bridge_pool::{ + get_key_from_hash, get_signed_root_key, +}; +use namada_core::ledger::storage::merkle_tree::StoreRef; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::BlockResults; -use crate::ledger::eth_bridge::storage::bridge_pool::{ - get_key_from_hash, get_signed_root_key, -}; use crate::ledger::events::log::dumb_queries; use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, MerkleTree, StoreRef, StoreType, DB}; +use crate::ledger::storage::{DBIter, MerkleTree, StoreType, DB}; use crate::ledger::storage_api::{self, CustomError, ResultExt, StorageRead}; use crate::tendermint::merkle::proof::Proof; -use crate::types::address::Address; use crate::types::eth_abi::EncodeCell; use crate::types::eth_bridge_pool::{ MultiSignedMerkleRoot, PendingTransfer, RelayProof, }; -use crate::types::hash::Hash; use crate::types::keccak::KeccakHash; use crate::types::storage::MembershipProof::BridgePool; -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] -use crate::types::storage::TxIndex; -use crate::types::storage::{self, BlockResults, Epoch, PrefixValue}; +use crate::types::storage::{self, Epoch, PrefixValue}; #[cfg(any(test, feature = "async-client"))] use crate::types::transaction::TxResult; @@ -73,9 +70,11 @@ router! {SHELL, ( "eth_bridge_pool" / "contents" ) -> Vec = read_ethereum_bridge_pool, - // Generate a merkle proof for the inclusion of requested transfers in the Ethereum bridge pool + // Generate a merkle proof for the inclusion of requested + // transfers in the Ethereum bridge pool ( "eth_bridge_pool" / "proof" ) -> EncodeCell = (with_options generate_bridge_pool_proof), + } // Handlers: @@ -94,7 +93,6 @@ where use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; use crate::types::storage::TxIndex; - use crate::types::transaction::{DecryptedTx, TxType}; let mut gas_meter = BlockGasMeter::default(); let mut write_log = WriteLog::default(); @@ -227,7 +225,11 @@ where let proof = if request.prove { let proof = ctx .storage - .get_existence_proof(&storage_key, &value, request.height) + .get_existence_proof( + &storage_key, + value.clone(), + request.height, + ) .into_storage_result()?; Some(proof) } else { @@ -282,7 +284,7 @@ where for PrefixValue { key, value } in &data { let mut proof: Proof = ctx .storage - .get_existence_proof(key, value, request.height) + .get_existence_proof(key, value.clone(), request.height) .into_storage_result()?; ops.append(&mut proof.ops); } @@ -485,10 +487,10 @@ mod test { use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSerialize}; - - use crate::ledger::eth_bridge::storage::bridge_pool::{ + use namada_core::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, get_signed_root_key, BridgePoolTree, }; + use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::RPC; use crate::ledger::storage_api::{self, StorageWrite}; diff --git a/shared/src/ledger/queries_ext.rs b/shared/src/ledger/queries_ext.rs index 98a725f984..5e4d53514c 100644 --- a/shared/src/ledger/queries_ext.rs +++ b/shared/src/ledger/queries_ext.rs @@ -2,19 +2,24 @@ use std::collections::BTreeSet; +use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada_core::ledger::storage::{self, Storage}; +use namada_core::ledger::storage_api; +use namada_core::types::key::dkg_session_keys::DkgPublicKey; +use namada_core::types::token; +use namada_proof_of_stake::PosBase; use thiserror::Error; use crate::ledger::parameters::EpochDuration; -use crate::ledger::pos::namada_proof_of_stake::types::VotingPower; use crate::ledger::pos::types::WeightedValidator; use crate::ledger::pos::PosParams; +use crate::tendermint_proto::google::protobuf; use crate::tendermint_proto::types::EvidenceParams; use crate::types::address::Address; use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::storage::{BlockHeight, Epoch}; -use crate::types::token::Amount; use crate::types::transaction::EllipticCurve; use crate::types::vote_extensions::validator_set_update::EthAddrBook; @@ -24,7 +29,7 @@ pub enum Error { /// The given address is not among the set of active validators for /// the corresponding epoch. #[error( - "The address '{:?}' is not among the active validator set for epoch \ + "The address '{0:?}' is not among the active validator set for epoch \ {1}" )] NotValidatorAddress(Address, Epoch), @@ -69,11 +74,11 @@ pub trait QueriesExt { // avoid a heap allocation; `F` will be the closure used to process the // iterator we currently return in the `Storage` impl // ```ignore - // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, VotingPower)>; + // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, token::Amount)>; // ``` // a similar strategy can be used for [`QueriesExt::get_active_validators`]: // ```ignore - // type ActiveValidatorsIter<'db, F>: Iterator>; + // type ActiveValidatorsIter<'db, F>: Iterator; // ``` /// Get the set of active validators for a given epoch (defaulting to the @@ -81,15 +86,15 @@ pub trait QueriesExt { fn get_active_validators( &self, epoch: Option, - ) -> BTreeSet>; + ) -> BTreeSet; /// Lookup the total voting power for an epoch (defaulting to the /// epoch of the current yet-to-be-committed block). - fn get_total_voting_power(&self, epoch: Option) -> VotingPower; + fn get_total_voting_power(&self, epoch: Option) -> token::Amount; /// Simple helper function for the ledger to get balances /// of the specified token at the specified address. - fn get_balance(&self, token: &Address, owner: &Address) -> Amount; + fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount; /// Return evidence parameters. // TODO: impove this docstring @@ -111,7 +116,7 @@ pub trait QueriesExt { &self, address: &Address, epoch: Option, - ) -> Result<(VotingPower, key::common::PublicKey)>; + ) -> Result<(token::Amount, key::common::PublicKey)>; /// Given a tendermint validator, the address is the hash /// of the validators public key. We look up the native @@ -157,5 +162,266 @@ pub trait QueriesExt { fn get_active_eth_addresses<'db>( &'db self, epoch: Option, - ) -> Box + 'db>; + ) -> Box + 'db>; +} + +impl QueriesExt for Storage +where + D: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, +{ + fn get_active_validators( + &self, + epoch: Option, + ) -> BTreeSet { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_set = self.read_validator_set(); + validator_set + .get(epoch) + .expect("Validators for an epoch should be known") + .active + .clone() + } + + fn get_total_voting_power(&self, epoch: Option) -> token::Amount { + self.get_active_validators(epoch) + .iter() + .map(|validator| validator.bonded_stake) + .sum::() + .into() + } + + fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount { + let balance = storage_api::StorageRead::read( + self, + &token::balance_key(token, owner), + ); + // Storage read must not fail, but there might be no value, in which + // case default (0) is returned + balance + .expect("Storage read in the protocol must not fail") + .unwrap_or_default() + } + + 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 = + std::cmp::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() + } + } + + fn get_validator_from_protocol_pk( + &self, + pk: &key::common::PublicKey, + epoch: Option, + ) -> Result> { + let pk_bytes = pk + .try_to_vec() + .expect("Serializing public key should not fail"); + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) + .iter() + .find(|validator| { + let pk_key = key::protocol_pk_key(&validator.address); + match self.read(&pk_key) { + Ok((Some(bytes), _)) => bytes == pk_bytes, + _ => false, + } + }) + .map(|validator| { + let dkg_key = + key::dkg_session_keys::dkg_pk_key(&validator.address); + let bytes = self + .read(&dkg_key) + .expect("Validator should have public dkg key") + .0 + .expect("Validator should have public dkg key"); + let dkg_publickey = + &::deserialize( + &mut bytes.as_ref(), + ) + .expect( + "DKG public key in storage should be deserializable", + ); + TendermintValidator { + power: validator.bonded_stake, + address: validator.address.to_string(), + public_key: dkg_publickey.into(), + } + }) + .ok_or_else(|| Error::NotValidatorKey(pk.to_string(), epoch)) + } + + fn get_validator_from_address( + &self, + address: &Address, + epoch: Option, + ) -> Result<(token::Amount, key::common::PublicKey)> { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.get_active_validators(Some(epoch)) + .iter() + .find(|validator| address == &validator.address) + .map(|validator| { + let protocol_pk_key = key::protocol_pk_key(&validator.address); + let bytes = self + .read(&protocol_pk_key) + .expect("Validator should have public protocol key") + .0 + .expect("Validator should have public protocol key"); + let protocol_pk: key::common::PublicKey = + BorshDeserialize::deserialize(&mut bytes.as_ref()).expect( + "Protocol public key in storage should be \ + deserializable", + ); + (validator.bonded_stake.into(), protocol_pk) + }) + .ok_or_else(|| Error::NotValidatorAddress(address.clone(), epoch)) + } + + fn get_validator_from_tm_address( + &self, + tm_address: &[u8], + epoch: Option, + ) -> Result
{ + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + let validator_raw_hash = core::str::from_utf8(tm_address) + .map_err(|_| Error::InvalidTMAddress)?; + self.read_validator_address_raw_hash(validator_raw_hash) + .ok_or_else(|| { + Error::NotValidatorKeyHash( + validator_raw_hash.to_string(), + epoch, + ) + }) + } + + #[cfg(feature = "abcipp")] + fn can_send_validator_set_update(&self, _can_send: SendValsetUpd) -> bool { + // TODO: implement this method for ABCI++; should only be able to send + // a validator set update at the second block of an epoch + true + } + + #[cfg(not(feature = "abcipp"))] + fn can_send_validator_set_update(&self, can_send: SendValsetUpd) -> bool { + // when checking vote extensions in Prepare + // and ProcessProposal, we simply return true + if matches!(can_send, SendValsetUpd::AtPrevHeight) { + return true; + } + + let current_decision_height = self.get_current_decision_height(); + + // NOTE: the first stored height in `fst_block_heights_of_each_epoch` + // is 0, because of a bug (should be 1), so this code needs to + // handle that case + // + // we can remove this check once that's fixed + match current_decision_height { + BlockHeight(1) => return false, + BlockHeight(2) => return true, + _ => (), + } + + let fst_heights_of_each_epoch = + self.block.pred_epochs.first_block_heights(); + + fst_heights_of_each_epoch + .last() + .map(|&h| { + let second_height_of_epoch = h + 1; + current_decision_height == second_height_of_epoch + }) + .unwrap_or(false) + } + + #[inline] + fn get_epoch(&self, height: BlockHeight) -> Option { + self.block.pred_epochs.get_epoch(height) + } + + #[inline] + fn get_current_decision_height(&self) -> BlockHeight { + self.last_height + 1 + } + + #[inline] + fn get_ethbridge_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.read_validator_eth_hot_key(validator) + .as_ref() + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) + } + + #[inline] + fn get_ethgov_from_namada_addr( + &self, + validator: &Address, + epoch: Option, + ) -> Option { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + self.read_validator_eth_cold_key(validator) + .as_ref() + .and_then(|epk| epk.get(epoch).and_then(|pk| pk.try_into().ok())) + } + + #[inline] + fn get_active_eth_addresses<'db>( + &'db self, + epoch: Option, + ) -> Box + 'db> + { + let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); + Box::new(self.get_active_validators(Some(epoch)).into_iter().map( + move |validator| { + let hot_key_addr = self + .get_ethbridge_from_namada_addr( + &validator.address, + Some(epoch), + ) + .expect( + "All Namada validators should have an Ethereum bridge \ + key", + ); + let cold_key_addr = self + .get_ethgov_from_namada_addr( + &validator.address, + Some(epoch), + ) + .expect( + "All Namada validators should have an Ethereum \ + governance key", + ); + let eth_addr_book = EthAddrBook { + hot_key_addr, + cold_key_addr, + }; + ( + eth_addr_book, + validator.address, + validator.bonded_stake.into(), + ) + }, + )) + } } diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 8324e665ed..1832e51ce9 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -1,15 +1,10 @@ //! Types definitions. -pub mod eth_abi; -pub mod eth_bridge_pool; -pub mod ethereum_events; pub mod ibc; -pub mod keccak; pub mod key; -pub mod vote_extensions; -pub mod voting_power; pub use namada_core::types::{ - address, chain, governance, hash, internal, masp, storage, time, token, - transaction, validity_predicate, + address, chain, eth_abi, eth_bridge_pool, ethereum_events, governance, + hash, internal, keccak, masp, storage, time, token, transaction, + validity_predicate, vote_extensions, voting_power, }; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 4861343186..8ded03ead8 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -118,7 +118,7 @@ fn run_ledger_with_ethereum_events_endpoint() -> Result<()> { ledger.exp_string( "Starting to listen for Borsh-serialized Ethereum events", )?; - ledger.exp_string("Anoma ledger node started")?; + ledger.exp_string("Namada ledger node started")?; ledger.send_control('c')?; ledger.exp_string( diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index f65595dbe3..e8e1f9b5c9 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -3,11 +3,11 @@ mod test_bridge_pool_vp { use std::path::PathBuf; use borsh::{BorshDeserialize, BorshSerialize}; + use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada::ledger::eth_bridge::bridge_pool_vp::BridgePoolVp; use namada::ledger::eth_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; - use namada::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada::ledger::eth_bridge::storage::wrapped_erc20s; use namada::ledger::eth_bridge::ADDRESS; use namada::proto::Tx; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index f3432e3c56..1eb71f720f 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -37,7 +37,7 @@ pub use namada_core::types::storage::{ self, BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, }; use namada_core::types::time::Rfc3339String; -pub use namada_core::types::*; +pub use namada_core::types::{eth_bridge_pool, *}; pub use namada_macros::transaction; use namada_vm_env::tx::*; use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 4bb00df750..43c899220b 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -165,17 +165,17 @@ impl namada_proof_of_stake::PosActions for Ctx { fn write_validator_eth_cold_key( &mut self, - address: &Self::Address, - value: types::ValidatorEthKey, - ) -> Result<(), Self::Error> { + address: &Address, + value: types::ValidatorEthKey, + ) -> Result<(), storage_api::Error> { self.write(&validator_eth_cold_key_key(address), &value) } fn write_validator_eth_hot_key( &mut self, - address: &Self::Address, - value: types::ValidatorEthKey, - ) -> Result<(), Self::Error> { + address: &Address, + value: types::ValidatorEthKey, + ) -> Result<(), storage_api::Error> { self.write(&validator_eth_hot_key_key(address), &value) } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 7aebbc65ea..ff6b383781 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -287,7 +287,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43473b34abc4b0b405efa0a250bac87eea888182b21687ee5c8115d279b0fda5" dependencies = [ - "bitvec", + "bitvec 0.22.3", "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.6", @@ -374,10 +374,22 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" dependencies = [ - "funty", - "radium", + "funty 1.2.0", + "radium 0.6.2", "tap", - "wyz", + "wyz 0.4.0", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty 2.0.0", + "radium 0.7.0", + "tap", + "wyz 0.5.1", ] [[package]] @@ -502,7 +514,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate", + "proc-macro-crate 0.1.5", "proc-macro2", "syn", ] @@ -533,6 +545,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytecheck" version = "0.6.9" @@ -1334,6 +1352,50 @@ dependencies = [ "version_check", ] +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3 0.10.6", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + [[package]] name = "eyre" version = "0.6.8" @@ -1378,11 +1440,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "bitvec", + "bitvec 0.22.3", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1434,6 +1508,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.25" @@ -2012,7 +2092,7 @@ dependencies = [ "prost", "ripemd160", "sha2 0.9.9", - "sha3", + "sha3 0.9.1", "sp-std", ] @@ -2032,6 +2112,44 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "incrementalmerkletree" version = "0.2.0" @@ -2106,7 +2224,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" dependencies = [ - "bitvec", + "bitvec 0.22.3", "bls12_381", "ff", "group", @@ -2276,7 +2394,7 @@ source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb17 dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", @@ -2458,6 +2576,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", + "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -2466,21 +2585,22 @@ dependencies = [ "masp_proofs", "namada_core", "namada_proof_of_stake", - "num-rational", - "parity-wasm 0.45.0", + "parity-wasm", "paste", "proptest", "prost", "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.4", "rayon", "rust_decimal", + "serde", "serde_json", "sha2 0.9.9", "tempfile", "tendermint", "tendermint-proto", "thiserror", - "tiny-keccak", "tracing", "wasmer", "wasmer-cache", @@ -2506,6 +2626,8 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "ethabi", + "eyre", "ferveo-common", "ibc", "ibc-proto", @@ -2513,6 +2635,7 @@ dependencies = [ "itertools", "libsecp256k1", "masp_primitives", + "num-rational", "proptest", "prost", "prost-types", @@ -2528,6 +2651,7 @@ dependencies = [ "tendermint", "tendermint-proto", "thiserror", + "tiny-keccak", "tonic-build", "tracing", "zeroize", @@ -2771,7 +2895,7 @@ dependencies = [ "aes", "arrayvec 0.7.2", "bigint", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "ff", "fpe", @@ -2799,10 +2923,30 @@ dependencies = [ ] [[package]] -name = "parity-wasm" -version = "0.42.2" +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec 1.0.1", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] [[package]] name = "parity-wasm" @@ -2997,6 +3141,19 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -3006,6 +3163,17 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3144,13 +3312,12 @@ dependencies = [ [[package]] name = "pwasm-utils" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880b3384fb00b8f6ecccd5d358b93bd2201900ae3daad213791d1864f6441f5c" +version = "0.20.0" +source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" dependencies = [ "byteorder", "log", - "parity-wasm 0.42.2", + "parity-wasm", ] [[package]] @@ -3196,6 +3363,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3482,6 +3655,16 @@ dependencies = [ "syn", ] +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rust_decimal" version = "1.26.1" @@ -3516,6 +3699,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.3.3" @@ -3880,6 +4069,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -5308,6 +5507,15 @@ dependencies = [ "tap", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zcash_encoding" version = "0.0.0" @@ -5347,7 +5555,7 @@ source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b dependencies = [ "aes", "bip0039", - "bitvec", + "bitvec 0.22.3", "blake2b_simd 1.0.0", "blake2s_simd 1.0.0", "bls12_381", diff --git a/wasm/checksums.json b/wasm/checksums.json index b1b9eae975..769ec86ac6 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,21 @@ { - "tx_bond.wasm": "tx_bond.4fc2b1c226d57d94e4043109d1af164ac69f8eb72e12525d97a123d8100817af.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.f41d51ed01d5c60909c8ff9a7ed31f19b2d3ef42c24d1daa4143d59e0e797d97.wasm", - "tx_ibc.wasm": "tx_ibc.582a0a6433782caaee708205fc22f40d2af34fcd6994ac8f5fb8bef627778b4d.wasm", - "tx_init_account.wasm": "tx_init_account.0c204ba845658516b20670346c0682595d16d1ca99f42eddde51d1cde4295ffb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0a9cbdd68510d723818bb5d95cb0770b4f054ca402f8bbcff18108ea413875de.wasm", - "tx_init_validator.wasm": "tx_init_validator.09d0e561565cb083cb243b4594882c4f424eda73a91f89cce1e814b35ba2eae5.wasm", - "tx_transfer.wasm": "tx_transfer.9f4ad0aed0f1fcf21398c4d12da4ae26937415b01524811c0dceb810f1d1bf4a.wasm", - "tx_unbond.wasm": "tx_unbond.72018d106cca5a856bca041ea30eee579ec61d29f246f1387f3da4ef72c6cfbd.wasm", - "tx_update_vp.wasm": "tx_update_vp.4d585d52ed7f3b1507ed092df82ff7edec362305eb9e84f4dae06f09d75cc727.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.49638241211faf327390a0f07920d8dd40c9d7676d9c1beccd3bad0ff5f6f1a9.wasm", - "tx_withdraw.wasm": "tx_withdraw.1f8faa002868664e42d6e4b7140d5f295a2a0f1e99968656ee4d91c496b8f08d.wasm", - "vp_masp.wasm": "vp_masp.ad12a384f8690ad1a7c084b0a2ce7e72b9743fac7b2229f9a0e290fd0e75619a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e7d6cc83ad23f7db10484a051e6ba25105900b154e179768ccc79b955d6f47ca.wasm", - "vp_token.wasm": "vp_token.fbec5c10439c0f2d421799188f9ab677967b931535262dbf4cc7591ea0978aa7.wasm", - "vp_user.wasm": "vp_user.4db5100a828f04ed1fe6d2fee09b56facd8dde1e3bc0ea8d288875bf934d581a.wasm" -} + "tx_bond.wasm": "tx_bond.4bb7bd05d6fbe0935eb153c07fad6ec542960fa6d34ddf189f3baf3aec2c2366.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.4f9be60f5704afa8efb1630bdbcafc98cfe473900fa88c0e712ffd13e4111e6c.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.07762dea8bebaee2417959a4cc12767701d012de2a2898455338a3185aba2034.wasm", + "tx_ibc.wasm": "tx_ibc.954c1ca77574b115086b7740378e89ca297c32284076f493646a593235e5b9db.wasm", + "tx_init_account.wasm": "tx_init_account.530f7ec309ecd67e99ecfd6e849eac2f0f894d2bec98cb8a88baa6ab8dc67da2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.da3ae4f65ef30915bf7c65840b35a8f8f3235fc525325587f3cf6eb6aa2eb540.wasm", + "tx_init_validator.wasm": "tx_init_validator.5335101ec592f111d5db21ccc05480a7344e29443d48d4578b52e71fd99e9791.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.f3d96ed54068d05aa0aa2890af08af5d9b066f02faa7a80439befdcdc12453d3.wasm", + "tx_transfer.wasm": "tx_transfer.3ab1dcf30f1e997afcb6c3a7b718ad2952d3fe5728c944a1484497ef3a676e4a.wasm", + "tx_unbond.wasm": "tx_unbond.470701b74900435653002d7e096319e4fe02639ec1a7c07fa24dca1c7c4a7a99.wasm", + "tx_update_vp.wasm": "tx_update_vp.4a0a31c29279e178990155e6a49b2c9e87ccc59ab4d31b8a4788c7e77d79f920.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.8f7825c909143569c5831dfe32e732780e37ef1a3c7f58132c700979a4504159.wasm", + "tx_withdraw.wasm": "tx_withdraw.fd59c1e294f588733a5cf57ba576b29fca884598eecec7214dadfa5ed5c4062f.wasm", + "vp_implicit.wasm": "vp_implicit.623ad4ffd91c80926ff81119da82778422840603a3a881b3c8348ab7ec9aa9c9.wasm", + "vp_masp.wasm": "vp_masp.bcf062352d54910897c1428fee8a65c6e75c967d780938b7d2dcab9e073362fa.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.56bba9bc553a764bb7dffa4e68a6f6f2c0c2d26a63cf077f05792749724c5e96.wasm", + "vp_token.wasm": "vp_token.69262edcba9a08e79563dfc454305c76ca6173964e16838effcfe833dbbfc1b1.wasm", + "vp_user.wasm": "vp_user.fb98a5dc4bc2b6c0f24f7e156a076de589e6ea0c734624f398c6ea5de317c7eb.wasm", + "vp_validator.wasm": "vp_validator.721d971b910694a739e5d669263db9145c98708057e1bed652c4069d162bbd90.wasm" +} \ No newline at end of file diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index f32d83f34e..d390d0189f 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -65,10 +65,14 @@ mod tests { pos_params: PosParams, ) -> TxResult { let consensus_key = key::testing::keypair_1().ref_to(); + let eth_cold_key = key::testing::keypair_3().ref_to(); + let eth_hot_key = key::testing::keypair_4().ref_to(); let genesis_validators = [GenesisValidator { address: commission_change.validator.clone(), tokens: token::Amount::from(1_000_000), consensus_key, + eth_cold_key, + eth_hot_key, commission_rate: initial_rate, max_commission_rate_change: max_change, }]; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 1322adea3c..deee724f50 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -324,8 +324,8 @@ mod tests { Ok(()) } - fn arb_initial_stake_and_unbond( - ) -> impl Strategy { + fn arb_initial_stake_and_unbond() + -> impl Strategy { // Generate initial stake token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( |initial_stake| { diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index f40653e3c7617d7aae1dadbb427441c9d5310e44..07ad663d1ec731a3a2095ca7080399cf2c4f4ed3 100755 GIT binary patch delta 1296 zcmah|T}&KR6u#%q&(1RYGd2Rdiwk#Wp}XutStu+l1(wTi3rHIhj1QKO`e0f7X|YYJ zSf-{JeKNp_G>u7(5&VzZ45^7RDL&blYK$>zqKO){sA)`n@Ie!!XFwBqavr`j-?{hP zdw#yVx?;SvVk~t>G{^hcG+$U?)8N3uG-K1M<1F73Ri>4N<&%j)zcM_ea6f< zD_Sdq;e0d@xg%Ad$u>0h6np#n2g;szkHY}x&Y1g#vJRe|)V>09acz^yPhZ$%mc%aK z1H1T!ufOu<_L8jxnc#v;qa^6Z>cfzc(|6Wzz^H|4cgC39K6TT-4A6!XrcHR-e3P&) z&`&rMIK~&(+z$gqAd8_;5^QVIw75c-&+!?`%@D(y{nboL! zv{3OA?89Vb5t`h4D?bLvVQcglgngB7|F+@#;L! zQSLM#ul5wp%Omo#7f#%r$i@Luc%tE9!tWctgOq!%aRVTZXPZPQPWh=)$XlzFY#d)} zx&+NQk~?5FhfV6;>xK#rKAjuL*+xPaK6#Fp8W@vTov|QA8uD>ovULH6f?NQRSz!zZ zRiO!erMV*EH8!D376593prmN`ztP}r!uICNl|GUe%-TwV3!k^zd(b;t!9Sb(J+=0= zNZwrMwUKUMLKmu)^OCmg9Upn%ADO7p!5}D6mB)yflcaq+Yv}XZd>?lAre$(*~&G|^- zLBgwrSkxOX|L>^%q-DwS9DgjFhcw>b86$kMa|+V#jm~#@C@nv`w8^B8c9~Wh-|GH= zT##FovnsD#Yg=FVb1{ZX#YwvKMse={C3dEFO$$0>)GZjDn+q~# z;>AN}+{xiCj@hBXotN&7hF<>s&?CF}Uqg5KRl3hM-w9)QY7hQ;G;%&M^>$(ketY$A$#^$i delta 1384 zcmah}U1(fI6rMBp?%lh&`*Q=$@9s9UcYl)XW;dH{vPm{+l4;Ux{@8*TeQF4WVzcpw z#1s^4Hn9=$Ey+MJKGcUaZIP<9x3o|NqZAaapd~7T;6p8?P)by!6kj~EK`>9=hwsdp zIWzN}bH2Hk*7bAi`s(3!y0}QD9VPjZX(++sG$GTfbfGk@ES8NX-SWs;9<0rUj_OK& zSfM(A3I=tlDiCl}jSzqeP%5a$>!b<*1>yu;0*Axp0{}&(i1Jf3CpgxAnrX5^wduC5 zZeOma7dq<>R8?mVd3^0P?vDCsV^ecHab$3)Z-7^<+NWTMQZizHq-=q-la61&*xB4B z{1JlLFM6FX?eniY2WwU?$R$hh5Jnm0dXeG4>=Rx+t?jLeAw2>Cdq$rupStZ@1IUVJ z3=8qJ@iAi5J%~8tK0$Xj?Qh-vfL3_DVPGQT-BRv*3{mMnARc>7gOMg`S_+5!oG^TO zN($mx-wiaj+e~WG?WukUEh1Xe4@vuE&36DPk*zxcDSM-?LRCfq#O2_jDh9}MxL%TF zN-;}yP7GoH9{dW#Ck;vQu;GmTHv0yu%IOp4;I1OZ@kXZ1nnCe`br%kbT=*2?X1IhH zj;z2z`*NhI%>8ckDFnow*ozPp-Ho#>7$R8Z-)>w0Xb|1ayRa_e z@s@;3CQ2+#g3i^sqv`-drzNe3=$4 zCxcNBWq{5s)AAk_Svf_jtvV!29MU*(1J;CTMH&9@b|^=kY`a>eptxW*gx!*B10(We zDw3H&snq!lr)^dSZLP~NtH(G#QlzqjMz@&!ZBDf@5WGDp0m98AB=0VphsG8@yUycWj z5W$)+K{0}HKFoKYe*WW$r`=~ECeGyQ5#P;CLCk)V`;>ZP{Kxa#ggfyT!;Fc~4}XnQ zTkqqP>iJw%}m|QOu=6t`~wN)O3nZP diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c043a68c426df5b22ac83f304e90d949ca1a7047..7c12ffa83eaf7fa275861132d5a685f21af3e13f 100755 GIT binary patch delta 121410 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 }!U=#iqJtXc-Ft3}9Aw*eWh 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 Jl|9!z{|_HO-HiYM literal 24984 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 + +
DECRYPTED
DECRYPT...
E
E
E
E
E
E
E
E
E
E
E
E
E
E
bin.try_dump(tx)
bin.try_dump(tx)
PROTOCOL
PROTOC...
ENCRYPTED
ENCRYPT...
Set M of mempool transactions
Set P of proposed transactions
BlockSpaceAllocator
BlockSpaceAllocator
Viewer does not support full SVG 1.1
diff --git a/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg b/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg new file mode 100644 index 0000000000..b19ad90ce7 --- /dev/null +++ b/documentation/specs/src/base-ledger/images/block-space-allocator-example.svg @@ -0,0 +1,4 @@ + + + +
Height
Height
H
H
D
D
P
P
E
E
H+1
H+1
D
D
P
P
H+2
H+2
P
P
H+3
H+3
P
P
E
E
P
P
H+4
H+4
E
E
P
P
D
D
Block space
Block space
Viewer does not support full SVG 1.1
\ No newline at end of file From 9b04f5a7f37e30a14509873c4a8b29f4f417e93d Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 19 Dec 2022 11:26:59 +0100 Subject: [PATCH 1970/1995] [feat]: Refactored ethereum bridge out of shared and into its own crate --- Cargo.lock | 58 ++++- Cargo.toml | 1 + apps/Cargo.toml | 4 +- apps/src/lib/config/genesis.rs | 2 +- .../lib/node/ledger/shell/prepare_proposal.rs | 6 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shell/queries.rs | 2 +- .../lib/node/ledger/shell/vote_extensions.rs | 2 +- .../shell/vote_extensions/eth_events.rs | 4 +- .../shell/vote_extensions/val_set_update.rs | 4 +- core/Cargo.toml | 6 +- ethereum_bridge/Cargo.toml | 49 ++++ ethereum_bridge/src/ledger/bridge_pool_vp.rs | 28 +++ .../src/ledger}/mod.rs | 3 +- .../src/ledger}/parameters.rs | 16 +- ethereum_bridge/src/ledger/protocol/mod.rs | 1 + .../transactions/ethereum_events/eth_msgs.rs | 13 +- .../transactions/ethereum_events/events.rs | 22 +- .../transactions/ethereum_events/mod.rs | 49 ++-- .../src/ledger/protocol/transactions/mod.rs | 20 +- .../src/ledger/protocol/transactions/read.rs | 15 +- .../ledger/protocol/transactions/update.rs | 13 +- .../src/ledger/protocol/transactions/utils.rs | 18 +- .../transactions/validator_set_update/mod.rs | 23 +- .../src/ledger/protocol/transactions/votes.rs | 14 +- .../protocol/transactions/votes/storage.rs | 15 +- .../protocol/transactions/votes/update.rs | 18 +- .../src/ledger}/storage/mod.rs | 0 .../src/ledger}/storage/vote_tallies.rs | 14 +- ethereum_bridge/src/ledger/vp.rs | 28 +++ ethereum_bridge/src/lib.rs | 16 ++ proof_of_stake/Cargo.toml | 10 + proof_of_stake/src/lib.rs | 5 + .../src/pos_queries.rs | 53 ++--- shared/Cargo.toml | 4 +- shared/src/ledger/eth_bridge.rs | 4 + shared/src/ledger/mod.rs | 2 - .../ethereum_bridge}/authorize.rs | 2 +- .../ethereum_bridge}/bridge_pool_vp.rs | 39 +-- .../ledger/native_vp/ethereum_bridge/mod.rs | 7 + .../ethereum_bridge/vp.rs} | 65 ++--- shared/src/ledger/native_vp/mod.rs | 1 + shared/src/ledger/pos/mod.rs | 1 + shared/src/ledger/protocol/mod.rs | 19 +- tests/src/e2e/eth_bridge_tests.rs | 2 +- tests/src/native_vp/eth_bridge_pool.rs | 9 +- wasm/Cargo.lock | 223 +++++++++++++++++- wasm_for_tests/wasm_source/Cargo.lock | 223 +++++++++++++++++- 48 files changed, 844 insertions(+), 291 deletions(-) create mode 100644 ethereum_bridge/Cargo.toml create mode 100644 ethereum_bridge/src/ledger/bridge_pool_vp.rs rename {shared/src/ledger/eth_bridge => ethereum_bridge/src/ledger}/mod.rs (67%) rename {shared/src/ledger/eth_bridge => ethereum_bridge/src/ledger}/parameters.rs (93%) create mode 100644 ethereum_bridge/src/ledger/protocol/mod.rs rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs (91%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/ethereum_events/events.rs (92%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/ethereum_events/mod.rs (93%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/mod.rs (83%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/read.rs (89%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/update.rs (88%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/utils.rs (96%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/validator_set_update/mod.rs (88%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/votes.rs (95%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/votes/storage.rs (90%) rename {shared => ethereum_bridge}/src/ledger/protocol/transactions/votes/update.rs (97%) rename {shared/src/ledger/eth_bridge => ethereum_bridge/src/ledger}/storage/mod.rs (100%) rename {shared/src/ledger/eth_bridge => ethereum_bridge/src/ledger}/storage/vote_tallies.rs (95%) create mode 100644 ethereum_bridge/src/ledger/vp.rs create mode 100644 ethereum_bridge/src/lib.rs rename shared/src/ledger/queries_ext.rs => proof_of_stake/src/pos_queries.rs (91%) create mode 100644 shared/src/ledger/eth_bridge.rs rename shared/src/ledger/{eth_bridge/vp => native_vp/ethereum_bridge}/authorize.rs (95%) rename shared/src/ledger/{eth_bridge => native_vp/ethereum_bridge}/bridge_pool_vp.rs (98%) create mode 100644 shared/src/ledger/native_vp/ethereum_bridge/mod.rs rename shared/src/ledger/{eth_bridge/vp/mod.rs => native_vp/ethereum_bridge/vp.rs} (94%) diff --git a/Cargo.lock b/Cargo.lock index fcfc0371f9..fedee68208 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2218,7 +2218,6 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2235,7 +2234,7 @@ dependencies = [ "digest 0.10.5", "ed25519-dalek", "either", - "ferveo-common", + "ferveo-common 0.1.0", "group-threshold-cryptography", "hex", "itertools", @@ -2252,6 +2251,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ferveo-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde 1.0.147", + "serde_bytes", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -2650,7 +2661,6 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -4043,7 +4053,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -4054,6 +4064,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", + "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", @@ -4078,7 +4089,6 @@ dependencies = [ "test-log", "thiserror", "tokio", - "toml", "tracing 0.1.37", "tracing-subscriber 0.3.16", "wasmer", @@ -4119,7 +4129,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0", "file-lock", "flate2", "futures 0.3.25", @@ -4206,7 +4216,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common", + "ferveo-common 0.1.0", "group-threshold-cryptography", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", @@ -4254,6 +4264,28 @@ dependencies = [ "namada", ] +[[package]] +name = "namada_ethereum_bridge" +version = "0.11.0" +dependencies = [ + "assert_matches", + "borsh", + "eyre", + "itertools", + "namada_core", + "namada_proof_of_stake", + "serde 1.0.147", + "serde_json", + "tendermint 0.23.5", + "tendermint 0.23.6", + "tendermint-proto 0.23.5", + "tendermint-proto 0.23.6", + "tendermint-rpc 0.23.5", + "tendermint-rpc 0.23.6", + "toml", + "tracing 0.1.37", +] + [[package]] name = "namada_macros" version = "0.11.0" @@ -4268,10 +4300,13 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", + "ferveo-common 0.1.0", "namada_core", "proptest", "rust_decimal", "rust_decimal_macros", + "tendermint-proto 0.23.5", + "tendermint-proto 0.23.6", "thiserror", "tracing 0.1.37", ] @@ -6644,7 +6679,6 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -7052,18 +7086,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 42a99343fc..e79c08c748 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "apps", "core", + "ethereum_bridge", "proof_of_stake", "shared", "tests", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1fc0d75bec..e3a980766b 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -95,8 +95,8 @@ data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" -ferveo = {git = "https://github.com/anoma/ferveo"} -ferveo-common = {git = "https://github.com/anoma/ferveo"} +ferveo = {path = "../../ferveo/ferveo"} #{git = "https://github.com/anoma/ferveo"} +ferveo-common = {path = "../../ferveo/ferveo-common"}#{git = "https://github.com/anoma/ferveo"} eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 2e9836af85..efa72849d9 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -6,7 +6,7 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; -use namada::ledger::eth_bridge::parameters::{ +use namada::ledger::eth_bridge::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; use namada::ledger::governance::parameters::GovParams; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 60206fec72..f323ab4eea 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,9 +1,7 @@ //! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell #[cfg(feature = "abcipp")] -use namada::ledger::queries_ext::QueriesExt; -#[cfg(feature = "abcipp")] -use namada::ledger::queries_ext::SendValsetUpd; +use namada::ledger::pos::{PosQueries, SendValsetUpd}; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::proto::Tx; @@ -212,7 +210,7 @@ mod test_prepare_proposal { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos::namada_proof_of_stake::types::WeightedValidator; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::queries_ext::QueriesExt; + use namada::ledger::pos::PosQueries; use namada::proto::{Signed, SignedTxData}; use namada::types::ethereum_events::EthereumEvent; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e9a92a9d6a..a4c46b9c14 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,7 +2,7 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use data_encoding::HEXUPPER; -use namada::ledger::queries_ext::{QueriesExt, SendValsetUpd}; +use namada::ledger::pos::{PosQueries, SendValsetUpd}; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] use namada::types::voting_power::FractionalVotingPower; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index bd10832b8d..e5df012af9 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -121,7 +121,7 @@ where #[cfg(test)] #[cfg(not(feature = "abcipp"))] mod test_queries { - use namada::ledger::queries_ext::{QueriesExt, SendValsetUpd}; + use namada::ledger::pos::{PosQueries, SendValsetUpd}; use namada::types::storage::Epoch; use super::*; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions.rs b/apps/src/lib/node/ledger/shell/vote_extensions.rs index c3ba354234..cdba951eb7 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions.rs @@ -5,7 +5,7 @@ pub mod val_set_update; #[cfg(feature = "abcipp")] use borsh::BorshDeserialize; -use namada::ledger::queries_ext::{QueriesExt, SendValsetUpd}; +use namada::ledger::pos::{PosQueries, SendValsetUpd}; use namada::proto::Signed; use namada::types::transaction::protocol::ProtocolTxType; #[cfg(feature = "abcipp")] diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs index 8493a37b34..0deeb29cc1 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/eth_events.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap}; -use namada::ledger::queries_ext::QueriesExt; +use namada::ledger::pos::PosQueries; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::proto::Signed; @@ -294,7 +294,7 @@ mod test_vote_extensions { use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::queries_ext::QueriesExt; + use namada::ledger::pos::PosQueries; use namada::types::ethereum_events::{ EthAddress, EthereumEvent, TransferToEthereum, }; diff --git a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs index 1f450356ae..ea7548df50 100644 --- a/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs +++ b/apps/src/lib/node/ledger/shell/vote_extensions/val_set_update.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use namada::ledger::pos::namada_proof_of_stake::PosBase; -use namada::ledger::queries_ext::QueriesExt; +use namada::ledger::pos::PosQueries; use namada::ledger::storage::traits::StorageHasher; use namada::ledger::storage::{DBIter, DB}; use namada::types::storage::BlockHeight; @@ -308,7 +308,7 @@ mod test_vote_extensions { use borsh::BorshSerialize; use namada::ledger::pos; use namada::ledger::pos::namada_proof_of_stake::PosBase; - use namada::ledger::queries_ext::QueriesExt; + use namada::ledger::pos::PosQueries; use namada::types::key::RefTo; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::ethereum_events; diff --git a/core/Cargo.toml b/core/Cargo.toml index 689d5171e6..ba58804d1b 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -68,9 +68,9 @@ derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" eyre = "0.6.8" -ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} -ferveo-common = {git = "https://github.com/anoma/ferveo"} -tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} +ferveo = {optional = true, path = "../../ferveo/ferveo"} # git = "https://github.com/anoma/ferveo"} +ferveo-common = {path = "../../ferveo/ferveo-common"} #git = "https://github.com/anoma/ferveo"} +tpke = {package = "group-threshold-cryptography", optional = true, path = "../../ferveo/tpke"}#git = "https://github.com/anoma/ferveo"} # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.14.0", default-features = false, optional = true} ibc-proto = {version = "0.17.1", default-features = false, optional = true} diff --git a/ethereum_bridge/Cargo.toml b/ethereum_bridge/Cargo.toml new file mode 100644 index 0000000000..9ab2883e75 --- /dev/null +++ b/ethereum_bridge/Cargo.toml @@ -0,0 +1,49 @@ +[package] +authors = ["Heliax AG "] +edition = "2021" +license = "GPL-3.0" +name = "namada_ethereum_bridge" +resolver = "2" +version = "0.11.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["abciplus"] + +abcipp = [ + "tendermint-abcipp", + "tendermint-rpc-abcipp", + "tendermint-proto-abcipp", + "namada_core/abcipp", + "namada_proof_of_stake/abcipp" +] + +abciplus = [ + "tendermint", + "tendermint-rpc", + "tendermint-proto", + "namada_core/abciplus", + "namada_core/tendermint", + "namada_proof_of_stake/abciplus", +] + +[dependencies] +namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify", "ferveo-tpke"]} +namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} +borsh = "0.9.0" +eyre = "0.6.8" +itertools = "0.10.0" +serde = {version = "1.0.125", features = ["derive"]} +serde_json = "1.0.62" +tendermint-abcipp = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-rpc-abcipp = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client"], optional = true} +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint = {version = "0.23.6", optional = true} +tendermint-rpc = {version = "0.23.6", features = ["http-client"], optional = true} +tendermint-proto = {version = "0.23.6", optional = true} +tracing = "0.1.30" + +[dev-dependencies] +assert_matches = "1.5.0" +toml = "0.5.8" \ No newline at end of file diff --git a/ethereum_bridge/src/ledger/bridge_pool_vp.rs b/ethereum_bridge/src/ledger/bridge_pool_vp.rs new file mode 100644 index 0000000000..771d6eb172 --- /dev/null +++ b/ethereum_bridge/src/ledger/bridge_pool_vp.rs @@ -0,0 +1,28 @@ +use borsh::BorshSerialize; +use namada_core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::address::nam; +use namada_core::types::token::{balance_key, Amount}; + +/// Initialize the storage owned by the Bridge Pool VP. +/// +/// This means that the amount of escrowed gas fees is +/// initialized to 0. +pub fn init_storage(storage: &mut Storage) +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = balance_key(&nam(), &BRIDGE_POOL_ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Bridge pool VP shouldn't \ + fail.", + ); +} diff --git a/shared/src/ledger/eth_bridge/mod.rs b/ethereum_bridge/src/ledger/mod.rs similarity index 67% rename from shared/src/ledger/eth_bridge/mod.rs rename to ethereum_bridge/src/ledger/mod.rs index c2415ed7d7..a5d108592b 100644 --- a/shared/src/ledger/eth_bridge/mod.rs +++ b/ethereum_bridge/src/ledger/mod.rs @@ -1,7 +1,6 @@ //! Validity predicate and storage keys for the Ethereum bridge account pub mod bridge_pool_vp; pub mod parameters; +pub mod protocol; pub mod storage; pub mod vp; - -pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; diff --git a/shared/src/ledger/eth_bridge/parameters.rs b/ethereum_bridge/src/ledger/parameters.rs similarity index 93% rename from shared/src/ledger/eth_bridge/parameters.rs rename to ethereum_bridge/src/ledger/parameters.rs index 46ff52c0a3..d619c4f1a0 100644 --- a/shared/src/ledger/eth_bridge/parameters.rs +++ b/ethereum_bridge/src/ledger/parameters.rs @@ -2,13 +2,13 @@ use std::num::NonZeroU64; use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::ledger::storage; +use namada_core::ledger::storage::types::encode; +use namada_core::ledger::storage::Storage; +use namada_core::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -use crate::ledger::eth_bridge; -use crate::ledger::eth_bridge::{bridge_pool_vp, storage as bridge_storage}; -use crate::ledger::storage::types::encode; -use crate::ledger::storage::{self, Storage}; -use crate::types::ethereum_events::EthAddress; +use crate::ledger::{bridge_pool_vp, storage as bridge_storage, vp}; /// Represents a configuration value for the minimum number of /// confirmations an Ethereum event must reach before it can be acted on. @@ -153,7 +153,7 @@ impl EthereumBridgeConfig { .write(&governance_contract_key, encode(governance)) .unwrap(); // Initialize the storage for the Ethereum Bridge VP. - eth_bridge::vp::init_storage(storage); + vp::init_storage(storage); // Initialize the storage for the Bridge Pool VP. bridge_pool_vp::init_storage(storage); } @@ -162,12 +162,12 @@ impl EthereumBridgeConfig { #[cfg(test)] mod tests { use eyre::Result; + use namada_core::types::ethereum_events::EthAddress; - use crate::ledger::eth_bridge::parameters::{ + use crate::ledger::parameters::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, UpgradeableContract, }; - use crate::types::ethereum_events::EthAddress; /// Ensure we can serialize and deserialize a [`Config`] struct to and from /// TOML. This can fail if complex fields are ordered before simple fields diff --git a/ethereum_bridge/src/ledger/protocol/mod.rs b/ethereum_bridge/src/ledger/protocol/mod.rs new file mode 100644 index 0000000000..0824d7a9cb --- /dev/null +++ b/ethereum_bridge/src/ledger/protocol/mod.rs @@ -0,0 +1 @@ +pub mod transactions; diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs similarity index 91% rename from shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs rename to ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs index aedec3e76b..785395860c 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs @@ -1,8 +1,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_core::types::ethereum_events::EthereumEvent; +use namada_core::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use crate::ledger::protocol::transactions::votes::{dedupe, Tally, Votes}; -use crate::types::ethereum_events::EthereumEvent; -use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; /// Represents an Ethereum event being seen by some validators #[derive( @@ -51,12 +51,13 @@ pub struct EthMsg { mod tests { use std::collections::BTreeSet; - use super::*; - use crate::types::address; - use crate::types::ethereum_events::testing::{ + use namada_core::types::address; + use namada_core::types::ethereum_events::testing::{ arbitrary_nonce, arbitrary_single_transfer, }; - use crate::types::storage::BlockHeight; + use namada_core::types::storage::BlockHeight; + + use super::*; #[test] /// Tests [`From`] for [`EthMsgUpdate`] diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs similarity index 92% rename from shared/src/ledger/protocol/transactions/ethereum_events/events.rs rename to ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs index 49cbb5e6af..90def86686 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs @@ -3,13 +3,13 @@ use std::collections::BTreeSet; use eyre::Result; +use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, Storage, DB}; +use namada_core::types::ethereum_events::{EthereumEvent, TransferToNamada}; +use namada_core::types::storage::Key; -use crate::ledger::eth_bridge::storage::wrapped_erc20s; use crate::ledger::protocol::transactions::update; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::ethereum_events::{EthereumEvent, TransferToNamada}; -use crate::types::storage::Key; /// Updates storage based on the given confirmed `event`. For example, for a /// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding @@ -90,15 +90,15 @@ mod tests { use assert_matches::assert_matches; use borsh::BorshSerialize; - - use super::*; - use crate::ledger::storage::testing::TestStorage; - use crate::types::address; - use crate::types::ethereum_events::testing::{ + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::types::address; + use namada_core::types::ethereum_events::testing::{ arbitrary_eth_address, arbitrary_keccak_hash, arbitrary_nonce, DAI_ERC20_ETH_ADDRESS, }; - use crate::types::token::Amount; + use namada_core::types::token::Amount; + + use super::*; #[test] /// Test that we do not make any changes to storage when acting on most diff --git a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs similarity index 93% rename from shared/src/ledger/protocol/transactions/ethereum_events/mod.rs rename to ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs index fde98f3f11..91c53d4c1c 100644 --- a/shared/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs @@ -7,19 +7,19 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use eth_msgs::EthMsgUpdate; use eyre::Result; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, Storage, DB}; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockHeight; +use namada_core::types::transaction::TxResult; +use namada_core::types::vote_extensions::ethereum_events::MultiSignedEthEvent; +use namada_core::types::voting_power::FractionalVotingPower; use super::ChangedKeys; -use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils::{self}; use crate::ledger::protocol::transactions::votes::update::NewVotes; use crate::ledger::protocol::transactions::votes::{self, calculate_new}; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::address::Address; -use crate::types::storage::BlockHeight; -use crate::types::transaction::TxResult; -use crate::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::types::voting_power::FractionalVotingPower; +use crate::ledger::storage::vote_tallies; impl utils::GetVoters for HashSet { #[inline] @@ -38,7 +38,7 @@ impl utils::GetVoters for HashSet { /// /// This function is deterministic based on some existing blockchain state and /// the passed `events`. -pub(crate) fn apply_derived_tx( +pub fn apply_derived_tx( storage: &mut Storage, events: Vec, ) -> Result @@ -172,24 +172,27 @@ mod tests { use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; + use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; + use namada_core::ledger::storage::mockdb::MockDB; + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::ledger::storage::traits::Sha256Hasher; + use namada_core::types::address; + use namada_core::types::ethereum_events::testing::{ + arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, + arbitrary_single_transfer, DAI_ERC20_ETH_ADDRESS, + }; + use namada_core::types::ethereum_events::{ + EthereumEvent, TransferToNamada, + }; + use namada_core::types::token::Amount; + use namada_proof_of_stake::epoched::Epoched; + use namada_proof_of_stake::storage::ValidatorSet; + use namada_proof_of_stake::types::WeightedValidator; + use namada_proof_of_stake::PosBase; use super::*; - use crate::ledger::eth_bridge::storage::wrapped_erc20s; - use crate::ledger::pos::namada_proof_of_stake::epoched::Epoched; - use crate::ledger::pos::namada_proof_of_stake::PosBase; - use crate::ledger::pos::types::{ValidatorSet, WeightedValidator}; use crate::ledger::protocol::transactions::utils::GetVoters; use crate::ledger::protocol::transactions::votes::Votes; - use crate::ledger::storage::mockdb::MockDB; - use crate::ledger::storage::testing::TestStorage; - use crate::ledger::storage::traits::Sha256Hasher; - use crate::types::address; - use crate::types::ethereum_events::testing::{ - arbitrary_amount, arbitrary_eth_address, arbitrary_nonce, - arbitrary_single_transfer, DAI_ERC20_ETH_ADDRESS, - }; - use crate::types::ethereum_events::{EthereumEvent, TransferToNamada}; - use crate::types::token::Amount; #[test] /// Test applying a `TransfersToNamada` batch containing a single transfer diff --git a/shared/src/ledger/protocol/transactions/mod.rs b/ethereum_bridge/src/ledger/protocol/transactions/mod.rs similarity index 83% rename from shared/src/ledger/protocol/transactions/mod.rs rename to ethereum_bridge/src/ledger/protocol/transactions/mod.rs index 0a8d4add86..9c7f684b71 100644 --- a/shared/src/ledger/protocol/transactions/mod.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/mod.rs @@ -4,22 +4,16 @@ //! to update their blockchain state in a deterministic way. This can be done //! natively rather than via the wasm environment as happens with regular //! transactions. - -use std::collections::BTreeSet; - -use crate::types::storage; - -pub(super) mod ethereum_events; - -pub(super) mod validator_set_update; - -mod votes; - +pub mod ethereum_events; mod read; - mod update; - mod utils; +pub mod validator_set_update; +mod votes; + +use std::collections::BTreeSet; + +use namada_core::types::storage; /// The keys changed while applying a protocol transaction. pub type ChangedKeys = BTreeSet; diff --git a/shared/src/ledger/protocol/transactions/read.rs b/ethereum_bridge/src/ledger/protocol/transactions/read.rs similarity index 89% rename from shared/src/ledger/protocol/transactions/read.rs rename to ethereum_bridge/src/ledger/protocol/transactions/read.rs index 6183b7b23a..4732a503f2 100644 --- a/shared/src/ledger/protocol/transactions/read.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/read.rs @@ -1,11 +1,10 @@ //! Helpers for reading from storage use borsh::BorshDeserialize; use eyre::{eyre, Result}; - -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::storage; -use crate::types::token::Amount; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::storage::{DBIter, Storage, DB}; +use namada_core::types::storage; +use namada_core::types::token::Amount; /// Returns the stored Amount, or 0 if not stored pub(super) fn amount_or_default( @@ -55,11 +54,11 @@ where mod tests { use assert_matches::assert_matches; use borsh::BorshSerialize; + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::types::storage; + use namada_core::types::token::Amount; use crate::ledger::protocol::transactions::read; - use crate::ledger::storage::testing::TestStorage; - use crate::types::storage; - use crate::types::token::Amount; #[test] fn test_amount_returns_zero_for_uninitialized_storage() { diff --git a/shared/src/ledger/protocol/transactions/update.rs b/ethereum_bridge/src/ledger/protocol/transactions/update.rs similarity index 88% rename from shared/src/ledger/protocol/transactions/update.rs rename to ethereum_bridge/src/ledger/protocol/transactions/update.rs index d70ed4d313..ec3cd686ca 100644 --- a/shared/src/ledger/protocol/transactions/update.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/update.rs @@ -1,11 +1,9 @@ //! Helpers for writing to storage use borsh::{BorshDeserialize, BorshSerialize}; use eyre::Result; - -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::storage; -use crate::types::token::Amount; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::storage; +use namada_core::types::token::Amount; /// Reads the `Amount` from key, applies update then writes it back pub fn amount( @@ -44,9 +42,8 @@ where mod tests { use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{eyre, Result}; - - use crate::ledger::storage::testing::TestStorage; - use crate::types::storage; + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::types::storage; #[test] /// Test updating a value diff --git a/shared/src/ledger/protocol/transactions/utils.rs b/ethereum_bridge/src/ledger/protocol/transactions/utils.rs similarity index 96% rename from shared/src/ledger/protocol/transactions/utils.rs rename to ethereum_bridge/src/ledger/protocol/transactions/utils.rs index a5e98467f5..a4b004304e 100644 --- a/shared/src/ledger/protocol/transactions/utils.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/utils.rs @@ -2,15 +2,13 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use eyre::eyre; use itertools::Itertools; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockHeight; use namada_core::types::token; - -use crate::ledger::pos::types::WeightedValidator; -use crate::ledger::queries_ext::QueriesExt; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::address::Address; -use crate::types::storage::BlockHeight; -use crate::types::voting_power::FractionalVotingPower; +use namada_core::types::voting_power::FractionalVotingPower; +use namada_proof_of_stake::pos_queries::PosQueries; +use namada_proof_of_stake::types::WeightedValidator; /// Proof of some arbitrary tally whose voters can be queried. pub(super) trait GetVoters { @@ -147,10 +145,10 @@ mod tests { use std::collections::HashSet; use assert_matches::assert_matches; + use namada_core::types::address; + use namada_core::types::ethereum_events::testing::arbitrary_bonded_stake; use super::*; - use crate::types::address; - use crate::types::ethereum_events::testing::arbitrary_bonded_stake; #[test] /// Test getting the voting power for the sole active validator from the set diff --git a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs b/ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs similarity index 88% rename from shared/src/ledger/protocol/transactions/validator_set_update/mod.rs rename to ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs index b8b71a2749..fcc566798f 100644 --- a/shared/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs @@ -3,22 +3,21 @@ use std::collections::{HashMap, HashSet}; use eyre::Result; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockHeight; +#[allow(unused_imports)] +use namada_core::types::transaction::protocol::ProtocolTxType; +use namada_core::types::transaction::TxResult; +use namada_core::types::vote_extensions::validator_set_update; +use namada_core::types::voting_power::FractionalVotingPower; +use namada_proof_of_stake::pos_queries::PosQueries; use super::ChangedKeys; -use crate::ledger::eth_bridge::storage::vote_tallies; use crate::ledger::protocol::transactions::utils; use crate::ledger::protocol::transactions::votes::update::NewVotes; use crate::ledger::protocol::transactions::votes::{self, Votes}; -use crate::ledger::queries_ext::QueriesExt; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::address::Address; -use crate::types::storage::BlockHeight; -#[allow(unused_imports)] -use crate::types::transaction::protocol::ProtocolTxType; -use crate::types::transaction::TxResult; -use crate::types::vote_extensions::validator_set_update; -use crate::types::voting_power::FractionalVotingPower; +use crate::ledger::storage::vote_tallies; impl utils::GetVoters for validator_set_update::VextDigest { #[inline] @@ -27,7 +26,7 @@ impl utils::GetVoters for validator_set_update::VextDigest { } } -pub(crate) fn aggregate_votes( +pub fn aggregate_votes( storage: &mut Storage, ext: validator_set_update::VextDigest, ) -> Result diff --git a/shared/src/ledger/protocol/transactions/votes.rs b/ethereum_bridge/src/ledger/protocol/transactions/votes.rs similarity index 95% rename from shared/src/ledger/protocol/transactions/votes.rs rename to ethereum_bridge/src/ledger/protocol/transactions/votes.rs index 98dd689cee..3ffba10d76 100644 --- a/shared/src/ledger/protocol/transactions/votes.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/votes.rs @@ -5,12 +5,11 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockHeight; +use namada_core::types::voting_power::FractionalVotingPower; -use super::ChangedKeys; -use crate::ledger::protocol::transactions::read; -use crate::types::address::Address; -use crate::types::storage::BlockHeight; -use crate::types::voting_power::FractionalVotingPower; +use super::{read, ChangedKeys}; pub(super) mod storage; pub(super) mod update; @@ -82,9 +81,10 @@ pub fn dedupe(signers: BTreeSet<(Address, BlockHeight)>) -> Votes { mod tests { use std::collections::BTreeSet; + use namada_core::types::address; + use namada_core::types::storage::BlockHeight; + use super::*; - use crate::types::address; - use crate::types::storage::BlockHeight; #[test] fn test_dedupe_empty() { diff --git a/shared/src/ledger/protocol/transactions/votes/storage.rs b/ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs similarity index 90% rename from shared/src/ledger/protocol/transactions/votes/storage.rs rename to ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs index 33fca417f8..102cb9ae05 100644 --- a/shared/src/ledger/protocol/transactions/votes/storage.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs @@ -1,10 +1,10 @@ use borsh::BorshSerialize; use eyre::Result; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::voting_power::FractionalVotingPower; use super::{Tally, Votes}; -use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; -use crate::types::voting_power::FractionalVotingPower; +use crate::ledger::storage::vote_tallies; pub fn write( storage: &mut Storage, @@ -49,11 +49,12 @@ where mod tests { use std::collections::BTreeMap; + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::types::address; + use namada_core::types::ethereum_events::EthereumEvent; + use namada_core::types::voting_power::FractionalVotingPower; + use super::*; - use crate::ledger::storage::testing::TestStorage; - use crate::types::address; - use crate::types::ethereum_events::EthereumEvent; - use crate::types::voting_power::FractionalVotingPower; #[test] fn test_write_tally() { diff --git a/shared/src/ledger/protocol/transactions/votes/update.rs b/ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs similarity index 97% rename from shared/src/ledger/protocol/transactions/votes/update.rs rename to ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs index 06c0db203e..122ef572fe 100644 --- a/shared/src/ledger/protocol/transactions/votes/update.rs +++ b/ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs @@ -2,14 +2,13 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; use eyre::{eyre, Result}; +use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use namada_core::types::address::Address; +use namada_core::types::storage::BlockHeight; +use namada_core::types::voting_power::FractionalVotingPower; use super::{ChangedKeys, Tally, Votes}; -use crate::ledger::eth_bridge::storage::vote_tallies; -use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; -use crate::types::address::Address; -use crate::types::storage::BlockHeight; -use crate::types::voting_power::FractionalVotingPower; +use crate::ledger::storage::vote_tallies; /// Wraps all the information about new votes to be applied to some existing /// tally in storage. @@ -193,12 +192,13 @@ fn keys_changed( mod tests { use std::collections::BTreeMap; + use namada_core::ledger::storage::testing::TestStorage; + use namada_core::types::address; + use namada_core::types::ethereum_events::EthereumEvent; + use super::*; use crate::ledger::protocol::transactions::votes; use crate::ledger::protocol::transactions::votes::update::tests::helpers::{arbitrary_event, setup_tally}; - use crate::ledger::storage::testing::TestStorage; - use crate::types::address; - use crate::types::ethereum_events::EthereumEvent; mod helpers { use super::*; diff --git a/shared/src/ledger/eth_bridge/storage/mod.rs b/ethereum_bridge/src/ledger/storage/mod.rs similarity index 100% rename from shared/src/ledger/eth_bridge/storage/mod.rs rename to ethereum_bridge/src/ledger/storage/mod.rs diff --git a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs b/ethereum_bridge/src/ledger/storage/vote_tallies.rs similarity index 95% rename from shared/src/ledger/eth_bridge/storage/vote_tallies.rs rename to ethereum_bridge/src/ledger/storage/vote_tallies.rs index 19d9a9370e..57dd75a0ea 100644 --- a/shared/src/ledger/eth_bridge/storage/vote_tallies.rs +++ b/ethereum_bridge/src/ledger/storage/vote_tallies.rs @@ -1,9 +1,9 @@ //! Functionality for accessing keys to do with tallying votes -use crate::types::ethereum_events::EthereumEvent; -use crate::types::hash::Hash; -use crate::types::storage::{Epoch, Key}; -use crate::types::vote_extensions::validator_set_update::VotingPowersMap; +use namada_core::types::ethereum_events::EthereumEvent; +use namada_core::types::hash::Hash; +use namada_core::types::storage::{Epoch, Key}; +use namada_core::types::vote_extensions::validator_set_update::VotingPowersMap; /// Storage sub-key space reserved to keeping track of the /// voting power assigned to Ethereum events. @@ -127,9 +127,11 @@ impl From<&Epoch> for Keys { #[cfg(test)] mod test { + use assert_matches::assert_matches; + use namada_core::ledger::eth_bridge::ADDRESS; + use namada_core::types::storage::DbKeySeg; + use super::*; - use crate::ledger::eth_bridge::ADDRESS; - use crate::types::storage::DbKeySeg; mod helpers { use super::*; diff --git a/ethereum_bridge/src/ledger/vp.rs b/ethereum_bridge/src/ledger/vp.rs new file mode 100644 index 0000000000..c857498170 --- /dev/null +++ b/ethereum_bridge/src/ledger/vp.rs @@ -0,0 +1,28 @@ +use borsh::BorshSerialize; +use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; +use namada_core::types::address::nam; +use namada_core::types::token::{balance_key, Amount}; + +/// Initialize the storage owned by the Ethereum Bridge VP. +/// +/// This means that the amount of escrowed Nam is +/// initialized to 0. +pub fn init_storage(storage: &mut ledger_storage::Storage) +where + D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: StorageHasher, +{ + let escrow_key = + balance_key(&nam(), &namada_core::ledger::eth_bridge::ADDRESS); + storage + .write( + &escrow_key, + Amount::default() + .try_to_vec() + .expect("Serializing an amount shouldn't fail."), + ) + .expect( + "Initializing the escrow balance of the Ethereum Bridge VP \ + shouldn't fail.", + ); +} diff --git a/ethereum_bridge/src/lib.rs b/ethereum_bridge/src/lib.rs new file mode 100644 index 0000000000..22a6f96d23 --- /dev/null +++ b/ethereum_bridge/src/lib.rs @@ -0,0 +1,16 @@ +pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; +pub use namada_core::types::{ + address, chain, eth_abi, eth_bridge_pool, ethereum_events, governance, + hash, internal, keccak, masp, storage, time, token, transaction, + validity_predicate, vote_extensions, voting_power, +}; +#[cfg(not(feature = "abcipp"))] +pub use {tendermint, tendermint_proto, tendermint_rpc}; +#[cfg(feature = "abcipp")] +pub use { + tendermint_abcipp as tendermint, + tendermint_proto_abcipp as tendermint_proto, + tendermint_rpc_abcipp as tendermint_rpc, +}; + +pub mod ledger; diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index d5dea7aa95..68e0ea0f8d 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -10,8 +10,13 @@ version = "0.11.0" [features] default = ["abciplus"] +abcipp = [ + "namada_core/abcipp", + "tendermint-proto-abcipp", +] abciplus = [ "namada_core/abciplus", + "tendermint-proto", ] # testing helpers testing = ["proptest"] @@ -20,11 +25,16 @@ testing = ["proptest"] namada_core = {path = "../core", default-features = false} borsh = "0.9.1" derivative = "2.2.0" +ferveo-common = {path = "../../ferveo/ferveo-common" }#git = "https://github.com/anoma/ferveo"} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} rust_decimal = { version = "1.26.1", features = ["borsh"] } rust_decimal_macros = "1.26.1" +tendermint-proto-abcipp = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} +tendermint-proto = {version = "0.23.6", optional = true} thiserror = "1.0.30" tracing = "0.1.30" [dev-dependencies] +# A fork with state machine testing +proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} \ No newline at end of file diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 015bc14ea3..77b47cfc8a 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -15,6 +15,7 @@ pub mod btree_set; pub mod epoched; pub mod parameters; +pub mod pos_queries; pub mod storage; pub mod types; pub mod validation; @@ -34,6 +35,10 @@ use namada_core::types::storage::Epoch; use namada_core::types::token; pub use parameters::PosParams; use rust_decimal::Decimal; +#[cfg(not(feature = "abcipp"))] +pub use tendermint_proto; +#[cfg(feature = "abcipp")] +pub use tendermint_proto_abcipp as tendermint_proto; use thiserror::Error; use types::{ ActiveValidator, Bonds, CommissionRates, GenesisValidator, Slash, diff --git a/shared/src/ledger/queries_ext.rs b/proof_of_stake/src/pos_queries.rs similarity index 91% rename from shared/src/ledger/queries_ext.rs rename to proof_of_stake/src/pos_queries.rs index e3f0230e9b..182e1f67f0 100644 --- a/shared/src/ledger/queries_ext.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -1,27 +1,25 @@ -//! API for querying the blockchain state. - +//! Storage API for querying data about Proof-of-stake related +//! data. This includes validator and epoch related data. use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; -use namada_core::ledger::storage::{self, Storage}; -use namada_core::ledger::storage_api; +use namada_core::ledger::parameters::EpochDuration; +use namada_core::ledger::storage::Storage; +use namada_core::ledger::{storage, storage_api}; +use namada_core::types::address::Address; +use namada_core::types::ethereum_events::EthAddress; use namada_core::types::key::dkg_session_keys::DkgPublicKey; -use namada_core::types::token; -use namada_proof_of_stake::PosBase; +use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::transaction::EllipticCurve; +use namada_core::types::vote_extensions::validator_set_update::EthAddrBook; +use namada_core::types::{key, token}; use thiserror::Error; -use crate::ledger::parameters::EpochDuration; -use crate::ledger::pos::types::WeightedValidator; -use crate::ledger::pos::PosParams; use crate::tendermint_proto::google::protobuf; use crate::tendermint_proto::types::EvidenceParams; -use crate::types::address::Address; -use crate::types::ethereum_events::EthAddress; -use crate::types::key; -use crate::types::storage::{BlockHeight, Epoch}; -use crate::types::transaction::EllipticCurve; -use crate::types::vote_extensions::validator_set_update::EthAddrBook; +use crate::types::WeightedValidator; +use crate::{PosBase, PosParams}; /// Errors returned by [`QueriesExt`] operations. #[derive(Error, Debug)] @@ -66,21 +64,9 @@ pub enum SendValsetUpd { AtPrevHeight, } -/// Methods used to query blockchain state, such as the currently -/// active set of validators. -pub trait QueriesExt { - // TODO: when Rust 1.65 becomes available in Namada, we should return this - // iterator type from [`QueriesExt::get_active_eth_addresses`], to - // avoid a heap allocation; `F` will be the closure used to process the - // iterator we currently return in the `Storage` impl - // ```ignore - // type ActiveEthAddressesIter<'db, F>: Iterator<(EthAddrBook, Address, token::Amount)>; - // ``` - // a similar strategy can be used for [`QueriesExt::get_active_validators`]: - // ```ignore - // type ActiveValidatorsIter<'db, F>: Iterator; - // ``` - +/// Methods used to query blockchain proof-of-stake related state, +/// such as the currently active set of validators. +pub trait PosQueries { /// Get the set of active validators for a given epoch (defaulting to the /// epoch of the current yet-to-be-committed block). fn get_active_validators( @@ -91,7 +77,6 @@ pub trait QueriesExt { /// Lookup the total voting power for an epoch (defaulting to the /// epoch of the current yet-to-be-committed block). fn get_total_voting_power(&self, epoch: Option) -> token::Amount; - /// Simple helper function for the ledger to get balances /// of the specified token at the specified address. fn get_balance(&self, token: &Address, owner: &Address) -> token::Amount; @@ -165,7 +150,7 @@ pub trait QueriesExt { ) -> Box + 'db>; } -impl QueriesExt for Storage +impl PosQueries for Storage where D: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, @@ -236,7 +221,7 @@ where .expect("Serializing public key should not fail"); let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); self.get_active_validators(Some(epoch)) - .iter() + .into_iter() .find(|validator| { let pk_key = key::protocol_pk_key(&validator.address); match self.read(&pk_key) { @@ -275,7 +260,7 @@ where ) -> Result<(token::Amount, key::common::PublicKey)> { let epoch = epoch.unwrap_or_else(|| self.get_current_epoch().0); self.get_active_validators(Some(epoch)) - .iter() + .into_iter() .find(|validator| address == &validator.address) .map(|validator| { let protocol_pk_key = key::protocol_pk_key(&validator.address); diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 912567b8b8..884212f653 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -51,10 +51,12 @@ abcipp = [ "tendermint-proto-abcipp", # it's OK to include the tendermint-rpc feature here, as we aren't currently building wasms with `abcipp` "tendermint-rpc-abcipp", + "namada_ethereum_bridge/abcipp", ] abciplus = [ "namada_core/abciplus", "namada_proof_of_stake/abciplus", + "namada_ethereum_bridge/abciplus", "ibc", "ibc-proto", "tendermint", @@ -82,6 +84,7 @@ testing = [ [dependencies] namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify"]} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} +namada_ethereum_bridge = {path = "../ethereum_bridge", default-features = false} async-trait = {version = "0.1.51", optional = true} bellman = "0.11.2" bls12_381 = "0.6.1" @@ -145,5 +148,4 @@ pretty_assertions = "0.7.2" proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tokio = {version = "1.8.2", default-features = false, features = ["rt", "macros"]} -toml = "0.5.8" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs new file mode 100644 index 0000000000..942d0bfb9d --- /dev/null +++ b/shared/src/ledger/eth_bridge.rs @@ -0,0 +1,4 @@ +//! Re-exporting types from the namada_ethereum_bridge crate. +pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; +pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; +pub use namada_ethereum_bridge::ledger::parameters::*; diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 951bf0ff4f..2ddf1d7cf4 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,5 +1,4 @@ //! The ledger modules - pub mod eth_bridge; pub mod events; pub mod ibc; @@ -9,7 +8,6 @@ pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; pub mod queries; -pub mod queries_ext; pub mod storage; pub mod vp_host_fns; diff --git a/shared/src/ledger/eth_bridge/vp/authorize.rs b/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs similarity index 95% rename from shared/src/ledger/eth_bridge/vp/authorize.rs rename to shared/src/ledger/native_vp/ethereum_bridge/authorize.rs index c5435b09dd..762d89d311 100644 --- a/shared/src/ledger/eth_bridge/vp/authorize.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs @@ -1,9 +1,9 @@ //! Functionality to do with checking whether a transaction is authorized by the //! "owner" of some key under this account use eyre::Result; +use namada_core::types::address::Address; use crate::ledger::native_vp::StorageReader; -use crate::types::address::Address; pub(super) fn is_authorized( _reader: impl StorageReader, diff --git a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs similarity index 98% rename from shared/src/ledger/eth_bridge/bridge_pool_vp.rs rename to shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index a4f38bb98c..3df37e3735 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -12,18 +12,18 @@ //! and that tokens to be transferred are escrowed. use std::collections::BTreeSet; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use eyre::eyre; use namada_core::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; +use namada_ethereum_bridge::ledger::storage; +use namada_ethereum_bridge::ledger::storage::wrapped_erc20s; -use crate::ledger::eth_bridge::storage; -use crate::ledger::eth_bridge::storage::wrapped_erc20s; -use crate::ledger::eth_bridge::vp::check_balance_changes; +use crate::ledger::native_vp::ethereum_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, Storage, DB}; +use crate::ledger::storage::{DBIter, DB}; use crate::proto::SignedTxData; use crate::types::address::{nam, Address, InternalAddress}; use crate::types::eth_bridge_pool::PendingTransfer; @@ -43,29 +43,6 @@ enum SignedAmount { Negative(Amount), } -/// Initialize the storage owned by the Bridge Pool VP. -/// -/// This means that the amount of escrowed gas fees is -/// initialized to 0. -pub fn init_storage(storage: &mut Storage) -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - let escrow_key = balance_key(&nam(), &BRIDGE_POOL_ADDRESS); - storage - .write( - &escrow_key, - Amount::default() - .try_to_vec() - .expect("Serializing an amount shouldn't fail."), - ) - .expect( - "Initializing the escrow balance of the Bridge pool VP shouldn't \ - fail.", - ); -} - /// Validity predicate for the Ethereum bridge pub struct BridgePoolVp<'ctx, D, H, CA> where @@ -395,11 +372,11 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use namada_core::types::address; - - use super::*; - use crate::ledger::eth_bridge::parameters::{ + use namada_ethereum_bridge::ledger::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; + + use super::*; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::traits::Sha256Hasher; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/mod.rs b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs new file mode 100644 index 0000000000..7e5062a251 --- /dev/null +++ b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs @@ -0,0 +1,7 @@ +//! Native validity predicates for the Namada Ethereum bridge. +//! This includes both the bridge vp and the vp for the bridge +//! pool. + +mod authorize; +pub mod bridge_pool_vp; +pub mod vp; diff --git a/shared/src/ledger/eth_bridge/vp/mod.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs similarity index 94% rename from shared/src/ledger/eth_bridge/vp/mod.rs rename to shared/src/ledger/native_vp/ethereum_bridge/vp.rs index f2f4456a90..2510991cb1 100644 --- a/shared/src/ledger/eth_bridge/vp/mod.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -1,45 +1,22 @@ //! Validity predicate for the Ethereum bridge - -mod authorize; - use std::collections::{BTreeSet, HashSet}; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use eyre::{eyre, Result}; use itertools::Itertools; - -use crate::ledger::eth_bridge::storage::{self, escrow_key, wrapped_erc20s}; +use namada_core::ledger::eth_bridge::storage::{ + self, escrow_key, wrapped_erc20s, +}; +use namada_core::ledger::storage::traits::StorageHasher; +use namada_core::ledger::{eth_bridge, storage as ledger_storage}; +use namada_core::types::address::{nam, Address, InternalAddress}; +use namada_core::types::storage::Key; +use namada_core::types::token::{balance_key, Amount}; + +use crate::ledger::native_vp::ethereum_bridge::authorize; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; -use crate::ledger::storage as ledger_storage; -use crate::ledger::storage::traits::StorageHasher; -use crate::types::address::{nam, Address, InternalAddress}; -use crate::types::storage::Key; -use crate::types::token::{balance_key, Amount}; use crate::vm::WasmCacheAccess; -/// Initialize the storage owned by the Ethereum Bridge VP. -/// -/// This means that the amount of escrowed Nam is -/// initialized to 0. -pub fn init_storage(storage: &mut ledger_storage::Storage) -where - D: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, -{ - let escrow_key = balance_key(&nam(), &super::ADDRESS); - storage - .write( - &escrow_key, - Amount::default() - .try_to_vec() - .expect("Serializing an amount shouldn't fail."), - ) - .expect( - "Initializing the escrow balance of the Ethereum Bridge VP \ - shouldn't fail.", - ); -} - /// Validity predicate for the Ethereum bridge pub struct EthBridge<'ctx, DB, H, CA> where @@ -65,7 +42,7 @@ where &self, verifiers: &BTreeSet
, ) -> Result { - let escrow_key = balance_key(&nam(), &super::ADDRESS); + let escrow_key = balance_key(&nam(), ð_bridge::ADDRESS); let escrow_pre: Amount = if let Ok(Some(bytes)) = self.ctx.read_bytes_pre(&escrow_key) { @@ -131,7 +108,7 @@ where { type Error = Error; - const ADDR: InternalAddress = super::INTERNAL_ADDRESS; + const ADDR: InternalAddress = eth_bridge::INTERNAL_ADDRESS; /// Validate that a wasm transaction is permitted to change keys under this /// account. @@ -410,14 +387,16 @@ mod tests { use std::default::Default; use std::env::temp_dir; + use borsh::BorshSerialize; + use namada_core::ledger::eth_bridge; use namada_core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada_core::types::address; + use namada_ethereum_bridge::ledger::parameters::{ + Contracts, EthereumBridgeConfig, UpgradeableContract, + }; use rand::Rng; use super::*; - use crate::ledger::eth_bridge::parameters::{ - Contracts, EthereumBridgeConfig, UpgradeableContract, - }; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::mockdb::MockDB; use crate::ledger::storage::traits::Sha256Hasher; @@ -500,7 +479,7 @@ mod tests { verifiers: &'a BTreeSet
, ) -> Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess> { Ctx::new( - &super::super::ADDRESS, + ð_bridge::ADDRESS, storage, write_log, tx, @@ -650,7 +629,7 @@ mod tests { .expect("Test failed"); // credit the balance to the escrow - let escrow_key = balance_key(&nam(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), ð_bridge::ADDRESS); writelog .write( &escrow_key, @@ -699,7 +678,7 @@ mod tests { .expect("Test failed"); // do not credit the balance to the escrow - let escrow_key = balance_key(&nam(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), ð_bridge::ADDRESS); writelog .write( &escrow_key, @@ -747,7 +726,7 @@ mod tests { .expect("Test failed"); // credit the balance to the escrow - let escrow_key = balance_key(&nam(), &super::super::ADDRESS); + let escrow_key = balance_key(&nam(), ð_bridge::ADDRESS); writelog .write( &escrow_key, diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index 4212789aeb..7b1be5678b 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -1,6 +1,7 @@ //! Native validity predicate interface associated with internal accounts such //! as the PoS and IBC modules. +pub mod ethereum_bridge; pub mod governance; pub mod parameters; pub mod slash_fund; diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 0ee800ca47..7a40e58110 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -6,6 +6,7 @@ use std::convert::TryFrom; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; +pub use namada_proof_of_stake::pos_queries::*; pub use namada_proof_of_stake::storage::*; pub use namada_proof_of_stake::types; use namada_proof_of_stake::PosBase; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 691440064b..900aec7eda 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -1,5 +1,4 @@ //! The ledger's protocol -mod transactions; use std::collections::BTreeSet; use std::panic; @@ -7,10 +6,10 @@ use std::panic; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; -use crate::ledger::eth_bridge::bridge_pool_vp::BridgePoolVp; -use crate::ledger::eth_bridge::vp::EthBridge; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; use crate::ledger::ibc::vp::{Ibc, IbcToken}; +use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; +use crate::ledger::native_vp::ethereum_bridge::vp::EthBridge; use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; use crate::ledger::native_vp::slash_fund::SlashFundVp; @@ -61,9 +60,9 @@ pub enum Error { #[error("SlashFund native VP error: {0}")] SlashFundNativeVpError(crate::ledger::native_vp::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] - EthBridgeNativeVpError(crate::ledger::eth_bridge::vp::Error), + EthBridgeNativeVpError(native_vp::ethereum_bridge::vp::Error), #[error("Ethereum bridge pool native VP error: {0}")] - BridgePoolNativeVpError(crate::ledger::eth_bridge::bridge_pool_vp::Error), + BridgePoolNativeVpError(native_vp::ethereum_bridge::bridge_pool_vp::Error), #[error("Access to an internal address {0} is forbidden")] AccessForbidden(InternalAddress), } @@ -205,6 +204,8 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { + use namada_ethereum_bridge::ledger::protocol::transactions; + use crate::types::vote_extensions::{ ethereum_events, validator_set_update, }; @@ -213,10 +214,8 @@ where ProtocolTxType::EthEventsVext(ext) => { let ethereum_events::VextDigest { events, .. } = ethereum_events::VextDigest::singleton(ext); - self::transactions::ethereum_events::apply_derived_tx( - storage, events, - ) - .map_err(Error::ProtocolTxError) + transactions::ethereum_events::apply_derived_tx(storage, events) + .map_err(Error::ProtocolTxError) } ProtocolTxType::ValSetUpdateVext(ext) => { // NOTE(feature = "abcipp"): we will not need to apply any @@ -231,7 +230,7 @@ where // relayer process of a newly available validator set update; // for this, we need to receive a mutable reference to the // event log, in `apply_protocol_tx()` - self::transactions::validator_set_update::aggregate_votes( + transactions::validator_set_update::aggregate_votes( storage, validator_set_update::VextDigest::singleton(ext), ) diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 819a213317..43a2ca0f3e 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -1,5 +1,5 @@ use color_eyre::eyre::Result; -use namada::ledger::eth_bridge::parameters::{ +use namada::ledger::eth_bridge::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; use namada::types::address::wnam; diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index e8e1f9b5c9..450b0460ff 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -4,12 +4,11 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; - use namada::ledger::eth_bridge::bridge_pool_vp::BridgePoolVp; - use namada::ledger::eth_bridge::parameters::{ - Contracts, EthereumBridgeConfig, UpgradeableContract, + use namada::ledger::eth_bridge::{ + wrapped_erc20s, Contracts, EthereumBridgeConfig, UpgradeableContract, + ADDRESS, }; - use namada::ledger::eth_bridge::storage::wrapped_erc20s; - use namada::ledger::eth_bridge::ADDRESS; + use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::Tx; use namada::types::address::{nam, wnam}; use namada::types::eth_bridge_pool::{ diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index ff6b383781..d49b3e3f63 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -138,6 +150,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -312,6 +337,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -392,6 +426,15 @@ dependencies = [ "wyz 0.5.1", ] +[[package]] +name = "blake2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -1239,6 +1282,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1265,6 +1309,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1421,6 +1469,54 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common 0.1.0", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + +[[package]] +name = "ferveo-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde", + "serde_bytes", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -1672,6 +1768,29 @@ dependencies = [ "subtle", ] +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -2192,6 +2311,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2454,6 +2576,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2502,6 +2634,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2529,6 +2673,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2576,7 +2726,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", "ibc", "ibc-proto", "itertools", @@ -2584,6 +2734,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", + "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", @@ -2617,6 +2768,7 @@ name = "namada_core" version = "0.11.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2628,7 +2780,9 @@ dependencies = [ "ed25519-consensus", "ethabi", "eyre", - "ferveo-common", + "ferveo", + "ferveo-common 0.1.0", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -2657,6 +2811,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "namada_ethereum_bridge" +version = "0.11.0" +dependencies = [ + "borsh", + "eyre", + "itertools", + "namada_core", + "namada_proof_of_stake", + "serde", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "tracing", +] + [[package]] name = "namada_macros" version = "0.11.0" @@ -2671,10 +2842,12 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", + "ferveo-common 0.1.0", "namada_core", "proptest", "rust_decimal", "rust_decimal_macros", + "tendermint-proto", "thiserror", "tracing", ] @@ -2782,6 +2955,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2794,6 +2981,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2815,6 +3011,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -4198,6 +4405,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subproductdomain" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index de06faa6a8..8ddededda7 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -98,6 +98,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ed-on-bls12-381" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b7ada17db3854f5994e74e60b18e10e818594935ee7e1d329800c117b32970" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + [[package]] name = "ark-ff" version = "0.3.0" @@ -138,6 +150,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ark-poly" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0f78f47537c2f15706db7e98fe64cc1711dbf9def81218194e17239e53e5aa" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.11.2", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -312,6 +337,15 @@ dependencies = [ "crunchy 0.1.6", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bip0039" version = "0.9.0" @@ -392,6 +426,15 @@ dependencies = [ "wyz 0.5.1", ] +[[package]] +name = "blake2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +dependencies = [ + "digest 0.10.5", +] + [[package]] name = "blake2b_simd" version = "0.5.11" @@ -1239,6 +1282,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature", ] @@ -1265,6 +1309,10 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", + "merlin", + "rand 0.7.3", + "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1421,6 +1469,54 @@ dependencies = [ "instant", ] +[[package]] +name = "ferveo" +version = "0.1.1" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "bincode", + "blake2", + "blake2b_simd 1.0.0", + "borsh", + "digest 0.10.5", + "ed25519-dalek", + "either", + "ferveo-common 0.1.0", + "group-threshold-cryptography", + "hex", + "itertools", + "measure_time", + "miracl_core", + "num", + "rand 0.7.3", + "rand 0.8.5", + "serde", + "serde_bytes", + "serde_json", + "subproductdomain", + "subtle", + "zeroize", +] + +[[package]] +name = "ferveo-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-ec", + "ark-serialize", + "ark-std", + "serde", + "serde_bytes", +] + [[package]] name = "ferveo-common" version = "0.1.0" @@ -1672,6 +1768,29 @@ dependencies = [ "subtle", ] +[[package]] +name = "group-threshold-cryptography" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "blake2b_simd 1.0.0", + "chacha20", + "hex", + "itertools", + "miracl_core", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "subproductdomain", + "thiserror", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -2192,6 +2311,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -2454,6 +2576,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +[[package]] +name = "measure_time" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" +dependencies = [ + "instant", + "log", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2502,6 +2634,18 @@ dependencies = [ "nonempty", ] +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "mime" version = "0.3.16" @@ -2529,6 +2673,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "miracl_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94c7128ba23c81f6471141b90f17654f89ef44a56e14b8a4dd0fddfccd655277" + [[package]] name = "moka" version = "0.8.6" @@ -2576,7 +2726,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common", + "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", "ibc", "ibc-proto", "itertools", @@ -2584,6 +2734,7 @@ dependencies = [ "masp_primitives", "masp_proofs", "namada_core", + "namada_ethereum_bridge", "namada_proof_of_stake", "parity-wasm", "paste", @@ -2617,6 +2768,7 @@ name = "namada_core" version = "0.11.0" dependencies = [ "ark-bls12-381", + "ark-ec", "ark-serialize", "bech32", "bellman", @@ -2628,7 +2780,9 @@ dependencies = [ "ed25519-consensus", "ethabi", "eyre", - "ferveo-common", + "ferveo", + "ferveo-common 0.1.0", + "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", @@ -2657,6 +2811,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "namada_ethereum_bridge" +version = "0.11.0" +dependencies = [ + "borsh", + "eyre", + "itertools", + "namada_core", + "namada_proof_of_stake", + "serde", + "serde_json", + "tendermint", + "tendermint-proto", + "tendermint-rpc", + "tracing", +] + [[package]] name = "namada_macros" version = "0.11.0" @@ -2671,10 +2842,12 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", + "ferveo-common 0.1.0", "namada_core", "proptest", "rust_decimal", "rust_decimal_macros", + "tendermint-proto", "thiserror", "tracing", ] @@ -2774,6 +2947,20 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -2786,6 +2973,15 @@ dependencies = [ "serde", ] +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2807,6 +3003,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -4190,6 +4397,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "subproductdomain" +version = "0.1.0" +dependencies = [ + "anyhow", + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", +] + [[package]] name = "subtle" version = "2.4.1" From b23b2ffe2dad352716965a2de58d75fc701bafcc Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 19 Dec 2022 11:48:10 +0100 Subject: [PATCH 1971/1995] [fix]: Checked the ferveo deps to point at the correct git revision --- Cargo.lock | 27 +++++++++------------------ apps/Cargo.toml | 4 ++-- core/Cargo.toml | 6 +++--- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- wasm/Cargo.lock | 25 ++++++++----------------- wasm_for_tests/wasm_source/Cargo.lock | 25 ++++++++----------------- 7 files changed, 32 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fedee68208..b93a4ef950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2218,6 +2218,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -2234,7 +2235,7 @@ dependencies = [ "digest 0.10.5", "ed25519-dalek", "either", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "hex", "itertools", @@ -2254,19 +2255,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -dependencies = [ - "anyhow", - "ark-ec", - "ark-serialize", - "ark-std", - "serde 1.0.147", - "serde_bytes", -] - -[[package]] -name = "ferveo-common" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", @@ -2661,6 +2650,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -4053,7 +4043,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", + "ferveo-common", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -4129,7 +4119,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common 0.1.0", + "ferveo-common", "file-lock", "flate2", "futures 0.3.25", @@ -4216,7 +4206,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", @@ -4300,7 +4290,7 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", - "ferveo-common 0.1.0", + "ferveo-common", "namada_core", "proptest", "rust_decimal", @@ -6679,6 +6669,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index e3a980766b..d67d866a40 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -95,8 +95,8 @@ data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" -ferveo = {path = "../../ferveo/ferveo"} #{git = "https://github.com/anoma/ferveo"} -ferveo-common = {path = "../../ferveo/ferveo-common"}#{git = "https://github.com/anoma/ferveo"} +ferveo = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" diff --git a/core/Cargo.toml b/core/Cargo.toml index ba58804d1b..0c63a01c9a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -68,9 +68,9 @@ derivative = "2.2.0" ed25519-consensus = "1.2.0" ethabi = "17.0.0" eyre = "0.6.8" -ferveo = {optional = true, path = "../../ferveo/ferveo"} # git = "https://github.com/anoma/ferveo"} -ferveo-common = {path = "../../ferveo/ferveo-common"} #git = "https://github.com/anoma/ferveo"} -tpke = {package = "group-threshold-cryptography", optional = true, path = "../../ferveo/tpke"}#git = "https://github.com/anoma/ferveo"} +ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} +tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} # TODO using the same version of tendermint-rs as we do here. ibc = {version = "0.14.0", default-features = false, optional = true} ibc-proto = {version = "0.17.1", default-features = false, optional = true} diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 68e0ea0f8d..7ef093b7b8 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -25,7 +25,7 @@ testing = ["proptest"] namada_core = {path = "../core", default-features = false} borsh = "0.9.1" derivative = "2.2.0" -ferveo-common = {path = "../../ferveo/ferveo-common" }#git = "https://github.com/anoma/ferveo"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} rust_decimal = { version = "1.26.1", features = ["borsh"] } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 884212f653..1839e54473 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -95,7 +95,7 @@ clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} data-encoding = "2.3.2" derivative = "2.2.0" eyre = "0.6.8" -ferveo-common = {git = "https://github.com/anoma/ferveo"} +ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "9e5e91c954158e7cff45c483fd06cd649a81553f"} # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index d49b3e3f63..f93f957360 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1472,6 +1472,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -1488,7 +1489,7 @@ dependencies = [ "digest 0.10.5", "ed25519-dalek", "either", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "hex", "itertools", @@ -1508,19 +1509,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -dependencies = [ - "anyhow", - "ark-ec", - "ark-serialize", - "ark-std", - "serde", - "serde_bytes", -] - -[[package]] -name = "ferveo-common" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", @@ -1771,6 +1760,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -2726,7 +2716,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", + "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -2781,7 +2771,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "ibc", "ibc-proto", @@ -2842,7 +2832,7 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", - "ferveo-common 0.1.0", + "ferveo-common", "namada_core", "proptest", "rust_decimal", @@ -4408,6 +4398,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subproductdomain" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 8ddededda7..b3d7db9812 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1472,6 +1472,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -1488,7 +1489,7 @@ dependencies = [ "digest 0.10.5", "ed25519-dalek", "either", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "hex", "itertools", @@ -1508,19 +1509,7 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -dependencies = [ - "anyhow", - "ark-ec", - "ark-serialize", - "ark-std", - "serde", - "serde_bytes", -] - -[[package]] -name = "ferveo-common" -version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", @@ -1771,6 +1760,7 @@ dependencies = [ [[package]] name = "group-threshold-cryptography" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-bls12-381", @@ -2726,7 +2716,7 @@ dependencies = [ "data-encoding", "derivative", "eyre", - "ferveo-common 0.1.0 (git+https://github.com/anoma/ferveo)", + "ferveo-common", "ibc", "ibc-proto", "itertools", @@ -2781,7 +2771,7 @@ dependencies = [ "ethabi", "eyre", "ferveo", - "ferveo-common 0.1.0", + "ferveo-common", "group-threshold-cryptography", "ibc", "ibc-proto", @@ -2842,7 +2832,7 @@ version = "0.11.0" dependencies = [ "borsh", "derivative", - "ferveo-common 0.1.0", + "ferveo-common", "namada_core", "proptest", "rust_decimal", @@ -4400,6 +4390,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subproductdomain" version = "0.1.0" +source = "git+https://github.com/anoma/ferveo?rev=9e5e91c954158e7cff45c483fd06cd649a81553f#9e5e91c954158e7cff45c483fd06cd649a81553f" dependencies = [ "anyhow", "ark-ec", From 1965ad36f227f96b9588a8314bf199208adb4472 Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 19 Dec 2022 14:18:18 +0100 Subject: [PATCH 1972/1995] [fix]: Fixing broken doc links --- proof_of_stake/src/pos_queries.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 182e1f67f0..c2d746b4b9 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -21,7 +21,7 @@ use crate::tendermint_proto::types::EvidenceParams; use crate::types::WeightedValidator; use crate::{PosBase, PosParams}; -/// Errors returned by [`QueriesExt`] operations. +/// Errors returned by [`PosQueries`] operations. #[derive(Error, Debug)] pub enum Error { /// The given address is not among the set of active validators for @@ -50,11 +50,11 @@ pub enum Error { InvalidTMAddress, } -/// Result type returned by [`QueriesExt`] operations. +/// Result type returned by [`PosQueries`] operations. pub type Result = ::std::result::Result; /// This enum is used as a parameter to -/// [`QueriesExt::can_send_validator_set_update`]. +/// [`PosQueries::can_send_validator_set_update`]. pub enum SendValsetUpd { /// Check if it is possible to send a validator set update /// vote extension at the current block height. From 908305bd6a6d72f6e413e60b299ff3fa31281528 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:34:22 +0000 Subject: [PATCH 1973/1995] Re-order tx building method defs in PrepareProposal --- .../lib/node/ledger/shell/prepare_proposal.rs | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index f28fcc2bf5..68492b8e8e 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -110,6 +110,67 @@ where response::PrepareProposal { txs } } + /// Builds a batch of DKG decrypted transactions. + // NOTE: we won't have frontrunning protection until V2 of the + // Anoma protocol; Namada runs V1, therefore this method is + // essentially a NOOP + // + // sources: + // - https://specs.namada.net/main/releases/v2.html + // - https://github.com/anoma/ferveo + fn build_decrypted_txs( + &mut self, + mut alloc: BlockSpaceAllocator, + ) -> (Vec, BlockSpaceAllocator) { + // TODO: This should not be hardcoded + let privkey = + ::G2Affine::prime_subgroup_generator(); + + let 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() + }) + // TODO: make sure all decrypted txs are accepted + .take_while(|tx_bytes| { + alloc.try_alloc(&tx_bytes[..]).map_or_else( + |status| match status { + AllocFailure::Rejected { bin_space_left } => { + tracing::warn!( + ?tx_bytes, + bin_space_left, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping decrypted tx from the current proposal", + ); + false + } + AllocFailure::OverflowsBin { bin_size } => { + tracing::warn!( + ?tx_bytes, + bin_size, + proposal_height = + ?self.storage.get_current_decision_height(), + "Dropping large decrypted tx from the current proposal", + ); + true + } + }, + |()| true, + ) + }) + .collect(); + let alloc = alloc.next_state(); + + (txs, alloc) + } + /// Builds a batch of vote extension transactions, comprised of Ethereum /// events and, optionally, a validator set update. #[cfg(feature = "abcipp")] @@ -320,67 +381,6 @@ where (txs, alloc) } - /// Builds a batch of DKG decrypted transactions. - // NOTE: we won't have frontrunning protection until V2 of the - // Anoma protocol; Namada runs V1, therefore this method is - // essentially a NOOP - // - // sources: - // - https://specs.namada.net/main/releases/v2.html - // - https://github.com/anoma/ferveo - fn build_decrypted_txs( - &mut self, - mut alloc: BlockSpaceAllocator, - ) -> (Vec, BlockSpaceAllocator) { - // TODO: This should not be hardcoded - let privkey = - ::G2Affine::prime_subgroup_generator(); - - let 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() - }) - // TODO: make sure all decrypted txs are accepted - .take_while(|tx_bytes| { - alloc.try_alloc(&tx_bytes[..]).map_or_else( - |status| match status { - AllocFailure::Rejected { bin_space_left } => { - tracing::warn!( - ?tx_bytes, - bin_space_left, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping decrypted tx from the current proposal", - ); - false - } - AllocFailure::OverflowsBin { bin_size } => { - tracing::warn!( - ?tx_bytes, - bin_size, - proposal_height = - ?self.storage.get_current_decision_height(), - "Dropping large decrypted tx from the current proposal", - ); - true - } - }, - |()| true, - ) - }) - .collect(); - let alloc = alloc.next_state(); - - (txs, alloc) - } - /// Builds a batch of transactions that can fit in the /// remaining space of the [`BlockSpaceAllocator`]. #[cfg(feature = "abcipp")] From 93fcdda2976ac342b6a7448ebe9e787a9c3d0d14 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:37:09 +0000 Subject: [PATCH 1974/1995] Add missing tx error code in ProcessProposal docstr --- apps/src/lib/node/ledger/shell/process_proposal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 39a27171f1..9cbb7565b8 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -350,6 +350,7 @@ where /// 5. More decrypted txs than expected /// 6. A transaction could not be decrypted /// 7. An error in the vote extensions included in the proposal + /// 8. Not enough block space was available for some tx /// /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the From b5c791a22ef4ddc39d38e96a3d5270db5f919a7e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:45:00 +0000 Subject: [PATCH 1975/1995] Replace B with byte for better readability --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index cac7e201bf..68084325e8 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -10,7 +10,7 @@ of Namada. [Block sizes in Tendermint] (configured through the $MaxBytes$ consensus -parameter) have a minimum value of $1\ B$, and a hard cap of $100\ +parameter) have a minimum value of 1 byte, and a hard cap of $100\ MiB$, reflecting header, evidence of misbehavior (used to slash Byzantine validators) and transaction data, as well as any potential protobuf serialization overhead. Some of these datum are dynamic in nature (e.g. From f5644bb02c61f8879fb46e2c4950b180fb4cd910 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:45:28 +0000 Subject: [PATCH 1976/1995] Update documentation/specs/src/base-ledger/block-space-allocator.md Co-authored-by: Jacob Turner --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 68084325e8..27dbc167ec 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -11,7 +11,7 @@ of Namada. [Block sizes in Tendermint] (configured through the $MaxBytes$ consensus parameter) have a minimum value of 1 byte, and a hard cap of $100\ -MiB$, reflecting header, evidence of misbehavior (used to slash +MiB$, reflecting the header, evidence of misbehavior (used to slash Byzantine validators) and transaction data, as well as any potential protobuf serialization overhead. Some of these datum are dynamic in nature (e.g. evidence of misbehavior), so the total size reserved to transactions in a block From 591ee746b3e25e0b28b06041a83d18289c1973c2 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:47:19 +0000 Subject: [PATCH 1977/1995] Fix invalid usage of singular word --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 27dbc167ec..3692bfc474 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -13,7 +13,7 @@ of Namada. parameter) have a minimum value of 1 byte, and a hard cap of $100\ MiB$, reflecting the header, evidence of misbehavior (used to slash Byzantine validators) and transaction data, as well as any potential protobuf -serialization overhead. Some of these datum are dynamic in nature (e.g. +serialization overhead. Some of these data are dynamic in nature (e.g. evidence of misbehavior), so the total size reserved to transactions in a block at some height $H_0$ might not be the same as another block's, say, at some height $H_1 : H_1 \ne H_0$. During Tendermint's `PrepareProposal` ABCI phase, From 71d43785d5aae3226410f9a8837f5f72159a7f1a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 09:48:26 +0000 Subject: [PATCH 1978/1995] Update documentation/specs/src/base-ledger/block-space-allocator.md Co-authored-by: Jacob Turner --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 3692bfc474..ea15ee4f10 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -17,7 +17,7 @@ serialization overhead. Some of these data are dynamic in nature (e.g. evidence of misbehavior), so the total size reserved to transactions in a block at some height $H_0$ might not be the same as another block's, say, at some height $H_1 : H_1 \ne H_0$. During Tendermint's `PrepareProposal` ABCI phase, -applications receive a $MaxTxBytes$ parameter, whose value already accounts for +applications receive a $MaxTxBytes$ parameter whose value already accounts for the total space available for transactions at some height $H$. Namada does not rely on the $MaxTxBytes$ parameter of `RequestPrepareProposal`; instead, app-side validators configure a $MaxProposalSize$ parameter at genesis (whose From 5a8a3ca710cc721b0349f8d3cbc5c1916445ed84 Mon Sep 17 00:00:00 2001 From: satan Date: Tue, 20 Dec 2022 10:50:38 +0100 Subject: [PATCH 1979/1995] [feat]: Removed unnecessary ledger subdirectory of the ethereum bridge sub-crate --- ethereum_bridge/Cargo.toml | 2 -- .../src/{ledger => }/bridge_pool_vp.rs | 0 ethereum_bridge/src/ledger/mod.rs | 6 ------ ethereum_bridge/src/lib.rs | 21 +++++-------------- .../src/{ledger => }/parameters.rs | 4 ++-- .../src/{ledger => }/protocol/mod.rs | 0 .../transactions/ethereum_events/eth_msgs.rs | 2 +- .../transactions/ethereum_events/events.rs | 2 +- .../transactions/ethereum_events/mod.rs | 12 +++++------ .../{ledger => }/protocol/transactions/mod.rs | 0 .../protocol/transactions/read.rs | 2 +- .../protocol/transactions/update.rs | 0 .../protocol/transactions/utils.rs | 0 .../transactions/validator_set_update/mod.rs | 8 +++---- .../protocol/transactions/votes.rs | 0 .../protocol/transactions/votes/storage.rs | 2 +- .../protocol/transactions/votes/update.rs | 8 ++++--- .../src/{ledger => }/storage/mod.rs | 0 .../src/{ledger => }/storage/vote_tallies.rs | 0 ethereum_bridge/src/{ledger => }/vp.rs | 0 shared/src/ledger/eth_bridge.rs | 2 +- .../ethereum_bridge/bridge_pool_vp.rs | 6 +++--- .../ledger/native_vp/ethereum_bridge/vp.rs | 2 +- shared/src/ledger/protocol/mod.rs | 2 +- 24 files changed, 32 insertions(+), 49 deletions(-) rename ethereum_bridge/src/{ledger => }/bridge_pool_vp.rs (100%) delete mode 100644 ethereum_bridge/src/ledger/mod.rs rename ethereum_bridge/src/{ledger => }/parameters.rs (98%) rename ethereum_bridge/src/{ledger => }/protocol/mod.rs (100%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/ethereum_events/eth_msgs.rs (97%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/ethereum_events/events.rs (99%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/ethereum_events/mod.rs (97%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/mod.rs (100%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/read.rs (98%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/update.rs (100%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/utils.rs (100%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/validator_set_update/mod.rs (95%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/votes.rs (100%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/votes/storage.rs (98%) rename ethereum_bridge/src/{ledger => }/protocol/transactions/votes/update.rs (98%) rename ethereum_bridge/src/{ledger => }/storage/mod.rs (100%) rename ethereum_bridge/src/{ledger => }/storage/vote_tallies.rs (100%) rename ethereum_bridge/src/{ledger => }/vp.rs (100%) diff --git a/ethereum_bridge/Cargo.toml b/ethereum_bridge/Cargo.toml index 9ab2883e75..51acdff834 100644 --- a/ethereum_bridge/Cargo.toml +++ b/ethereum_bridge/Cargo.toml @@ -6,8 +6,6 @@ name = "namada_ethereum_bridge" resolver = "2" version = "0.11.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [features] default = ["abciplus"] diff --git a/ethereum_bridge/src/ledger/bridge_pool_vp.rs b/ethereum_bridge/src/bridge_pool_vp.rs similarity index 100% rename from ethereum_bridge/src/ledger/bridge_pool_vp.rs rename to ethereum_bridge/src/bridge_pool_vp.rs diff --git a/ethereum_bridge/src/ledger/mod.rs b/ethereum_bridge/src/ledger/mod.rs deleted file mode 100644 index a5d108592b..0000000000 --- a/ethereum_bridge/src/ledger/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Validity predicate and storage keys for the Ethereum bridge account -pub mod bridge_pool_vp; -pub mod parameters; -pub mod protocol; -pub mod storage; -pub mod vp; diff --git a/ethereum_bridge/src/lib.rs b/ethereum_bridge/src/lib.rs index 22a6f96d23..8a1e04d02b 100644 --- a/ethereum_bridge/src/lib.rs +++ b/ethereum_bridge/src/lib.rs @@ -1,16 +1,5 @@ -pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; -pub use namada_core::types::{ - address, chain, eth_abi, eth_bridge_pool, ethereum_events, governance, - hash, internal, keccak, masp, storage, time, token, transaction, - validity_predicate, vote_extensions, voting_power, -}; -#[cfg(not(feature = "abcipp"))] -pub use {tendermint, tendermint_proto, tendermint_rpc}; -#[cfg(feature = "abcipp")] -pub use { - tendermint_abcipp as tendermint, - tendermint_proto_abcipp as tendermint_proto, - tendermint_rpc_abcipp as tendermint_rpc, -}; - -pub mod ledger; +pub mod bridge_pool_vp; +pub mod parameters; +pub mod protocol; +pub mod storage; +pub mod vp; diff --git a/ethereum_bridge/src/ledger/parameters.rs b/ethereum_bridge/src/parameters.rs similarity index 98% rename from ethereum_bridge/src/ledger/parameters.rs rename to ethereum_bridge/src/parameters.rs index d619c4f1a0..30ae43a37f 100644 --- a/ethereum_bridge/src/ledger/parameters.rs +++ b/ethereum_bridge/src/parameters.rs @@ -8,7 +8,7 @@ use namada_core::ledger::storage::Storage; use namada_core::types::ethereum_events::EthAddress; use serde::{Deserialize, Serialize}; -use crate::ledger::{bridge_pool_vp, storage as bridge_storage, vp}; +use crate::{bridge_pool_vp, storage as bridge_storage, vp}; /// Represents a configuration value for the minimum number of /// confirmations an Ethereum event must reach before it can be acted on. @@ -164,7 +164,7 @@ mod tests { use eyre::Result; use namada_core::types::ethereum_events::EthAddress; - use crate::ledger::parameters::{ + use crate::parameters::{ ContractVersion, Contracts, EthereumBridgeConfig, MinimumConfirmations, UpgradeableContract, }; diff --git a/ethereum_bridge/src/ledger/protocol/mod.rs b/ethereum_bridge/src/protocol/mod.rs similarity index 100% rename from ethereum_bridge/src/ledger/protocol/mod.rs rename to ethereum_bridge/src/protocol/mod.rs diff --git a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs similarity index 97% rename from ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs rename to ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs index 785395860c..df9b9740ee 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/eth_msgs.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/eth_msgs.rs @@ -2,7 +2,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::types::ethereum_events::EthereumEvent; use namada_core::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use crate::ledger::protocol::transactions::votes::{dedupe, Tally, Votes}; +use crate::protocol::transactions::votes::{dedupe, Tally, Votes}; /// Represents an Ethereum event being seen by some validators #[derive( diff --git a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs similarity index 99% rename from ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs rename to ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index 90def86686..9b2f9a460b 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -9,7 +9,7 @@ use namada_core::ledger::storage::{DBIter, Storage, DB}; use namada_core::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada_core::types::storage::Key; -use crate::ledger::protocol::transactions::update; +use crate::protocol::transactions::update; /// Updates storage based on the given confirmed `event`. For example, for a /// confirmed [`EthereumEvent::TransfersToNamada`], mint the corresponding diff --git a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs similarity index 97% rename from ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs rename to ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index 91c53d4c1c..706dfdbd8c 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -16,10 +16,10 @@ use namada_core::types::vote_extensions::ethereum_events::MultiSignedEthEvent; use namada_core::types::voting_power::FractionalVotingPower; use super::ChangedKeys; -use crate::ledger::protocol::transactions::utils::{self}; -use crate::ledger::protocol::transactions::votes::update::NewVotes; -use crate::ledger::protocol::transactions::votes::{self, calculate_new}; -use crate::ledger::storage::vote_tallies; +use crate::protocol::transactions::utils; +use crate::protocol::transactions::votes::update::NewVotes; +use crate::protocol::transactions::votes::{self, calculate_new}; +use crate::storage::vote_tallies; impl utils::GetVoters for HashSet { #[inline] @@ -191,8 +191,8 @@ mod tests { use namada_proof_of_stake::PosBase; use super::*; - use crate::ledger::protocol::transactions::utils::GetVoters; - use crate::ledger::protocol::transactions::votes::Votes; + use crate::protocol::transactions::utils::GetVoters; + use crate::protocol::transactions::votes::Votes; #[test] /// Test applying a `TransfersToNamada` batch containing a single transfer diff --git a/ethereum_bridge/src/ledger/protocol/transactions/mod.rs b/ethereum_bridge/src/protocol/transactions/mod.rs similarity index 100% rename from ethereum_bridge/src/ledger/protocol/transactions/mod.rs rename to ethereum_bridge/src/protocol/transactions/mod.rs diff --git a/ethereum_bridge/src/ledger/protocol/transactions/read.rs b/ethereum_bridge/src/protocol/transactions/read.rs similarity index 98% rename from ethereum_bridge/src/ledger/protocol/transactions/read.rs rename to ethereum_bridge/src/protocol/transactions/read.rs index 4732a503f2..1562045873 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/read.rs +++ b/ethereum_bridge/src/protocol/transactions/read.rs @@ -58,7 +58,7 @@ mod tests { use namada_core::types::storage; use namada_core::types::token::Amount; - use crate::ledger::protocol::transactions::read; + use crate::protocol::transactions::read; #[test] fn test_amount_returns_zero_for_uninitialized_storage() { diff --git a/ethereum_bridge/src/ledger/protocol/transactions/update.rs b/ethereum_bridge/src/protocol/transactions/update.rs similarity index 100% rename from ethereum_bridge/src/ledger/protocol/transactions/update.rs rename to ethereum_bridge/src/protocol/transactions/update.rs diff --git a/ethereum_bridge/src/ledger/protocol/transactions/utils.rs b/ethereum_bridge/src/protocol/transactions/utils.rs similarity index 100% rename from ethereum_bridge/src/ledger/protocol/transactions/utils.rs rename to ethereum_bridge/src/protocol/transactions/utils.rs diff --git a/ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs b/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs similarity index 95% rename from ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs rename to ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs index fcc566798f..b92c779101 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/validator_set_update/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs @@ -14,10 +14,10 @@ use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::pos_queries::PosQueries; use super::ChangedKeys; -use crate::ledger::protocol::transactions::utils; -use crate::ledger::protocol::transactions::votes::update::NewVotes; -use crate::ledger::protocol::transactions::votes::{self, Votes}; -use crate::ledger::storage::vote_tallies; +use crate::protocol::transactions::utils; +use crate::protocol::transactions::votes::update::NewVotes; +use crate::protocol::transactions::votes::{self, Votes}; +use crate::storage::vote_tallies; impl utils::GetVoters for validator_set_update::VextDigest { #[inline] diff --git a/ethereum_bridge/src/ledger/protocol/transactions/votes.rs b/ethereum_bridge/src/protocol/transactions/votes.rs similarity index 100% rename from ethereum_bridge/src/ledger/protocol/transactions/votes.rs rename to ethereum_bridge/src/protocol/transactions/votes.rs diff --git a/ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs b/ethereum_bridge/src/protocol/transactions/votes/storage.rs similarity index 98% rename from ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs rename to ethereum_bridge/src/protocol/transactions/votes/storage.rs index 102cb9ae05..918b1efe1e 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/votes/storage.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/storage.rs @@ -4,7 +4,7 @@ use namada_core::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use namada_core::types::voting_power::FractionalVotingPower; use super::{Tally, Votes}; -use crate::ledger::storage::vote_tallies; +use crate::storage::vote_tallies; pub fn write( storage: &mut Storage, diff --git a/ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs b/ethereum_bridge/src/protocol/transactions/votes/update.rs similarity index 98% rename from ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs rename to ethereum_bridge/src/protocol/transactions/votes/update.rs index 122ef572fe..1a58f7faee 100644 --- a/ethereum_bridge/src/ledger/protocol/transactions/votes/update.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/update.rs @@ -8,7 +8,7 @@ use namada_core::types::storage::BlockHeight; use namada_core::types::voting_power::FractionalVotingPower; use super::{ChangedKeys, Tally, Votes}; -use crate::ledger::storage::vote_tallies; +use crate::storage::vote_tallies; /// Wraps all the information about new votes to be applied to some existing /// tally in storage. @@ -197,8 +197,10 @@ mod tests { use namada_core::types::ethereum_events::EthereumEvent; use super::*; - use crate::ledger::protocol::transactions::votes; - use crate::ledger::protocol::transactions::votes::update::tests::helpers::{arbitrary_event, setup_tally}; + use crate::protocol::transactions::votes; + use crate::protocol::transactions::votes::update::tests::helpers::{ + arbitrary_event, setup_tally, + }; mod helpers { use super::*; diff --git a/ethereum_bridge/src/ledger/storage/mod.rs b/ethereum_bridge/src/storage/mod.rs similarity index 100% rename from ethereum_bridge/src/ledger/storage/mod.rs rename to ethereum_bridge/src/storage/mod.rs diff --git a/ethereum_bridge/src/ledger/storage/vote_tallies.rs b/ethereum_bridge/src/storage/vote_tallies.rs similarity index 100% rename from ethereum_bridge/src/ledger/storage/vote_tallies.rs rename to ethereum_bridge/src/storage/vote_tallies.rs diff --git a/ethereum_bridge/src/ledger/vp.rs b/ethereum_bridge/src/vp.rs similarity index 100% rename from ethereum_bridge/src/ledger/vp.rs rename to ethereum_bridge/src/vp.rs diff --git a/shared/src/ledger/eth_bridge.rs b/shared/src/ledger/eth_bridge.rs index 942d0bfb9d..1aa9e4d7f7 100644 --- a/shared/src/ledger/eth_bridge.rs +++ b/shared/src/ledger/eth_bridge.rs @@ -1,4 +1,4 @@ //! Re-exporting types from the namada_ethereum_bridge crate. pub use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; pub use namada_core::ledger::eth_bridge::{ADDRESS, INTERNAL_ADDRESS}; -pub use namada_ethereum_bridge::ledger::parameters::*; +pub use namada_ethereum_bridge::parameters::*; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 3df37e3735..3f3d2d2689 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -17,8 +17,8 @@ use eyre::eyre; use namada_core::ledger::eth_bridge::storage::bridge_pool::{ get_pending_key, is_bridge_pool_key, BRIDGE_POOL_ADDRESS, }; -use namada_ethereum_bridge::ledger::storage; -use namada_ethereum_bridge::ledger::storage::wrapped_erc20s; +use namada_ethereum_bridge::storage; +use namada_ethereum_bridge::storage::wrapped_erc20s; use crate::ledger::native_vp::ethereum_bridge::vp::check_balance_changes; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader}; @@ -372,7 +372,7 @@ mod test_bridge_pool_vp { use borsh::{BorshDeserialize, BorshSerialize}; use namada_core::ledger::eth_bridge::storage::bridge_pool::get_signed_root_key; use namada_core::types::address; - use namada_ethereum_bridge::ledger::parameters::{ + use namada_ethereum_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 2510991cb1..e8cb3b7158 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -391,7 +391,7 @@ mod tests { use namada_core::ledger::eth_bridge; use namada_core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada_core::types::address; - use namada_ethereum_bridge::ledger::parameters::{ + use namada_ethereum_bridge::parameters::{ Contracts, EthereumBridgeConfig, UpgradeableContract, }; use rand::Rng; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 900aec7eda..244db4f4b9 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -204,7 +204,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - use namada_ethereum_bridge::ledger::protocol::transactions; + use namada_ethereum_bridge::protocol::transactions; use crate::types::vote_extensions::{ ethereum_events, validator_set_update, From 8ab563044aab7658d3e5f91165c661e032339f74 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 10:08:25 +0000 Subject: [PATCH 1980/1995] Update documentation/specs/src/base-ledger/block-space-allocator.md Co-authored-by: Jacob Turner --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index ea15ee4f10..13ec4b20d9 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -33,7 +33,7 @@ fed a set of transactions $M = \{\ tx\ |\ tx\text{ in Tendermint's mempool}\ \}$, whose total combined size (i.e. the sum of the bytes occupied by each $tx : tx \in M$) may be greater than $MaxProposalBytes$. Therefore, consensus round leaders are responsible for selecting a batch of transactions $P$ whose total -combined bytes $P_{Len} : P_{Len} \le MaxProposalBytes$. +combined bytes $P_{Len} \le MaxProposalBytes$. To stay within these bounds, block space is **allotted** to different kinds of transactions: decrypted, protocol and encrypted transactions. Each kind of From 740ea05e62239b9fe25ccd0e30331b0b63fdcbf7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 10:08:35 +0000 Subject: [PATCH 1981/1995] Update documentation/specs/src/base-ledger/block-space-allocator.md Co-authored-by: Jacob Turner --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 13ec4b20d9..6fddced631 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -139,7 +139,7 @@ appearing in this order, as [examplified above](#example)). Let us fix $H$ as the height of the block $B$ currently being decided through Tendermint's consensus mechanism, $P$ as the batch of transactions proposed at $H$ as $B$'s payload and $V$ as the current set of active validators. To vote on $P$, each -validator $v : v \in V$ checks: +validator $v \in V$ checks: * If the length of $P$ in bytes, defined as $P_{Len} : P_{Len} = \sum_{tx \in P} \text{size\_of}(tx)$, is not greater than $MaxProposalBytes$. From cd896b6ff02a0c4c8f45cd94d86c5240c5f0532a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 10:08:47 +0000 Subject: [PATCH 1982/1995] Update documentation/specs/src/base-ledger/block-space-allocator.md Co-authored-by: Jacob Turner --- documentation/specs/src/base-ledger/block-space-allocator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 6fddced631..18fe584668 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -141,7 +141,7 @@ consensus mechanism, $P$ as the batch of transactions proposed at $H$ as $B$'s payload and $V$ as the current set of active validators. To vote on $P$, each validator $v \in V$ checks: -* If the length of $P$ in bytes, defined as $P_{Len} : P_{Len} = \sum_{tx \in +* If the length of $P$ in bytes, defined as $P_{Len} := \sum_{tx \in P} \text{size\_of}(tx)$, is not greater than $MaxProposalBytes$. * If $P$ does not contain more than $\frac{1}{3} MaxProposalBytes$ worth of encrypted transactions. From 1c63c49dcc2888f8bc55fef392d3a78d69db977c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 10:07:58 +0000 Subject: [PATCH 1983/1995] Add governance specs for max_proposal_bytes updates --- .../specs/src/base-ledger/block-space-allocator.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 18fe584668..407e8d2df9 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -10,7 +10,7 @@ of Namada. [Block sizes in Tendermint] (configured through the $MaxBytes$ consensus -parameter) have a minimum value of 1 byte, and a hard cap of $100\ +parameter) have a minimum value of $1\ \text{byte}$, and a hard cap of $100\ MiB$, reflecting the header, evidence of misbehavior (used to slash Byzantine validators) and transaction data, as well as any potential protobuf serialization overhead. Some of these data are dynamic in nature (e.g. @@ -20,9 +20,11 @@ height $H_1 : H_1 \ne H_0$. During Tendermint's `PrepareProposal` ABCI phase, applications receive a $MaxTxBytes$ parameter whose value already accounts for the total space available for transactions at some height $H$. Namada does not rely on the $MaxTxBytes$ parameter of `RequestPrepareProposal`; instead, -app-side validators configure a $MaxProposalSize$ parameter at genesis (whose -value is static throughout the course of the chain) and set Tendermint blocks' -$MaxBytes$ parameter to its upper bound. +app-side validators configure a $MaxProposalSize$ parameter at genesis (or +through governance) and set Tendermint blocks' $MaxBytes$ parameter to its upper bound. +Governance parameter update proposals for $MaxProposalBytes_H$, where $H$ is some +arbitrary block height, should be such that $MaxProposalBytes_H \ge \frac{1}{3} MaxProposalBytes_{H-1}$, +to leave enough room for decrypted transactions from $H-1$ at $H$. [Block sizes in Tendermint]: From 4d3e6e0f0f221b9523e124f1588a80b85c58ba6b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 10:53:49 +0000 Subject: [PATCH 1984/1995] Explain how the fourth state of the block space allocator works --- .../specs/src/base-ledger/block-space-allocator.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 407e8d2df9..f86254f6d3 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -88,8 +88,14 @@ block space remaining after allocating space for decrypted and protocol transactions. 4. `FillingRemainingSpace` - The final state of the `BlockSpaceAllocator`. Due to the short-circuit behavior of a `TxBin`, on allocation errors, some space -may be left unutilized at the end of the third state. This state allows -protocol transactions to be included in the block's remaining space. +may be left unutilized at the end of the third state. At this state, the only kinds of +transactions that are left to fill the available block space are +of type encrypted and protocol, but encrypted transactions are forbidden +to be included, to avoid breaking their invariant regarding +allotted block space (i.e. encrypted transactions can only occupy up to +$\frac{1}{3}$ of the total block space for a given height $H$). As such, +only protocol transactions are allowed at the fourth and final state of +the `BlockSpaceAllocator`. For a fixed block height $H_0$, if at $H_0 - 1$ and $H_0$ no encrypted transactions are included in the respective proposals, the block decided for @@ -157,7 +163,7 @@ $P$, for height $H$. Should any of these conditions not be met at some arbitrary round $R$ of $H$, all honest validators $V_h : V_h \subseteq V$ will reject the proposal $P$. Byzantine validators are permitted to re-order the layout of $P$ typically -derived from the [block space allocator](#transaction-batch-construction) $A$, +derived from the [`BlockSpaceAllocator`](#transaction-batch-construction) $A$, under normal operation, however this should not be a compromising factor of the safety and liveness properties of Namada. The rigid layout of $B$ is simply a consequence of $A$ allocating in different phases. From 2fda882cc2d9a29c36e5e074adfa9465dc463bb9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 20 Dec 2022 11:06:16 +0000 Subject: [PATCH 1985/1995] Move governance specs to a new sub-section --- .../src/base-ledger/block-space-allocator.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index f86254f6d3..22bb5c9648 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -21,10 +21,8 @@ applications receive a $MaxTxBytes$ parameter whose value already accounts for the total space available for transactions at some height $H$. Namada does not rely on the $MaxTxBytes$ parameter of `RequestPrepareProposal`; instead, app-side validators configure a $MaxProposalSize$ parameter at genesis (or -through governance) and set Tendermint blocks' $MaxBytes$ parameter to its upper bound. -Governance parameter update proposals for $MaxProposalBytes_H$, where $H$ is some -arbitrary block height, should be such that $MaxProposalBytes_H \ge \frac{1}{3} MaxProposalBytes_{H-1}$, -to leave enough room for decrypted transactions from $H-1$ at $H$. +through governance) and set Tendermint blocks' $MaxBytes$ parameter to its +upper bound. [Block sizes in Tendermint]: @@ -88,7 +86,8 @@ block space remaining after allocating space for decrypted and protocol transactions. 4. `FillingRemainingSpace` - The final state of the `BlockSpaceAllocator`. Due to the short-circuit behavior of a `TxBin`, on allocation errors, some space -may be left unutilized at the end of the third state. At this state, the only kinds of +may be left unutilized at the end of the third state. At this state, the only +kinds of transactions that are left to fill the available block space are of type encrypted and protocol, but encrypted transactions are forbidden to be included, to avoid breaking their invariant regarding @@ -191,3 +190,12 @@ constructed. We cover validator set updates in detail in [the Ethereum bridge section]. [the Ethereum bridge section]: ../interoperability/ethereum-bridge.md + +## Governance + +Governance parameter update proposals for $MaxProposalBytes_H$ that take effect +at $H$, where $H$ is some arbitrary block height, should be such that +$MaxProposalBytes_H \ge \frac{1}{3} MaxProposalBytes_{H-1}$, to leave enough +room for all decrypted transactions from $H-1$ at $H$. Subsequent block heights +$H' : H' > H$ should eventually lead to allotted block space converging to about +$\frac{1}{3} MaxProposalBytes_H$ for each kind of transaction type. From 459dbbb7cfa556099864b5c7c28dee5e778b4dbf Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 21 Dec 2022 00:56:25 -0500 Subject: [PATCH 1986/1995] vp_verify_masp: avoid panicking unwrap()s Malformed transactions can cause the node to panic because this function unwraps values. Return the proper error, or a failure if asked to verify a transaction with no shielded part. --- shared/src/vm/host_env.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 90e6e4405f..655774080f 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1822,22 +1822,26 @@ where EVAL: VpEvaluator, CA: WasmCacheAccess, { - use masp_primitives::transaction::Transaction; - use crate::types::token::Transfer; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; let (tx_bytes, gas) = env .memory .read_bytes(tx_ptr, tx_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; vp_host_fns::add_gas(gas_meter, gas)?; + let full_tx: Transfer = - BorshDeserialize::try_from_slice(tx_bytes.as_slice()).unwrap(); - let shielded_tx: Transaction = full_tx.shielded.unwrap(); - Ok(HostEnvResult::from(crate::ledger::masp::verify_shielded_tx( - &shielded_tx, - )) - .to_i64()) + BorshDeserialize::try_from_slice(tx_bytes.as_slice()) + .map_err(vp_host_fns::RuntimeError::EncodingError)?; + + match full_tx.shielded { + Some(shielded_tx) => Ok(HostEnvResult::from( + crate::ledger::masp::verify_shielded_tx(&shielded_tx), + ) + .to_i64()), + None => Ok(HostEnvResult::Fail.to_i64()), + } } /// Log a string from exposed to the wasm VM Tx environment. The message will be From 97934a7042c5dc8119154fb4a5a8ffe5edb2d6c0 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 21 Dec 2022 00:59:31 -0500 Subject: [PATCH 1987/1995] changelog: add #942 --- .changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md diff --git a/.changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md b/.changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md new file mode 100644 index 0000000000..4c3cfdee3c --- /dev/null +++ b/.changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md @@ -0,0 +1,2 @@ +- Avoid panicking unwrap()s in vp_verify_masp, to prevent crashing the node on + malformed transactions. ([#942](https://github.com/anoma/namada/pull/942)) \ No newline at end of file From 6920cbcb5b21eadf65aadd21b943247250c47abd Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 21 Dec 2022 01:01:11 -0500 Subject: [PATCH 1988/1995] Namada 0.12.1 --- .../bug-fixes/942-vp-verify-masp-failure.md | 0 .changelog/v0.12.1/summary.md | 2 ++ CHANGELOG.md | 10 ++++++ Cargo.lock | 20 +++++------ apps/Cargo.toml | 2 +- core/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/Cargo.lock | 22 ++++++------ wasm/checksums.json | 34 +++++++++--------- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/tx_mint_tokens.wasm | Bin 352858 -> 352849 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 201332 -> 203872 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152564 -> 152560 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 226795 -> 226642 bytes wasm_for_tests/vp_always_false.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_always_true.wasm | Bin 156489 -> 156489 bytes wasm_for_tests/vp_eval.wasm | Bin 157426 -> 157426 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 158937 -> 158937 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170817 -> 168265 bytes wasm_for_tests/wasm_source/Cargo.lock | 18 +++++----- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 30 files changed, 73 insertions(+), 61 deletions(-) rename .changelog/{unreleased => v0.12.1}/bug-fixes/942-vp-verify-masp-failure.md (100%) create mode 100644 .changelog/v0.12.1/summary.md diff --git a/.changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md b/.changelog/v0.12.1/bug-fixes/942-vp-verify-masp-failure.md similarity index 100% rename from .changelog/unreleased/bug-fixes/942-vp-verify-masp-failure.md rename to .changelog/v0.12.1/bug-fixes/942-vp-verify-masp-failure.md diff --git a/.changelog/v0.12.1/summary.md b/.changelog/v0.12.1/summary.md new file mode 100644 index 0000000000..330b094aec --- /dev/null +++ b/.changelog/v0.12.1/summary.md @@ -0,0 +1,2 @@ +Namada 0.12.1 is a hotfix release, fixing a node crash on malformed +transactions to the MASP. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aaab24c50..c43936dd31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # CHANGELOG +## v0.12.1 + +Namada 0.12.1 is a hotfix release, fixing a node crash on malformed +transactions to the MASP. + +### BUG FIXES + +- Avoid panicking unwrap()s in vp_verify_masp, to prevent crashing the node on + malformed transactions. ([#942](https://github.com/anoma/namada/pull/942)) + ## v0.12.0 Namada 0.12.0 is a scheduled minor release. diff --git a/Cargo.lock b/Cargo.lock index 1a0a9ae50b..569ef32e37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3635,7 +3635,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.12.0" +version = "0.12.1" dependencies = [ "assert_matches", "async-trait", @@ -3692,7 +3692,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.12.0" +version = "0.12.1" dependencies = [ "ark-serialize", "ark-std", @@ -3778,7 +3778,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.12.0" +version = "0.12.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3830,7 +3830,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "itertools", @@ -3841,7 +3841,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.12.0" +version = "0.12.1" dependencies = [ "quote", "syn", @@ -3849,7 +3849,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "derivative", @@ -3863,7 +3863,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.12.0" +version = "0.12.1" dependencies = [ "assert_cmd", "borsh", @@ -3907,7 +3907,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "masp_primitives", @@ -3922,7 +3922,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "hex", @@ -3933,7 +3933,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "namada_core", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index bba5ca030e..7bbe802b58 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.12.0" +version = "0.12.1" default-run = "namada" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/core/Cargo.toml b/core/Cargo.toml index 7754310669..5f5381b38f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_core" resolver = "2" -version = "0.12.0" +version = "0.12.1" [features] default = [] diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index 2fc6c2cc18..a493ea3dc9 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.12.0" +version = "0.12.1" [features] default = ["abciplus"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 4bb96bbe49..ab2dea5b9e 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.12.0" +version = "0.12.1" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 822ef2fabb..a7b1417608 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.12.0" +version = "0.12.1" [features] default = ["abciplus"] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index efcd0e81a0..0683648271 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.12.0" +version = "0.12.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 4403453dc9..fd8c123bb5 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.12.0" +version = "0.12.1" [features] default = ["abciplus", "wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 945f14ad7b..ae0e303939 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.12.0" +version = "0.12.1" [features] default = ["abciplus"] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index cf4e6a7d9f..df7f726863 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.12.0" +version = "0.12.1" [features] default = ["abciplus"] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index a0d76feadc..3489f62172 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.12.0" +version = "0.12.1" [features] default = ["abciplus"] diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index bd82ea3be8..52b7029972 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -2456,7 +2456,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.12.0" +version = "0.12.1" dependencies = [ "async-trait", "bellman", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.12.0" +version = "0.12.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2541,7 +2541,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.12.0" +version = "0.12.1" dependencies = [ "quote", "syn", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "derivative", @@ -2563,7 +2563,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.12.0" +version = "0.12.1" dependencies = [ "chrono", "concat-idents", @@ -2592,7 +2592,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "masp_primitives", @@ -2607,7 +2607,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "hex", @@ -2618,7 +2618,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "namada_core", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4601,7 +4601,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "getrandom 0.2.8", @@ -4738,7 +4738,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm/checksums.json b/wasm/checksums.json index b13508300c..bf6efb760c 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.be9c75f96b3b4880b7934d42ee218582b6304f6326a4588d1e6ac1ea4cc61c49.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.cd861e0e82f4934be6d8382d6fff98286b4fadbc20ab826b9e817f6666021273.wasm", - "tx_ibc.wasm": "tx_ibc.13daeb0c88abba264d3052129eda0713bcf1a71f6f69bf37ec2494d0d9119f1f.wasm", - "tx_init_account.wasm": "tx_init_account.e21cfd7e96802f8e841613fb89f1571451401d002a159c5e9586855ac1374df5.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b9a77bc9e416f33f1e715f25696ae41582e1b379422f7a643549884e0c73e9de.wasm", - "tx_init_validator.wasm": "tx_init_validator.1e9732873861c625f239e74245f8c504a57359c06614ba40387a71811ca4a097.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.47bc922a8be5571620a647ae442a1af7d03d05d29bef95f0b32cdfe00b11fee9.wasm", - "tx_transfer.wasm": "tx_transfer.bbd1ef5d9461c78f0288986de046baad77e10671addc5edaf3c68ea1ae4ecc99.wasm", - "tx_unbond.wasm": "tx_unbond.c0a690d0ad43a94294a6405bae3327f638a657446c74dc61dbb3a4d2ce488b5e.wasm", + "tx_bond.wasm": "tx_bond.c80510dae9aa8785a33301a16747b358da20cb125edae01e330eba2153ca28f4.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.655b344c2d9cc602f9250cf390112ff2c9293537cdc1eb4a52c58ee622ccb538.wasm", + "tx_ibc.wasm": "tx_ibc.de66b4cc33061f503c63964debda8061ab9e0f1294e6c5c510d4b94a409a2edf.wasm", + "tx_init_account.wasm": "tx_init_account.87940d07eac068e03cfe5cd8b491032b3f1814d36060cbc761beb528f1c27b46.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2464b3de1c3aa4ed25cf592366df3b744ec3d2030ce952ea18f38066d4091693.wasm", + "tx_init_validator.wasm": "tx_init_validator.6a27ed0de01ab555628129ac9f05801d5ac812824633bd36e43b1d67b6360db6.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d22e9b3310bad29fea038b42b4120f12b11ffe0ec4012ddf2d709aed490cac97.wasm", + "tx_transfer.wasm": "tx_transfer.3f6d8d0c103bd631c7cd12b58663e026aa9743f8552b14919a1b5bcd9fd31741.wasm", + "tx_unbond.wasm": "tx_unbond.6b801584c4d01f7b52988e5ecf971050e1575f1e15f818f3e2694604b402ebd8.wasm", "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.263fd9f4cb40f283756f394d86bdea3417e9ecd0568d6582c07a5b6bd14287d6.wasm", - "tx_withdraw.wasm": "tx_withdraw.6ce8faf6a32340178ddeaeb91a9b40e7f0433334e5c1f357964bf8e11d0077f1.wasm", - "vp_implicit.wasm": "vp_implicit.17f5c2af947ccfadce22d0fffecde1a1b4bc4ca3acd5dd8b459c3dce4afcb4e8.wasm", - "vp_masp.wasm": "vp_masp.5620cb6e555161641337d308851c760fbab4f9d3693cfd378703aa55e285249d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.362584b063cc4aaf8b72af0ed8af8d05a179ebefec596b6ab65e0ca255ec3c80.wasm", - "vp_token.wasm": "vp_token.a289723dd182fe0206e6c4cf1f426a6100787b20e2653d2fad6031e8106157f3.wasm", - "vp_user.wasm": "vp_user.b83b2d0616bb2244c8a92021665a0be749282a53fe1c493e98c330a6ed983833.wasm", - "vp_validator.wasm": "vp_validator.59e3e7729e14eeacc17d76b736d1760d59a1a6e9d6acbc9a870e1835438f524a.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.37269505f1526de9680b619413478a071ed2f92b22924e6fbb3b7127f88da41a.wasm", + "tx_withdraw.wasm": "tx_withdraw.e5affdbd26cdd08aff6dfc37c5b3d92a8784547e28d039b863a6a95d5ac1cc97.wasm", + "vp_implicit.wasm": "vp_implicit.2f0b200b9e0f3db5151e0b86f5e66ae1b7e43e01d8cdc852db1337c744fac4b6.wasm", + "vp_masp.wasm": "vp_masp.da15ab8670750f400fc12616921dc3c959386bdb5d2df4f455f2299847c257ee.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.13f1911794bc69e4d7dcebd30b048740398b5d45f7efee95a07a627b1a46fa47.wasm", + "vp_token.wasm": "vp_token.497a346ebcc1b7e7b844a8aab644a8d43ab9399006a95c7ca411035b8966b05e.wasm", + "vp_user.wasm": "vp_user.59c397bd2356d01cea5379de64882423cf1e2bb361301651573c1e3619d43551.wasm", + "vp_validator.wasm": "vp_validator.0b13e2f48407640ca35414c5b855f3e20f3393f60ff987aaa3b0febbb0d64e51.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 345ec86237..708225ef63 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.12.0" +version = "0.12.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 07ae7758db..31cb9374b0 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.12.0" +version = "0.12.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 36731a505b..0e57b8aec1 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.12.0" +version = "0.12.1" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 72e1e8f075a1bc7c0f7061848deb59e2bbb37dd9..c5bdd46d33bd84919b65a81c87c723ecb8a5ea79 100755 GIT binary patch delta 7985 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 delta 8033 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 diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 584ebfdcb192dc64c52f445596312cd0127c8d51..a8e7a81527f8c5135a439d61cb53e7660571ef9c 100755 GIT binary patch delta 30762 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 delta 28243 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 OD4I{K&2dVR=Kllp&}~Qn diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 8f62c8211e4f1d4561e7e8aac1b45c676b4a8bb0..1b5b368833822bec0478e5d20d7974aaa235a550 100755 GIT binary patch delta 3122 zcmbVOYfMx}6rMB7UV&ZU!h+&1%7R$kpyHzwrVxiKgCy~wZ77xxr^)IuL@yj<~!%i`R1IN zIddO*BOZ7od=-S+=cF07>Ep&vm^@`_&WF>m2`<5Q zy_EL=C_#^D7;MKMO#6m3o$#{^#zr&dG-;|^t0(MYY?Oh+-P$P`AIGw=YZ1jmwC7J>@3~ofh*f4A<|R0=BsLm~^!>3}0F~Gp zcR^mE`Hiz<_>s~CLf2$4}C!#l@bwDwNsikXm zMSiBL%%a9Ji`L};b%@i{tRL*zUu>g0(S;9t4W;CzCQdf+JYMa^8|DQ3HPHj-aaHft zN!w5P*+9-Rxs{J~GAGbcw7NCFPLA3!%)WA1a7b94n_{wP!rsXTa%(1MR-x@r3S)EnZWj82>Rdf(fb zTWdEiiT+huzcA=Prz?e*9Kn`%lMq~e;SX7`!1ckbnr}kYfC;M8GNx+LAmc>0c0tBY zw{}&={oPtC-gDVEZIPwqb%%_}YnhD6>pmHi*Ml-9uig;0Mp(4L8mLMtn;4}!nbvls zYXH{>Z|baXOeFKRY(QLhWlUU;WlUVpLf9HEam5fzJtF+=8<>8;2cF&tWT z&xkY*r}5m#e&EBKBh#QsS28_dJ5SGomQE#0)RB&(1Oiw)YC``GT8|pnSy0QXN#0L8 zMQTxGwO1JJS=r#nxmlB`{W@z7wU4us!#iv813l?IUp)Br%hRi(saux2(FDc%y#)b) zVr=+$Hpy%YGfhyZ=j1hW@aeY2Pl3Pi>e24!qQ^(evUO*E8}Rny80{W~&Pn~z?OvkS zFU^Dj?Z-vcr8}ucrig*lokmyCLZ40+D;u}(R@1YKXInfyaP0C)(1P2R4^Fxw-ik0X zO>%3|>L6p*!1tNB=I1c5Jf-g$D(M3m%QWsXU5{GAlyLFgM5h5`RwP3sW~?{@&3fC4 z+W>WVcGb|>b3wO8d5BR5X^r?|RR!!sEC}fg$~e;P#@2!@@RL4wbv%bE%rA5(yT!Yr z;)=qaN|6}GiXl8if1v|ziB<`mBypT!(IAFF3mu29aqw?X;={FeWuK56PWY~qsI9ld z=XhX^L;0tcowR|Xjd-Pkd@6yfFH-MchF}AwTdppMilh=3GpHHQCop6>lZo7>q#jZda_fw25x$V zwNfE0#heYI$mVaDuNY6ZA1`m%*g4c^eKs!)e%6m|ZlJRkkNL$jL|0$Dl8$A@)>spq z)3t54BP0C1RVM!Ym{C@Vfiks8|!1G9_odrsf?fcflyr16h# delta 3128 zcmbVOeNa?Y6u;*#`&`%s9xNd4^09n4vV)3WAtjT%GEGV+r*Rm|8B?>V9W+cD{7NWI za&(k9fIDUeC9Msli;sdCV8n5qOxL2K!iLGw$r^K}fzm%r&CY#G>&3s4o!$5D@7!~L zzkANT_uL)wMPBzs`V%3F0nj(X6vZ(e+;9*V!prujisotwNl!1bFMjOtc~8t=@Z`c` z48S?qqu20p05#|}O@}>r)^uR%nNNFJ9%FMDbD1>NqxBMYGdA167LV2`V?WkL6vB1< zI^yty4s0@ufj^tyfAE2J>{i^y;PLt$r30W9m&ZD>&oaf{+0x6j*)Fbm!J{2)A`nD1 zGIcB(>mr4QH?m?ZgP)0_!TRQ?c>r~I!n%y6IKlSxn3=H8wsb6mUy2sfsEN)5KH$S> zOEC=K-I(P@!U{(xYse>(rMJY^L)zO%!+XH2(VE}6x8`Q;1~!@PxF*qwd*WhXhyGq% z0YD@6$A2Nu(0t$CF?giCW7hlz&!F86JFqul0;VVAkAPDWp1|DxvKxoTPov~zCND7XGT!RLi{?c9DcK8W zal?eyQ}(p?vK-Fxxs4aOm)FiByWSFd)@OSYXZp-J)23B$x zmV9n;aXNpzT&qE!Bb7GdX~%q8N}KZ+Ma~l3mu3*lC8ZfC&1so*()8yuFN%XQC;ORu z>ps9d+JJFL^h4QM5io=LZ8CwsJW zGIn{i?_@lY)bS6uqjINgCDy$%Ce{WS6YC)v6YIw^Cf1fPwnbXCDH^CsCVL`UbusNi z^XLFB5w0AmZ%icfs_Z~o!!jnVn=&S?f5O;ik+fn-t2Nw3fWm5*F-digV3H>X(hA59 zq!pAgX?=rvxs%{9mgHto&EAwdwer&8(cxT`F-Z)|m=tcxm=xZ;Phn>m+ssllHkx0% zJd~p8COj&00oj4Hf-)wpei>6V7sA*UA!%7jt2*2j%G^E~Q&{^)a5!@x$PT3ClQC(v z;#ZR$l|OwnI$M`zOtintm?HR7#>D!!jEVJb7~8lMK_pH3({NWPg3kz#%2to;Kw76| zOj_qK2sbnC@y&Tweg?JwU1UUfu|@!W#cDiWo6Sv+b~hG@7v*>uFL8bGCs zU@NeOD<04C<-tZUY}|MQdrHYGaQbxE%QV%dVhzoYlkti^}FhA6A!5N%>aX z77;|2;?ZK%$p&xEBTO9hlh{|5k=8>+J%_P;<22I&`HV27T>NvQ{eZFS)8RDct#5{) z{{8wZ0NvQNVOreD5Uo)lV%5poX}rCm0rnx5hx-i4c4cBW?K2W(U+_)g~w3 zX?XHmenHLu^5fbH2mFph)O9gr>o;CXkDmy}8+vVi!OokFmx7`r#JoE+{0{bQcJkq4 zhS%j|c(THwe0_|(>_!3tShU4Se=O*!bO^H%1>Jwb>_b024`6W1YNxlGI>m!7?M$wL zhwfpmP8jR3c&jM0rCXm@j7vL!=eKSfY3fVfSQY_4>Md`c0L~Jzbo*lQ>fZhuZOptK zaV9vaYt>hxA_EgtCO!eot37C*vUcSw>+oEy!(14rvRV4IS|=p_=dw3mWt?1JS+$xq zqO@S9LjP<@phpfdr7fQBY^LAYGg1PHoxWSHP diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 2831315a06674278bd7e2bc8a224b3fa4ffc7634..471bceb93b60337506a2a63bfd2f5dc4e63fb9b0 100755 GIT binary patch delta 14726 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 V{E)5Mxxct8`RiPbbJX&_{|9R`CmsL* diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index db0729c87c2ed342252a61f6337ec3f576657a79..f5d1881c27d8b93065907d0e793ed70c5281c2cc 100755 GIT binary patch delta 257 zcmX?kjPv9%&Iubtm|2;b7#JCunHhm33o{cV2Q$~iBU8BSOwFvAS?yhz95yF0?lqZw zz`TLcXtJY)>t=bgXhtyeD3D~ge936I`J<&ZACS`Z@L`<1!bg^ulR<&Ok-?0Kft#6u zadW4qJoDsNW^R+YeZ3%N@bNG>&3bg?*U8;yX0UUAn=BXLxcRy7Yi>s4$(JL4Ga79^ zALYr&=rma}CK*W9#?&)9ZT=gh0TNP+3j%T~<1(3moW}{%ft>2Z+aQ_n^3Jc?lsx` z)GV5j(QvY(h5KYhvpO*AD3D~ge936M`J<&ZACS`Z@L?2SW?3utnVA`ZBnuN4BL_1x^TZKd&W(k?jtL}%)rRa!{9XQ(UD&#cb}QTzPZ9v zo_X>svui-?Hu;y&tj$Y(*;pCPH?Il$!p3Mmd10g>qtoWgk*-XPj*}&0l7VDxOg*FH z=D#r-j6jZBTo90~j9bCzyjdy!07&R@!gQc~b>eLhCpwZL p2S$g<;raSNvOPbH(P8tw{O2q{{l`icurfL{r&n!HuVQ@k0s#0ZXV(A# delta 342 zcmX?kjPv9%&Iy}@n3)(D8JU?Gfg~db3o|nl*Tf?Yist%W-mE^pejvFf!IFFgUVg zX)rM;F|s%)FaQ|}jE;-~T-=l2dAm>M_MIib%)rRa!{9XQ(UD&#cb}QTzIll+J1e8b z<~2cI*cdG)FN`!~G}?SQ(v^wPezIgtGLWo|sb{p`{5M8}5y(-C3j&graVr=dH!H;- z00})#m=2V$PP`4`geM0BIjfVqxPXS4<>@mTO-{@Ez-Tu)JYOG3w&#a2+HJm<|C|M= V|5(WaRz|z#^s4RYRg8~b006ZZYl#2= diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 8711cfca76723154a25f603751343bff408550e6..4aec2531643b31cec121ab865efd0a2f5dde4bd8 100755 GIT binary patch delta 171 zcmex#mh;nD&IuPpm{^&Z85kLvm>7X13lk#;6BF~qCsVoX&CIP?*j(J0T{kx~?lqbG z(7b`sc=8-`*Ub)Q(TrfG`{b1tbwHMe6 Qi89R`<=Z*R8DG5z0Da#ytN;K2 delta 173 zcmex#mh;nD&IuPpSeclZ85kLvm>7X1GZP~R3lr1CCsVnc&CIP?*j(M1?Kd|w?lsxW zW**JRXf*ktnd@W+vj!+@xdn)&Vfm8LWV4W!H6M`j^YCG02Aa5eo2LxxnupPN^2g}kj7FQ^#ssl2x@>kx^yX(Y-rQ3V&&KF7*&$J; NnWKC=M>*rG_W&qIGa&!~ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index d7d10f8a3601729dc55a246c024fdd57aef0b55a..0cbbe237616e5738290f3a5614ddb64b8aa56a3f 100755 GIT binary patch delta 1218 zcmah|Z%kWN6o2QvS61lg9yls3ZFh@hKoJ2St}<5ug~W)8m9Q)vpMb z^@a6d7gLU(030~z{1$RB;_4>6;u<3KyGJ2M%(_p?V8>marzz=*rYPdGd}$`70$-q;&-3~(@JWgLzJw)OBR0n0u?SI;=UVV zDGc-w9xA_X)zi&XaCDN&xiC^O0&e`Sq7ZWNPQ^onty|BKKucR|NFet%2Pk%n6YCN= zs%0J&d?QkY<<;Z3r*Z*jtCwIK{?OvZtNu7tU}4P+@n34XNt|$PKZ)~2ZQrUm@)~jO zVIsOcDnpGpw<9UT17eVW4#esjV>(MTH_OHyC#imCaMq|_Dq^i?6byv@bkT75FkQ3~ zeui*=#9>H(Pjv~Gcm1U{q*L^>P_`K?%)~%zz_ieZ(14$W8VT=&f|y$sz{SWUt>0ru zG|i+)VEF@kqg*V+$P8}0*w!X`qHpP2B{qi8ei;0szujk|E~g&-2EdO!`^unMOzoqV zIsUx=AYq_mi12F1SwnY?+4%H(hVIWDc%A~k9e9MWAwFt#(X(VRjen_!J=k~D22El) zJ_i)P_{5upO`VVB=w8s6ncppY+TYH;z@p@2-YX?^n$6xaJa`ark)OQ9L_kE}pFW)GAS1_#;+ z-yJw_%#+FN|IQ<#&t|B`5H^;%W(Zu4JH@%9EvnJ{#dl`RoPv5$@X{Wj zzK4gxDt*|+k+bsuw~wdBrlD0l{A!Vs+RD=S4XH;9;?VJWaEZC&;|k>A?g delta 1215 zcmah|T})GF7(VYgZ7r78!my*ioOZN8EA0?LpfdR?l)4paZD29ep+#3@*+g-f#5h=B zER&y5Uz2HMI^(j$ZHY@h31lOV3!`&m30Y!hceBOaXf&C-GBY#TdybOG#j=~{oacMr zyg$$Pe7Rg+E|)ho17dcjq)01Cq5vRCK&Sz|@|(STwA|vBpL?;YVSRI3yDqUU`TQDl zK{eCpTGvWN#on^=3V)!kzVW5aO*jr0p@i3p5A?7O%M6>Ll4lG{070xXrXk4B82`!F z+6?6u#qS?z$6;1#zuJiX3e(L`NynG~Z z8NiD5m9oi7wKPm-b7!cq6*HA*A%wbM7(zS}d@T7rN-r6hAg_`&=Ad9;4=+;T0Fja> zAz3Gb62krKZ<>^JJrx|8qH->bR}Vu8E>%~+3;3w|Wx|#Xr%9#XHZ+h*wi+8qHiH8j zvvw@rxQJgTqPVzi3RAV$@Dt+@?yc*FjrcfPiudc%P>YHBdE)sEJz7;HY>#ipO<}uM zQAolo@ub3*^%7U;^$g?hhLa*};x`&|B9!tAP2T~r?np{uv&}7{+H->XjkR7;`-S*G z?6icPZI7S@lkFbD!|j&{OA}iNdlE4$jMsw=72?K35^`@TbEa0WYFSs+g zn%_>H0#fE^$0+YjdPMr2NOd4|9)K`E*y-2OB-5|`1Q5pFT~$!Sr+3j?$pS9?Du zY~Huiu2{e<>)9POOT9qL6XL>e_kBP{VQ>6OxsFVVurCuOugzxu(z?k2Z2Wz4Lpi%l zAH@*ts=~|IeU6-(f~w1Zzb(yCF1FIqXgb$NL0F=hC%W781fi_@% diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 9bf70703e9321d30adc58bb57689f8bf4244d69a..247eb8a4a4ab8bf1402590e5b73c54b9d496b474 100755 GIT binary patch delta 27697 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/U{QoQKH6r(a)*Mk&kMI6(9O&N=0o^Ec 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 RR|^Hg^;4R1uEp;l{Xe)#b{hZy diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1abb89a73c..5bcad8929f 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -2456,7 +2456,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.12.0" +version = "0.12.1" dependencies = [ "async-trait", "bellman", @@ -2500,7 +2500,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.12.0" +version = "0.12.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -2541,7 +2541,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.12.0" +version = "0.12.1" dependencies = [ "quote", "syn", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "derivative", @@ -2563,7 +2563,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.12.0" +version = "0.12.1" dependencies = [ "chrono", "concat-idents", @@ -2592,7 +2592,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "masp_primitives", @@ -2607,7 +2607,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "hex", @@ -2618,7 +2618,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "namada_core", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.12.0" +version = "0.12.1" dependencies = [ "borsh", "getrandom 0.2.8", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index dd17f4c0dc..96a000968c 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.12.0" +version = "0.12.1" [lib] crate-type = ["cdylib"] From bcbcbb9db015caeee8b53080017fb4f00c896622 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 21 Dec 2022 10:20:51 +0000 Subject: [PATCH 1989/1995] Fix CI in eth-bridge-integration --- .github/workflows/build-and-test-bridge.yml | 8 ++++---- .github/workflows/build-and-test.yml | 8 ++++---- .github/workflows/build-tendermint.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 8762ec14d7..1e0827f724 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -151,7 +151,7 @@ jobs: suffix: '' cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus env: CARGO_INCREMENTAL: 0 @@ -349,7 +349,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - nightly_version: [nightly-2022-11-03] + nightly_version: [nightly-2022-05-20] mold_version: [1.7.0] make: - name: e2e @@ -357,14 +357,14 @@ jobs: index: 0 cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus wait_for: namada-release-eth (ubuntu-20.04, 1.7.0, ABCI Release build, namada-e2e-release, v2) - name: e2e suffix: '' index: 1 cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus wait_for: namada-release-eth (ubuntu-20.04, 1.7.0, ABCI Release build, namada-e2e-release, v2) env: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 89ebd8d82f..ae3be0fb0b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -153,7 +153,7 @@ jobs: suffix: '' cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus env: CARGO_INCREMENTAL: 0 @@ -351,7 +351,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04] - nightly_version: [nightly-2022-11-03] + nightly_version: [nightly-2022-05-20] mold_version: [1.7.0] make: - name: e2e @@ -359,14 +359,14 @@ jobs: index: 0 cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus wait_for: namada-release (ubuntu-20.04, 1.7.0, ABCI Release build, namada-e2e-release, v2) - name: e2e suffix: '' index: 1 cache_key: namada cache_version: v2 - tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_artifact: tendermint-unreleased-v0.1.4-abciplus wait_for: namada-release (ubuntu-20.04, 1.7.0, ABCI Release build, namada-e2e-release, v2) env: diff --git a/.github/workflows/build-tendermint.yml b/.github/workflows/build-tendermint.yml index 1e78f1d986..d7977a4b3b 100644 --- a/.github/workflows/build-tendermint.yml +++ b/.github/workflows/build-tendermint.yml @@ -23,7 +23,7 @@ jobs: make: - name: tendermint-unreleased repository: heliaxdev/tendermint - tendermint_version: ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 + tendermint_version: v0.1.4-abciplus steps: - name: Build ${{ matrix.make.name }} From 16173b0b2b7b995ff8175f00e9870994eb8fc276 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Dec 2022 11:02:46 +0000 Subject: [PATCH 1990/1995] [ci] wasm checksums update --- wasm/checksums.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 465dd6c321..ce1a992538 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.c80510dae9aa8785a33301a16747b358da20cb125edae01e330eba2153ca28f4.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.0335a3029d5748f1ba9c309dc09296924d4369c9bf560fb06cf2b0184f534188.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.655b344c2d9cc602f9250cf390112ff2c9293537cdc1eb4a52c58ee622ccb538.wasm", - "tx_ibc.wasm": "tx_ibc.de66b4cc33061f503c63964debda8061ab9e0f1294e6c5c510d4b94a409a2edf.wasm", - "tx_init_account.wasm": "tx_init_account.87940d07eac068e03cfe5cd8b491032b3f1814d36060cbc761beb528f1c27b46.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2464b3de1c3aa4ed25cf592366df3b744ec3d2030ce952ea18f38066d4091693.wasm", - "tx_init_validator.wasm": "tx_init_validator.6a27ed0de01ab555628129ac9f05801d5ac812824633bd36e43b1d67b6360db6.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.d22e9b3310bad29fea038b42b4120f12b11ffe0ec4012ddf2d709aed490cac97.wasm", - "tx_transfer.wasm": "tx_transfer.3f6d8d0c103bd631c7cd12b58663e026aa9743f8552b14919a1b5bcd9fd31741.wasm", - "tx_unbond.wasm": "tx_unbond.6b801584c4d01f7b52988e5ecf971050e1575f1e15f818f3e2694604b402ebd8.wasm", - "tx_update_vp.wasm": "tx_update_vp.ee2e9b882c4accadf4626e87d801c9ac8ea8c61ccea677e0532fc6c1ee7db6a2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.37269505f1526de9680b619413478a071ed2f92b22924e6fbb3b7127f88da41a.wasm", - "tx_withdraw.wasm": "tx_withdraw.e5affdbd26cdd08aff6dfc37c5b3d92a8784547e28d039b863a6a95d5ac1cc97.wasm", - "vp_implicit.wasm": "vp_implicit.2f0b200b9e0f3db5151e0b86f5e66ae1b7e43e01d8cdc852db1337c744fac4b6.wasm", - "vp_masp.wasm": "vp_masp.da15ab8670750f400fc12616921dc3c959386bdb5d2df4f455f2299847c257ee.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.13f1911794bc69e4d7dcebd30b048740398b5d45f7efee95a07a627b1a46fa47.wasm", - "vp_token.wasm": "vp_token.497a346ebcc1b7e7b844a8aab644a8d43ab9399006a95c7ca411035b8966b05e.wasm", - "vp_user.wasm": "vp_user.59c397bd2356d01cea5379de64882423cf1e2bb361301651573c1e3619d43551.wasm", - "vp_validator.wasm": "vp_validator.0b13e2f48407640ca35414c5b855f3e20f3393f60ff987aaa3b0febbb0d64e51.wasm" -} + "tx_bond.wasm": "tx_bond.aff95592a6394c8b766ebba48131d762b23bf86c3423279836749f64cd6173d8.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.1e5d9b7d20e9b014f76df5995fadcc1bb5bcceff2a4d2282e0e2adab3702d559.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.b34543d08b1aa73ddbbfbeaac08b8160c1738fe1a1601e6d5961ab827e3d37e0.wasm", + "tx_ibc.wasm": "tx_ibc.3700f2036627ca242b6c9139f2b3fb356487a33832e3247ace8c084733431fad.wasm", + "tx_init_account.wasm": "tx_init_account.719a06117fbe799507ada30d93f0fc726745cb38cd91fc0a5528889518fa9c67.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.657ca43087e12159032a1ed6b75a8cc441c68ce64775dffd143be4ea8280872f.wasm", + "tx_init_validator.wasm": "tx_init_validator.3f6d393c25d2c27dd828797821797b08d4bd18d3a2def95e3dd080e1fc2fcec6.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d870fceccbb0d6c8f6ce3513b1ef1e7cafe3c480781ccfbdb48992b469dfe5d7.wasm", + "tx_transfer.wasm": "tx_transfer.95f07ac2f3b87d75be7b05f2fc1399c7da998ee70718e7f6ab875c508705b84e.wasm", + "tx_unbond.wasm": "tx_unbond.1586c43aba2d86f1e0376d1d9850cf46f79a90fb4b2d5db0dd8f0661d88cba3c.wasm", + "tx_update_vp.wasm": "tx_update_vp.5e9abf7b7577aebf790b9a1c68568adcfdc8e42361949aae00e67803bf444eb2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.a473f41c6024f6f9e08ebb98de918b4534360d2102c18fd478c718ab3c15352f.wasm", + "tx_withdraw.wasm": "tx_withdraw.772e3cbdfde38aaead0a07393a6a4c0b15c86aed38fb9aebb4f5efb53b5e2a71.wasm", + "vp_implicit.wasm": "vp_implicit.1440282d60cb76c659828de6f0086a955e5400c1f5b44cda9e00fbce352bda3c.wasm", + "vp_masp.wasm": "vp_masp.9ab4a7a574e0cfe56f3da89fdb430235b7922f5d4d8f60ca99933c9e19646449.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.fb43fdfec782778da81eeb326677f0f079cb108444a5f7c3698a77e643363ce0.wasm", + "vp_token.wasm": "vp_token.b941d69d7b0c6de31d9e3fbb72b708e0b4d480112d318e1966a05e8af0d1c5bf.wasm", + "vp_user.wasm": "vp_user.4d1c16c065506f3ba9c0086af82740e89921e1a89c43fde79a1132201b3d6917.wasm", + "vp_validator.wasm": "vp_validator.a31e752aab8f0e56a99d40c58a0fb2b09c5d025d41f8a8c649e8751b0be0dc81.wasm" +} \ No newline at end of file From 61c2e0eeb5dd90f5c50d3b2493f39a63fab7c00b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 09:23:48 +0000 Subject: [PATCH 1991/1995] Prohibit encrypted txs at 2nd and 3rd height offs within epoch --- .../lib/node/ledger/shell/process_proposal.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index e0ae3fa39a..5ce9662c0e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,6 +2,7 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use data_encoding::HEXUPPER; +use namada::core::hints; use namada::core::ledger::storage::Storage; use namada::ledger::pos::{PosQueries, SendValsetUpd}; use namada::types::transaction::protocol::ProtocolTxType; @@ -548,6 +549,14 @@ where .into(), }; } + if hints::unlikely(!self.can_include_encrypted_txs()) { + return TxResult { + code: ErrorCodes::AllocationError.into(), + info: "Wrapper txs not allowed at the current block \ + height" + .into(), + }; + } // validate the ciphertext via Ferveo if !tx.validate_ciphertext() { @@ -619,6 +628,14 @@ where true } } + + /// Checks if it is possible to include encrypted txs at the current block + /// height. + fn can_include_encrypted_txs(&self) -> bool { + let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); + let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); + is_2nd_height_off || is_3rd_height_off + } } /// We test the failure cases of [`process_proposal`]. The happy flows From c79011f97bebd530c5b4445397acb1b11efa6629 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 09:37:47 +0000 Subject: [PATCH 1992/1995] Fix logic to reject encrypted txs at 2nd and 3rd heights within epoch --- apps/src/lib/node/ledger/shell/process_proposal.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 5ce9662c0e..a351e2b8aa 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -549,7 +549,7 @@ where .into(), }; } - if hints::unlikely(!self.can_include_encrypted_txs()) { + if hints::unlikely(self.encrypted_txs_not_allowed()) { return TxResult { code: ErrorCodes::AllocationError.into(), info: "Wrapper txs not allowed at the current block \ @@ -629,9 +629,9 @@ where } } - /// Checks if it is possible to include encrypted txs at the current block - /// height. - fn can_include_encrypted_txs(&self) -> bool { + /// Checks if it is not possible to include encrypted txs at the current + /// block height. + fn encrypted_txs_not_allowed(&self) -> bool { let is_2nd_height_off = self.storage.is_deciding_offset_within_epoch(1); let is_3rd_height_off = self.storage.is_deciding_offset_within_epoch(2); is_2nd_height_off || is_3rd_height_off From cfb770795cc6182aab0bca4c490a4b7f83b5b95e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 10:12:42 +0000 Subject: [PATCH 1993/1995] Add a unit test to check if we reject wrapper txs at 2nd and 3rd height offs --- .../lib/node/ledger/shell/process_proposal.rs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index a351e2b8aa..6d581d6d2e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1603,4 +1603,75 @@ mod test_process_proposal { ), ); } + + /// Test that if we reject wrapper txs + /// when they shouldn't be included in blocks. + /// + /// Currently, the conditions to reject wrapper + /// txs are simply to check if we are at the 2nd + /// or 3rd height offset within an epoch. + #[test] + fn test_include_only_protocol_txs() { + let (mut shell, _recv, _) = test_utils::setup_at_height(1u64); + let keypair = gen_keypair(); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some(b"transaction data".to_vec()), + ); + let wrapper = WrapperTx::new( + Fee { + amount: 1234.into(), + token: shell.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ) + .sign(&keypair) + .expect("Test failed") + .to_bytes(); + for height in [1u64, 2] { + shell.storage.last_height = height.into(); + #[cfg(feature = "abcipp")] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.clone(), get_empty_eth_ev_digest(&shell)], + }; + if let Err(TestError::RejectProposal(mut resp)) = + shell.process_proposal(request) + { + assert_eq!(resp.len(), 2); + resp.remove(0) + } else { + panic!("Test failed") + } + }; + #[cfg(not(feature = "abcipp"))] + let response = { + let request = ProcessProposal { + txs: vec![wrapper.clone()], + }; + if let Err(TestError::RejectProposal(mut resp)) = + shell.process_proposal(request) + { + assert_eq!(resp.len(), 1); + resp.remove(0) + } else { + panic!("Test failed") + } + }; + assert_eq!( + response.result.code, + u32::from(ErrorCodes::AllocationError) + ); + assert_eq!( + response.result.info, + String::from( + "Wrapper txs not allowed at the current block height" + ), + ); + } + } } From 651bd40969fec65422bb555a707d66f21eeace67 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 10:24:45 +0000 Subject: [PATCH 1994/1995] Update block space allocator specs --- documentation/specs/src/base-ledger/block-space-allocator.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/documentation/specs/src/base-ledger/block-space-allocator.md b/documentation/specs/src/base-ledger/block-space-allocator.md index 22bb5c9648..aa05dcbaea 100644 --- a/documentation/specs/src/base-ledger/block-space-allocator.md +++ b/documentation/specs/src/base-ledger/block-space-allocator.md @@ -158,6 +158,11 @@ MaxProposalBytes$ bytes of the available block space at $H$ (or any block height, in fact). * If all decrypted transactions from $H-1$ have been included in the proposal $P$, for height $H$. +* That no encrypted transactions were included in the proposal $P$, if no +encrypted transactions should be included at $H$. + - N.b. the conditions to reject encrypted transactions are still not clearly + specced out, therefore they will be left out of this section, for the + time being. Should any of these conditions not be met at some arbitrary round $R$ of $H$, all honest validators $V_h : V_h \subseteq V$ will reject the proposal $P$. From 1883821ce3919135fa5247abe971bcfd26b40e38 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 22 Dec 2022 10:28:37 +0000 Subject: [PATCH 1995/1995] Small docstr fix --- apps/src/lib/node/ledger/shell/process_proposal.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 6d581d6d2e..1a0004506e 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1604,8 +1604,7 @@ mod test_process_proposal { ); } - /// Test that if we reject wrapper txs - /// when they shouldn't be included in blocks. + /// Test if we reject wrapper txs when they shouldn't be included in blocks. /// /// Currently, the conditions to reject wrapper /// txs are simply to check if we are at the 2nd

Vs(y$9{|yB)eK7+~RgPt<`uwYW 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

8VD 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